# HG changeset patch # User Sam # Date 1701008806 -25200 # Node ID 970f74d5284e9d0ea4d256f061a8bf459d08824b # Parent 0d46e6638bd689d400febff041bbe85da5e58838 did: small refactoring and some bug fixes diff -r 0d46e6638bd6 -r 970f74d5284e src/semicongine/material.nim --- a/src/semicongine/material.nim Sun Nov 26 19:53:00 2023 +0700 +++ b/src/semicongine/material.nim Sun Nov 26 21:26:46 2023 +0700 @@ -9,7 +9,8 @@ type MaterialType* = object name*: string - meshAttributes*: Table[string, DataType] + vertexAttributes*: Table[string, DataType] + instanceAttributes*: Table[string, DataType] attributes*: Table[string, DataType] MaterialData* = object theType*: MaterialType @@ -30,30 +31,30 @@ let EMPTY_MATERIAL* = MaterialType( name: "empty material", - meshAttributes: {"position": Vec3F32}.toTable, + vertexAttributes: {"position": Vec3F32}.toTable, ) let COLORED_MATERIAL* = MaterialType( name: "single color material", - meshAttributes: {"position": Vec3F32}.toTable, + vertexAttributes: {"position": Vec3F32}.toTable, attributes: {"color": Vec4F32}.toTable, ) let VERTEX_COLORED_MATERIAL* = MaterialType( name: "vertex color material", - meshAttributes: { + vertexAttributes: { "position": Vec3F32, "color": Vec4F32, }.toTable, ) let SINGLE_COLOR_MATERIAL* = MaterialType( name: "single color material", - meshAttributes: { + vertexAttributes: { "position": Vec3F32, }.toTable, attributes: {"color": Vec4F32}.toTable ) let SINGLE_TEXTURE_MATERIAL* = MaterialType( name: "single texture material", - meshAttributes: { + vertexAttributes: { "position": Vec3F32, "uv": Vec2F32, }.toTable, @@ -61,7 +62,7 @@ ) let COLORED_SINGLE_TEXTURE_MATERIAL* = MaterialType( name: "colored single texture material", - meshAttributes: { + vertexAttributes: { "position": Vec3F32, "uv": Vec2F32, }.toTable, diff -r 0d46e6638bd6 -r 970f74d5284e src/semicongine/mesh.nim --- a/src/semicongine/mesh.nim Sun Nov 26 19:53:00 2023 +0700 +++ b/src/semicongine/mesh.nim Sun Nov 26 21:26:46 2023 +0700 @@ -42,7 +42,7 @@ mesh.material func `material=`*(mesh: var MeshObject, material: MaterialData) = - for name, theType in material.theType.meshAttributes: + for name, theType in material.theType.vertexAttributes: if mesh.vertexData.contains(name): assert mesh.vertexData[name].theType == theType, &"{material.theType} expected mesh attribute '{name}' to be '{theType}' but it is {mesh.vertexData[name].theType}" elif mesh.instanceData.contains(name): @@ -395,8 +395,8 @@ transform: mesh.transform, instanceTransforms: mesh.instanceTransforms, visible: mesh.visible, + material: mesh.material ) - `material=`(result, mesh.material) for attribute, datalist in mesh.vertexData.pairs: result.initVertexAttribute(attribute, datalist.theType) for attribute, datalist in mesh.instanceData.pairs: diff -r 0d46e6638bd6 -r 970f74d5284e src/semicongine/renderer.nim --- a/src/semicongine/renderer.nim Sun Nov 26 19:53:00 2023 +0700 +++ b/src/semicongine/renderer.nim Sun Nov 26 21:26:46 2023 +0700 @@ -62,9 +62,9 @@ func inputs(renderer: Renderer, scene: Scene): seq[ShaderAttribute] = var found: Table[string, ShaderAttribute] for i in 0 ..< renderer.renderPass.subpasses.len: - for (materialType, pipeline) in renderer.renderPass.subpasses[i].pipelines: + for (materialType, shaderPipeline) in renderer.renderPass.subpasses[i].shaderPipelines: if scene.usesMaterial(materialType): - for input in pipeline.inputs: + for input in shaderPipeline.inputs: if found.contains(input.name): assert input.name == found[input.name].name, &"{input.name}: {input.name} != {found[input.name].name}" assert input.theType == found[input.name].theType, &"{input.name}: {input.theType} != {found[input.name].theType}" @@ -74,8 +74,8 @@ result.add input found[input.name] = input -func materialCompatibleWithPipeline(scene: Scene, materialType: MaterialType, pipeline: Pipeline): (bool, string) = - for uniform in pipeline.uniforms: +func materialCompatibleWithPipeline(scene: Scene, materialType: MaterialType, shaderPipeline: ShaderPipeline): (bool, string) = + for uniform in shaderPipeline.uniforms: if scene.shaderGlobals.contains(uniform.name): if scene.shaderGlobals[uniform.name].theType != uniform.theType: return (true, &"shader uniform needs type {uniform.theType} but scene global is of type {scene.shaderGlobals[uniform.name].theType}") @@ -87,7 +87,7 @@ break if not foundMatch: return (true, &"shader uniform '{uniform.name}' was not found in scene globals or scene materials") - for texture in pipeline.samplers: + for texture in shaderPipeline.samplers: if scene.shaderGlobals.contains(texture.name): if scene.shaderGlobals[texture.name].theType != texture.theType: return (true, &"shader texture '{texture.name}' needs type {texture.theType} but scene global is of type {scene.shaderGlobals[texture.name].theType}") @@ -102,9 +102,10 @@ return (false, "") -func meshCompatibleWithPipeline(scene: Scene, mesh: Mesh, pipeline: Pipeline): (bool, string) = - for input in pipeline.inputs: - if input.name == TRANSFORM_ATTRIBUTE: # will be populated automatically +func meshCompatibleWithPipeline(scene: Scene, mesh: Mesh, shaderPipeline: ShaderPipeline): (bool, string) = + for input in shaderPipeline.inputs: + if input.name in [TRANSFORM_ATTRIBUTE, MATERIALINDEX_ATTRIBUTE]: # will be populated automatically + assert input.perInstance == true, &"Currently the {input.name} attribute must be a per instance attribute" continue if not (input.name in mesh[].attributes): return (true, &"Shader input '{input.name}' is not available for mesh") @@ -115,7 +116,7 @@ if input.perInstance and not mesh[].instanceAttributes.contains(input.name): return (true, &"Shader input '{input.name}' expected to be per instance attribute, but mesh has no such instance attribute (available are: {mesh[].instanceAttributes})") - let pipelineCompatability = scene.materialCompatibleWithPipeline(mesh.material.theType, pipeline) + let pipelineCompatability = scene.materialCompatibleWithPipeline(mesh.material.theType, shaderPipeline) if pipelineCompatability[0]: return (true, pipelineCompatability[1]) return (false, "") @@ -128,13 +129,13 @@ var foundRenderableObject = false var materialTypes: seq[MaterialType] for i in 0 ..< renderer.renderPass.subpasses.len: - for (materialType, pipeline) in renderer.renderPass.subpasses[i].pipelines: + for (materialType, shaderPipeline) in renderer.renderPass.subpasses[i].shaderPipelines: materialTypes.add materialType for mesh in scene.meshes: if mesh.material.theType == materialType: foundRenderableObject = true - let (error, message) = scene.meshCompatibleWithPipeline(mesh, pipeline) - assert not error, &"Mesh '{mesh}' not compatible with assigned pipeline ({materialType}) because: {message}" + let (error, message) = scene.meshCompatibleWithPipeline(mesh, shaderPipeline) + assert not error, &"Mesh '{mesh}' not compatible with assigned shaderPipeline ({materialType}) because: {message}" if not foundRenderableObject: var matTypes: Table[string, MaterialType] @@ -158,7 +159,7 @@ # automatically populate material and tranform attributes for mesh in scene.meshes: if not (TRANSFORM_ATTRIBUTE in mesh[].attributes): - mesh[].initInstanceAttribute(TRANSFORM_ATTRIBUTE, Unit4) + mesh[].initInstanceAttribute(TRANSFORM_ATTRIBUTE, Unit4) if not (MATERIALINDEX_ATTRIBUTE in mesh[].attributes): mesh[].initInstanceAttribute(MATERIALINDEX_ATTRIBUTE, uint16(scenedata.materials[mesh.material.theType].find(mesh.material))) @@ -221,19 +222,20 @@ for mesh in scene.meshes: for attribute in inputs: scenedata.vertexBufferOffsets[(mesh, attribute.name)] = perLocationOffsets[attribute.memoryPerformanceHint] - let size = mesh[].getRawData(attribute.name)[1] - perLocationOffsets[attribute.memoryPerformanceHint] += size - if perLocationOffsets[attribute.memoryPerformanceHint] mod VERTEX_ATTRIB_ALIGNMENT != 0: - perLocationOffsets[attribute.memoryPerformanceHint] += VERTEX_ATTRIB_ALIGNMENT - (perLocationOffsets[attribute.memoryPerformanceHint] mod VERTEX_ATTRIB_ALIGNMENT) + if mesh[].attributes.contains(attribute.name): + let size = mesh[].getRawData(attribute.name)[1] + perLocationOffsets[attribute.memoryPerformanceHint] += size + if perLocationOffsets[attribute.memoryPerformanceHint] mod VERTEX_ATTRIB_ALIGNMENT != 0: + perLocationOffsets[attribute.memoryPerformanceHint] += VERTEX_ATTRIB_ALIGNMENT - (perLocationOffsets[attribute.memoryPerformanceHint] mod VERTEX_ATTRIB_ALIGNMENT) - # fill offsets per pipeline (as sequence corresponds to shader input binding) + # fill offsets per shaderPipeline (as sequence corresponds to shader input binding) var offsets: Table[VkPipeline, seq[(string, MemoryPerformanceHint, int)]] for subpass_i in 0 ..< renderer.renderPass.subpasses.len: - for (materialType, pipeline) in renderer.renderPass.subpasses[subpass_i].pipelines: + for (materialType, shaderPipeline) in renderer.renderPass.subpasses[subpass_i].shaderPipelines: if scene.usesMaterial(materialType): - offsets[pipeline.vk] = newSeq[(string, MemoryPerformanceHint, int)]() - for attribute in pipeline.inputs: - offsets[pipeline.vk].add (attribute.name, attribute.memoryPerformanceHint, scenedata.vertexBufferOffsets[(mesh, attribute.name)]) + offsets[shaderPipeline.vk] = newSeq[(string, MemoryPerformanceHint, int)]() + for attribute in shaderPipeline.inputs: + offsets[shaderPipeline.vk].add (attribute.name, attribute.memoryPerformanceHint, scenedata.vertexBufferOffsets[(mesh, attribute.name)]) # create drawables let indexed = mesh.indexType != MeshIndexType.None @@ -263,17 +265,17 @@ # setup uniforms and textures (anything descriptor) var uploadedTextures: Table[Texture, VulkanTexture] for subpass_i in 0 ..< renderer.renderPass.subpasses.len: - for (materialType, pipeline) in renderer.renderPass.subpasses[subpass_i].pipelines: + for (materialType, shaderPipeline) in renderer.renderPass.subpasses[subpass_i].shaderPipelines: if scene.usesMaterial(materialType): # gather textures - scenedata.textures[pipeline.vk] = initTable[string, seq[VulkanTexture]]() - for texture in pipeline.samplers: - scenedata.textures[pipeline.vk][texture.name] = newSeq[VulkanTexture]() + scenedata.textures[shaderPipeline.vk] = initTable[string, seq[VulkanTexture]]() + for texture in shaderPipeline.samplers: + scenedata.textures[shaderPipeline.vk][texture.name] = newSeq[VulkanTexture]() if scene.shaderGlobals.contains(texture.name): for textureValue in getValues[Texture](scene.shaderGlobals[texture.name])[]: if not uploadedTextures.contains(textureValue): uploadedTextures[textureValue] = renderer.device.uploadTexture(textureValue) - scenedata.textures[pipeline.vk][texture.name].add uploadedTextures[textureValue] + scenedata.textures[shaderPipeline.vk][texture.name].add uploadedTextures[textureValue] else: var foundTexture = false for material in scene.getMaterials(materialType): @@ -286,20 +288,20 @@ let textureValue = getValues[Texture](value)[][0] if not uploadedTextures.contains(textureValue): uploadedTextures[textureValue] = renderer.device.uploadTexture(textureValue) - scenedata.textures[pipeline.vk][texture.name].add uploadedTextures[textureValue] + scenedata.textures[shaderPipeline.vk][texture.name].add uploadedTextures[textureValue] break assert foundTexture, &"No texture found in shaderGlobals or materials for '{texture.name}'" - let nTextures = scenedata.textures[pipeline.vk][texture.name].len + let nTextures = scenedata.textures[shaderPipeline.vk][texture.name].len assert (texture.arrayCount == 0 and nTextures == 1) or texture.arrayCount == nTextures, &"Shader assigned to render '{materialType}' expected {texture.arrayCount} textures for '{texture.name}' but got {nTextures}" # gather uniform sizes var uniformBufferSize = 0 - for uniform in pipeline.uniforms: + for uniform in shaderPipeline.uniforms: uniformBufferSize += uniform.size if uniformBufferSize > 0: - scenedata.uniformBuffers[pipeline.vk] = newSeq[Buffer]() + scenedata.uniformBuffers[shaderPipeline.vk] = newSeq[Buffer]() for frame_i in 0 ..< renderer.swapchain.inFlightFrames: - scenedata.uniformBuffers[pipeline.vk].add renderer.device.createBuffer( + scenedata.uniformBuffers[shaderPipeline.vk].add renderer.device.createBuffer( size=uniformBufferSize, usage=[VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT], requireMappable=true, @@ -308,23 +310,23 @@ # setup descriptors var poolsizes = @[(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, renderer.swapchain.inFlightFrames)] - if scenedata.textures[pipeline.vk].len > 0: + if scenedata.textures[shaderPipeline.vk].len > 0: var textureCount = 0 - for textures in scenedata.textures[pipeline.vk].values: + for textures in scenedata.textures[shaderPipeline.vk].values: textureCount += textures.len poolsizes.add (VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, renderer.swapchain.inFlightFrames * textureCount * 2) - scenedata.descriptorPools[pipeline.vk] = renderer.device.createDescriptorSetPool(poolsizes) + scenedata.descriptorPools[shaderPipeline.vk] = renderer.device.createDescriptorSetPool(poolsizes) - scenedata.descriptorSets[pipeline.vk] = pipeline.setupDescriptors( - scenedata.descriptorPools[pipeline.vk], - scenedata.uniformBuffers.getOrDefault(pipeline.vk, @[]), - scenedata.textures[pipeline.vk], + scenedata.descriptorSets[shaderPipeline.vk] = shaderPipeline.setupDescriptors( + scenedata.descriptorPools[shaderPipeline.vk], + scenedata.uniformBuffers.getOrDefault(shaderPipeline.vk, @[]), + scenedata.textures[shaderPipeline.vk], inFlightFrames=renderer.swapchain.inFlightFrames, emptyTexture=renderer.emptyTexture, ) for frame_i in 0 ..< renderer.swapchain.inFlightFrames: - scenedata.descriptorSets[pipeline.vk][frame_i].writeDescriptorSet() + scenedata.descriptorSets[shaderPipeline.vk][frame_i].writeDescriptorSet() renderer.scenedata[scene] = scenedata @@ -367,20 +369,20 @@ # loop over all used shaders/pipelines for i in 0 ..< renderer.renderPass.subpasses.len: - for (materialType, pipeline) in renderer.renderPass.subpasses[i].pipelines: + for (materialType, shaderPipeline) in renderer.renderPass.subpasses[i].shaderPipelines: if ( scene.usesMaterial(materialType) and - renderer.scenedata[scene].uniformBuffers.hasKey(pipeline.vk) and - renderer.scenedata[scene].uniformBuffers[pipeline.vk].len != 0 + renderer.scenedata[scene].uniformBuffers.hasKey(shaderPipeline.vk) and + renderer.scenedata[scene].uniformBuffers[shaderPipeline.vk].len != 0 ): - assert renderer.scenedata[scene].uniformBuffers[pipeline.vk][renderer.swapchain.currentInFlight].vk.valid + assert renderer.scenedata[scene].uniformBuffers[shaderPipeline.vk][renderer.swapchain.currentInFlight].vk.valid if forceAll: - for buffer in renderer.scenedata[scene].uniformBuffers[pipeline.vk]: + for buffer in renderer.scenedata[scene].uniformBuffers[shaderPipeline.vk]: assert buffer.vk.valid var offset = 0 - # loop over all uniforms of the shader-pipeline - for uniform in pipeline.uniforms: + # loop over all uniforms of the shader-shaderPipeline + for uniform in shaderPipeline.uniforms: if dirty.contains(uniform.name) or forceAll: # only update if necessary var value: DataList if scene.shaderGlobals.hasKey(uniform.name): @@ -406,7 +408,7 @@ # TODO: technically we would only need to update the uniform buffer of the current # frameInFlight, but we don't track for which frame the shaderglobals are no longer dirty # therefore we have to update the uniform values in all buffers, of all inFlightframes (usually 2) - for buffer in renderer.scenedata[scene].uniformBuffers[pipeline.vk]: + for buffer in renderer.scenedata[scene].uniformBuffers[shaderPipeline.vk]: buffer.setData(pdata, size, offset) offset += uniform.size scene.clearDirtyShaderGlobals() @@ -436,13 +438,13 @@ debug " Index buffer: ", renderer.scenedata[scene].indexBuffer for i in 0 ..< renderer.renderPass.subpasses.len: - for (materialType, pipeline) in renderer.renderPass.subpasses[i].pipelines: + for (materialType, shaderPipeline) in renderer.renderPass.subpasses[i].shaderPipelines: if scene.usesMaterial(materialType): - debug &"Start pipeline for '{materialType}'" - commandBuffer.vkCmdBindPipeline(renderer.renderPass.subpasses[i].pipelineBindPoint, pipeline.vk) - commandBuffer.vkCmdBindDescriptorSets(renderer.renderPass.subpasses[i].pipelineBindPoint, pipeline.layout, 0, 1, addr(renderer.scenedata[scene].descriptorSets[pipeline.vk][renderer.swapchain.currentInFlight].vk), 0, nil) + debug &"Start shaderPipeline for '{materialType}'" + commandBuffer.vkCmdBindPipeline(renderer.renderPass.subpasses[i].pipelineBindPoint, shaderPipeline.vk) + commandBuffer.vkCmdBindDescriptorSets(renderer.renderPass.subpasses[i].pipelineBindPoint, shaderPipeline.layout, 0, 1, addr(renderer.scenedata[scene].descriptorSets[shaderPipeline.vk][renderer.swapchain.currentInFlight].vk), 0, nil) for (drawable, mesh) in renderer.scenedata[scene].drawables.filterIt(it[1].visible and it[1].material.theType == materialType): - drawable.draw(commandBuffer, vertexBuffers=renderer.scenedata[scene].vertexBuffers, indexBuffer=renderer.scenedata[scene].indexBuffer, pipeline.vk) + drawable.draw(commandBuffer, vertexBuffers=renderer.scenedata[scene].vertexBuffers, indexBuffer=renderer.scenedata[scene].indexBuffer, shaderPipeline.vk) if i < renderer.renderPass.subpasses.len - 1: commandBuffer.vkCmdNextSubpass(VK_SUBPASS_CONTENTS_INLINE) diff -r 0d46e6638bd6 -r 970f74d5284e src/semicongine/text.nim --- a/src/semicongine/text.nim Sun Nov 26 19:53:00 2023 +0700 +++ b/src/semicongine/text.nim Sun Nov 26 21:26:46 2023 +0700 @@ -29,7 +29,7 @@ UV_ATTRIB = SHADER_ATTRIB_PREFIX & "uv" TEXT_MATERIAL_TYPE* = MaterialType( name: "default-text-material-type", - meshAttributes: {TRANSFORM_ATTRIB: Mat4F32, POSITION_ATTRIB: Vec3F32, UV_ATTRIB: Vec2F32}.toTable, + vertexAttributes: {TRANSFORM_ATTRIB: Mat4F32, POSITION_ATTRIB: Vec3F32, UV_ATTRIB: Vec2F32}.toTable, attributes: {"fontAtlas": TextureType}.toTable, ) TEXT_SHADER* = createShaderConfiguration( diff -r 0d46e6638bd6 -r 970f74d5284e src/semicongine/vulkan/pipeline.nim --- a/src/semicongine/vulkan/pipeline.nim Sun Nov 26 19:53:00 2023 +0700 +++ b/src/semicongine/vulkan/pipeline.nim Sun Nov 26 21:26:46 2023 +0700 @@ -10,7 +10,7 @@ import ./image type - Pipeline* = object + ShaderPipeline* = object device*: Device vk*: VkPipeline layout*: VkPipelineLayout @@ -18,16 +18,16 @@ shaderModules*: (ShaderModule, ShaderModule) descriptorSetLayout*: DescriptorSetLayout -func inputs*(pipeline: Pipeline): seq[ShaderAttribute] = +func inputs*(pipeline: ShaderPipeline): seq[ShaderAttribute] = pipeline.shaderConfiguration.inputs -func uniforms*(pipeline: Pipeline): seq[ShaderAttribute] = +func uniforms*(pipeline: ShaderPipeline): seq[ShaderAttribute] = pipeline.shaderConfiguration.uniforms -func samplers*(pipeline: Pipeline): seq[ShaderAttribute] = +func samplers*(pipeline: ShaderPipeline): seq[ShaderAttribute] = pipeline.shaderConfiguration.samplers -proc setupDescriptors*(pipeline: Pipeline, descriptorPool: DescriptorPool, buffers: seq[Buffer], textures: var Table[string, seq[VulkanTexture]], inFlightFrames: int, emptyTexture: VulkanTexture): seq[DescriptorSet] = +proc setupDescriptors*(pipeline: ShaderPipeline, descriptorPool: DescriptorPool, buffers: seq[Buffer], textures: var Table[string, seq[VulkanTexture]], inFlightFrames: int, emptyTexture: VulkanTexture): seq[DescriptorSet] = assert pipeline.vk.valid assert buffers.len == 0 or buffers.len == inFlightFrames # need to guard against this in case we have no uniforms, then we also create no buffers @@ -55,7 +55,7 @@ descriptor.imageviews.add emptyTexture.imageView descriptor.samplers.add emptyTexture.sampler -proc createPipeline*(device: Device, renderPass: VkRenderPass, shaderConfiguration: ShaderConfiguration, inFlightFrames: int, subpass = 0'u32, backFaceCulling=true): Pipeline = +proc createPipeline*(device: Device, renderPass: VkRenderPass, shaderConfiguration: ShaderConfiguration, inFlightFrames: int, subpass = 0'u32, backFaceCulling=true): ShaderPipeline = assert renderPass.valid assert device.vk.valid @@ -187,7 +187,7 @@ discard result.uniforms # just for assertion -proc destroy*(pipeline: var Pipeline) = +proc destroy*(pipeline: var ShaderPipeline) = assert pipeline.device.vk.valid assert pipeline.vk.valid assert pipeline.layout.valid diff -r 0d46e6638bd6 -r 970f74d5284e src/semicongine/vulkan/renderpass.nim --- a/src/semicongine/vulkan/renderpass.nim Sun Nov 26 19:53:00 2023 +0700 +++ b/src/semicongine/vulkan/renderpass.nim Sun Nov 26 21:26:46 2023 +0700 @@ -15,7 +15,7 @@ flags: VkSubpassDescriptionFlags outputs: seq[VkAttachmentReference] depthStencil: Option[VkAttachmentReference] - pipelines*: seq[(MaterialType, Pipeline)] + shaderPipelines*: seq[(MaterialType, ShaderPipeline)] RenderPass* = object vk*: VkRenderPass device*: Device @@ -102,7 +102,7 @@ )] result = device.createRenderPass(attachments=attachments, subpasses=subpasses, dependencies=dependencies) for (materialtype, shaderconfig) in shaders: - result.subpasses[0].pipelines.add (materialtype, device.createPipeline(result.vk, shaderconfig, inFlightFrames, 0, backFaceCulling=backFaceCulling)) + result.subpasses[0].shaderPipelines.add (materialtype, device.createPipeline(result.vk, shaderconfig, inFlightFrames, 0, backFaceCulling=backFaceCulling)) proc beginRenderCommands*(commandBuffer: VkCommandBuffer, renderpass: RenderPass, framebuffer: Framebuffer) = @@ -161,6 +161,6 @@ renderPass.device.vk.vkDestroyRenderPass(renderPass.vk, nil) renderPass.vk.reset for i in 0 ..< renderPass.subpasses.len: - for _, pipeline in renderPass.subpasses[i].pipelines.mitems: + for _, pipeline in renderPass.subpasses[i].shaderPipelines.mitems: pipeline.destroy() renderPass.subpasses = @[]