Mercurial > games > semicongine
changeset 1176:511c9f7cd1da compiletime-tests
sync to notebook in bedroom
author | sam <sam@basx.dev> |
---|---|
date | Sat, 29 Jun 2024 21:04:04 +0700 |
parents | a94732d98cc6 |
children | 4ef959278451 |
files | static_utils.nim |
diffstat | 1 files changed, 242 insertions(+), 58 deletions(-) [+] |
line wrap: on
line diff
--- a/static_utils.nim Sat Jun 29 11:13:35 2024 +0700 +++ b/static_utils.nim Sat Jun 29 21:04:04 2024 +0700 @@ -21,6 +21,7 @@ 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 @@ -125,9 +126,10 @@ let `isinstancename` {.inject.} = hasCustomPragma(value, InstanceAttribute) body -template ForDescriptorFields*(inputData: typed, typename, countname, bindingNumber, body: untyped): untyped = +template ForDescriptorFields*(inputData: typed, fieldname, typename, countname, bindingNumber, body: untyped): untyped = var `bindingNumber` {.inject.} = 1'u32 for theFieldname, value in fieldPairs(inputData): + let `fieldname` {.inject.} = theFieldname when typeof(value) is Texture: block: let `typename` {.inject.} = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER @@ -195,9 +197,31 @@ type IndexType = enum None, UInt8, UInt16, UInt32 - RenderBuffers = object - deviceBuffers: seq[Buffer] # for fast reads - hostVisibleBuffers: seq[Buffer] # for fast writes + + IndirectGPUMemory = object + vk: VkDeviceMemory + size: uint64 + DirectGPUMemory = object + vk: VkDeviceMemory + size: uint64 + data: pointer + GPUMemory = IndirectGPUMemory | DirectGPUMemory + + Buffer[TMemory: GPUMemory] = object + vk: VkBuffer + memory*: TMemory + offset: uint64 + size: uint64 + + GPUArray[T: SupportedGPUType, TMemory: GPUMemory] = object + data: seq[T] + buffer: Buffer[TMemory] + offset: uint64 + GPUValue[T: object|array, TMemory: GPUMemory] = object + data: T + buffer: Buffer[TMemory] + offset: uint64 + Renderable[TMesh, TInstance] = object vertexBuffers: seq[VkBuffer] bufferOffsets: seq[VkDeviceSize] @@ -209,10 +233,19 @@ indexBuffer: VkBuffer indexCount: uint32 indexBufferOffset: VkDeviceSize + Pipeline[TShader] = object pipeline: VkPipeline layout: VkPipelineLayout - descriptorSets: array[INFLIGHTFRAMES.int, VkDescriptorSet] + descriptorSetLayout: VkDescriptorSetLayout + RenderData = object + descriptorPool: VkDescriptorPool + indirectMemory: seq[IndirectGPUMemory] + nextFreeIndirectMemoryOffset: seq[uint64] + indirectBuffers: seq[Buffer[IndirectGPUMemory]] + directMemory: seq[DirectGPUMemory] + nextFreeDirectMemoryOffset: seq[uint64] + directBuffers: seq[Buffer[DirectGPUMemory]] converter toVkIndexType(indexType: IndexType): VkIndexType = case indexType: @@ -424,15 +457,11 @@ polygonMode: VkPolygonMode = VK_POLYGON_MODE_FILL, cullMode: VkCullModeFlagBits = VK_CULL_MODE_BACK_BIT, frontFace: VkFrontFace = VK_FRONT_FACE_CLOCKWISE, + descriptorPoolLimit = 1024 ): 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] - ForDescriptorFields(default(TShader), descriptorType, descriptorCount, descriptorBindingNumber): + ForDescriptorFields(default(TShader), fieldName, descriptorType, descriptorCount, descriptorBindingNumber): layoutbindings.add VkDescriptorSetLayoutBinding( binding: descriptorBindingNumber, descriptorType: descriptorType, @@ -445,12 +474,12 @@ bindingCount: uint32(layoutbindings.len), pBindings: layoutbindings.ToCPointer ) - var descriptorSetLayout: VkDescriptorSetLayout - checkVkResult vkCreateDescriptorSetLayout(device, addr(layoutCreateInfo), nil, addr(descriptorSetLayout)) + result.descriptorSetLayout: VkDescriptorSetLayout + checkVkResult vkCreateDescriptorSetLayout(device, addr(layoutCreateInfo), nil, addr(result.descriptorSetLayout)) let pipelineLayoutInfo = VkPipelineLayoutCreateInfo( sType: VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, setLayoutCount: 1, - pSetLayouts: addr(descriptorSetLayout), + pSetLayouts: addr(result.descriptorSetLayout), # pushConstantRangeCount: uint32(pushConstants.len), # pPushConstantRanges: pushConstants.ToCPointer, ) @@ -582,37 +611,108 @@ addr(result.pipeline) ) - # create descriptors, one per frame-in-flight - let nSamplers = 0'u32 - let nUniformBuffers = 0'u32 +proc AllocateIndirectMemory(device: VkDevice, pDevice: VkPhysicalDevice, allocationSize: uint64): IndirectGPUMemory = + # chooses biggest memory type that has NOT VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT + result.size = allocationSize + + # find a good memory type + var physicalProperties: VkPhysicalDeviceMemoryProperties + vkGetPhysicalDeviceMemoryProperties(pDevice, addr physicalProperties) + + var biggestHeap: uint64 = -1 + var memoryTypeIndex = -1 + # try to find non-host-visible type + for i in 0 ..< physicalProperties.memoryTypeCount: + if VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT not in toEnums(physicalProperties.memoryTypes[i].propertyFlags) + let size = physicalProperties.memoryHeaps[physicalProperties.memoryTypes[i].heapIndex].size + if size > biggestHeap: + biggest = size + memoryTypeIndex = i + + # If we did not found a device-only memory type, let's just take the biggest overall + if memoryTypeIndex < 0: + for i in 0 ..< physicalProperties.memoryTypeCount: + let size = physicalProperties.memoryHeaps[physicalProperties.memoryTypes[i].heapIndex].size + if size > biggestHeap: + biggest = size + memoryTypeIndex = i + + assert memoryTypeIndex >= 0, "Unable to find indirect memory type" + var allocationInfo = VkMemoryAllocateInfo( + sType: VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + allocationSize: initialAllocationSize, + memoryTypeIndex: memoryTypeIndex, + ) + checkVkResult vkAllocateMemory( + device, + addr allocationInfo, + nil, + addr result.vk + ) + +proc AllocateDirectMemory(device: VkDevice, allocationSize: uint64): DirectGPUMemory = + result.size = allocationSize + + # find a good memory type + var physicalProperties: VkPhysicalDeviceMemoryProperties + vkGetPhysicalDeviceMemoryProperties(pDevice, addr physicalProperties) - 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 biggestHeap: uint64 = -1 + var memoryTypeIndex = -1 + # try to find host-visible type + for i in 0 ..< physicalProperties.memoryTypeCount: + if VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT in toEnums(physicalProperties.memoryTypes[i].propertyFlags) + let size = physicalProperties.memoryHeaps[physicalProperties.memoryTypes[i].heapIndex].size + if size > biggestHeap: + biggest = size + memoryTypeIndex = i - 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) + assert memoryTypeIndex >= 0, "Unable to find direct (aka host-visible) memory type" + var allocationInfo = VkMemoryAllocateInfo( + sType: VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + allocationSize: initialAllocationSize, + memoryTypeIndex: memoryTypeIndex, + ) + checkVkResult vkAllocateMemory( + device, + addr allocationInfo, + nil, + addr result.vk + ) + checkVkResult result.device.vk.vkMapMemory( + memory = result.vk, + offset = 0'u64, + size = result.size, + flags = VkMemoryMapFlags(0), + ppData = addr(result.data) + ) + +proc InitRenderData(device: VkDevice, descriptorPoolLimit = 1024): RenderData = + # allocate descriptor pools + var poolSizes = [ + VkDescriptorPoolSize(thetype: VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, descriptorCount: descriptorPoolLimit) + VkDescriptorPoolSize(thetype: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, descriptorCount: descriptorPoolLimit) + ] + var poolInfo = VkDescriptorPoolCreateInfo( + sType: VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + poolSizeCount: poolSizes.len.uint32, + pPoolSizes: poolSizes.ToCPointer, + maxSets: descriptorPoolLimit, + ) + checkVkResult vkCreateDescriptorPool(device, addr(poolInfo), nil, addr(result.descriptorPool)) + + # allocate some memory + var initialAllocationSize: 1_000_000_000 # TODO: make this more dynamic or something + result.indirectMemory = @[AllocateIndirectMemory(device, size=initialAllocationSize)] + result.nextFreeIndirectMemoryOffset = @[0'u64] + result.directMemory = @[AllocateDirectMemory(device, size=initialAllocationSize)] + result.nextFreeDirectMemoryOffset = @[0'u64] proc WriteDescriptors[TShader](device: VkDevice, pipeline: Pipeline[TShader]) = var descriptorSetWrites: seq[VkWriteDescriptorSet] - ForDescriptorFields(default(TShader), descriptorType, descriptorCount, descriptorBindingNumber): + # map (buffer + offset + range) to descriptor + # map (texture) to descriptor + ForDescriptorFields(default(TShader), fieldName, descriptorType, descriptorCount, descriptorBindingNumber): for frameInFlight in 0 ..< pipeline.descriptorSets.len: if descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER: # TODO @@ -653,7 +753,6 @@ proc CreateRenderable[TMesh, TInstance]( mesh: TMesh, instance: TInstance, - buffers: RenderBuffers, ): Renderable[TMesh, TInstance] = result.indexType = None @@ -676,23 +775,31 @@ # assert normal fields of TMesh|Globals == normal fields of TShaderDescriptors for inputName, inputValue in default(TShader).fieldPairs: var foundField = false + + # Vertex input data when hasCustomPragma(inputValue, VertexAttribute): assert typeof(inputValue) is SupportedGPUType for meshName, meshValue in default(TMesh).fieldPairs: when meshName == inputName: + assert meshValue is GPUArray, "Mesh attribute '" & meshName & "' must be of type 'GPUArray' but is of type " & tt.name(typeof(meshValue)) 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)) & "'" + assert elementType(meshValue.data) 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.data)) & "'" foundField = true assert foundField, "Shader input '" & tt.name(TShader) & "." & inputName & ": " & tt.name(typeof(inputValue)) & "' not found in '" & tt.name(TMesh) & "'" + + # Instance input data elif hasCustomPragma(inputValue, InstanceAttribute): assert typeof(inputValue) is SupportedGPUType for instanceName, instanceValue in default(TInstance).fieldPairs: when instanceName == inputName: + assert instanceValue is GPUArray, "Instance attribute '" & instanceName & "' must be of type 'GPUArray' but is of type " & tt.name(typeof(instanceName)) 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)) & "'" + assert elementType(instanceValue.data) 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.data)) & "'" 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: + + # Texture + elif typeof(inputValue) is Texture: 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" @@ -704,8 +811,28 @@ 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) & "'" + + # Uniform block + elif typeof(inputValue) is object: + for meshName, meshValue in default(TMesh).fieldPairs: + when meshName == inputName: + assert meshValue is GPUValue, "Mesh attribute '" & meshName & "' must be of type 'GPUValue' but is of type " & tt.name(typeof(meshValue)) + assert foundField == false, "Shader input '" & tt.name(TShader) & "." & inputName & "' has been found more than once" + assert typeof(meshValue.data) 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.data)) & "'" + foundField = true + for globalName, globalValue in default(TGlobals).fieldPairs: + when globalName == inputName: + assert globalValue is GPUValue, "global attribute '" & globalName & "' must be of type 'GPUValue' but is of type " & tt.name(typeof(globalValue)) + assert foundField == false, "Shader input '" & tt.name(TShader) & "." & inputName & "' has been found more than once" + assert typeof(globalValue.data) 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.data)) & "'" + foundField = true + assert foundField, "Shader input '" & tt.name(TShader) & "." & inputName & ": " & tt.name(typeof(inputValue)) & "' not found in '" & tt.name(TMesh) & "|" & tt.name(TGlobals) & "'" + + # array elif typeof(inputValue) is array: - when (elementType(inputValue) is Texture or elementType(inputValue) is object): + + # texture-array + when elementType(inputValue) is Texture: 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" @@ -718,6 +845,22 @@ foundField = true assert foundField, "Shader input '" & tt.name(TShader) & "." & inputName & ": " & tt.name(typeof(inputValue)) & "' not found in '" & tt.name(TMesh) & "|" & tt.name(TGlobals) & "'" + # uniform-block array + elif elementType(inputValue) is object: + for meshName, meshValue in default(TMesh).fieldPairs: + when meshName == inputName: + assert meshValue is GPUValue, "Mesh attribute '" & meshName & "' must be of type 'GPUValue' but is of type " & tt.name(typeof(meshValue)) + assert foundField == false, "Shader input '" & tt.name(TShader) & "." & inputName & "' has been found more than once" + assert typeof(meshValue.data) 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.data)) & "'" + foundField = true + for globalName, globalValue in default(TGlobals).fieldPairs: + when globalName == inputName: + assert globalValue is GPUValue, "global attribute '" & globalName & "' must be of type 'GPUValue' but is of type " & tt.name(typeof(globalValue)) + assert foundField == false, "Shader input '" & tt.name(TShader) & "." & inputName & "' has been found more than once" + assert typeof(globalValue.data) 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.data)) & "'" + 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], @@ -768,28 +911,29 @@ ShaderSettings = object brightness: float32 MeshA = object - position: seq[Vec3f] - transparency: float - material: array[3, MaterialA] - materialTextures: array[3, Texture] + position: GPUArray[Vec3f, IndirectGPUMemory] InstanceA = object - transform: seq[Mat4] - position: seq[Vec3f] - Globals = object + rotation: GPUArray[Vec4f, IndirectGPUMemory] + objPosition: GPUArray[Vec3f, IndirectGPUMemory] + UniformsA = object + materials: GPUValue[array[3, MaterialA], IndirectGPUMemory] + materialTextures: array[3, Texture] + GlobalsA = object fontAtlas: Texture - settings: ShaderSettings + settings: GPUValue[ShaderSettings, IndirectGPUMemory] ShaderA = object # vertex input position {.VertexAttribute.}: Vec3f - transform {.InstanceAttribute.}: Mat4 + objPosition {.InstanceAttribute.}: Vec3f + rotation {.InstanceAttribute.}: Vec4f # intermediate test {.Pass.}: float32 test1 {.PassFlat.}: Vec3f # output color {.ShaderOutput.}: Vec4f # uniforms - material: array[3, MaterialA] + materials: array[3, MaterialA] settings: ShaderSettings # textures fontAtlas: Texture @@ -806,7 +950,6 @@ layers = @["VK_LAYER_KHRONOS_validation"], ) - let selectedPhysicalDevice = i.GetPhysicalDevices().FilterBestGraphics() let dev = i.CreateDevice( selectedPhysicalDevice, @@ -816,8 +959,26 @@ let frameWidth = 100'u32 let frameHeight = 100'u32 - var myRenderable: Renderable[MeshA, InstanceA] - var myGlobals: Globals + var myMesh1 = MeshA( + position: GPUArray[Vec3f, IndirectGPUMemory](data: @[NewVec3f(0, 0, ), NewVec3f(0, 0, ), NewVec3f(0, 0, )]), + ) + var uniforms1 = UniformsA( + materials: GPUValue[array[3, MaterialA], IndirectGPUMemory](data: [ + MaterialA(reflection: 0, baseColor: NewVec3f(1, 0, 0)), + MaterialA(reflection: 0.1, baseColor: NewVec3f(0, 1, 0)), + MaterialA(reflection: 0.5, baseColor: NewVec3f(0, 0, 1)), + ]), + materialTextures: [ + Texture(isGrayscale: false, colorImage: Image[RGBAPixel](width: 1, height: 1, imagedata: @[[255'u8, 0'u8, 0'u8, 255'u8]])), + Texture(isGrayscale: false, colorImage: Image[RGBAPixel](width: 1, height: 1, imagedata: @[[0'u8, 255'u8, 0'u8, 255'u8]])), + Texture(isGrayscale: false, colorImage: Image[RGBAPixel](width: 1, height: 1, imagedata: @[[0'u8, 0'u8, 255'u8, 255'u8]])), + ] + ) + var instances1 = InstanceA( + rotation: GPUArray[Vec4f, IndirectGPUMemory](data: @[NewVec4f(1, 0, 0, 0.1), NewVec4f(0, 1, 0, 0.1)]), + objPosition: GPUArray[Vec3f, IndirectGPUMemory](data: @[NewVec3f(0, 0, 0), NewVec3f(1, 1, 1)]), + ) + var myGlobals: GlobalsA # setup for rendering (TODO: swapchain & framebuffers) @@ -829,7 +990,30 @@ let shaderObject = dev.vk.CompileShader(shader) var pipeline1 = CreatePipeline(dev.vk, renderPass = renderpass, shaderObject) + var renderdata = InitRenderData(dev.vk) + + # create descriptor sets + var descriptorSets: array[INFLIGHTFRAMES.int, VkDescriptorSet] + var layouts = newSeqWith(descriptorSets.len, pipeline.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), descriptorSets.ToCPointer) + + + #[ # TODO: probably here: allocate renderables, uniform buffers & textures + let meshBuffers: seq[(bool, uint64)] = GetBufferSizes[MeshA](item = myMesh1) + let instanceBuffers: seq[(bool, uint64)] = GetBufferSizes[InstanceA](item = instances1) + let globalBuffers: seq[(bool, uint64)] = GetBufferSizes[Globals](item = myGlobals) + var myRenderable = CreateRenderable() + UploadTextures[MeshA]() + UploadTextures[Globals]() + ]# + var myRenderable: Renderable[MeshA, InstanceA] # descriptors WriteDescriptors(dev.vk, pipeline1)