changeset 1217:f819a874058f compiletime-tests

merge
author sam <sam@basx.dev>
date Wed, 17 Jul 2024 21:00:54 +0700
parents 8b5558b29387 (current diff) 04e446a7eb2b (diff)
children 56781cc0fc7c
files semiconginev2.nim
diffstat 7 files changed, 297 insertions(+), 84 deletions(-) [+]
line wrap: on
line diff
--- a/semicongine/core/utils.nim	Wed Jul 17 21:00:37 2024 +0700
+++ b/semicongine/core/utils.nim	Wed Jul 17 21:00:54 2024 +0700
@@ -30,3 +30,13 @@
 
 func Size*[T: seq](list: T): uint64 =
   uint64(list.len * sizeof(get(genericParams(typeof(list)), 0)))
+
+template TimeAndLog*(body: untyped): untyped =
+  let t0 = getMonoTime()
+  body
+  echo (getMonoTime() - t0).inNanoseconds.float / 1_000_000
+
+template TimeAndLog*(name: string, body: untyped): untyped =
+  let t0 = getMonoTime()
+  body
+  echo name, ": ", (getMonoTime() - t0).inNanoseconds.float / 1_000_000
--- a/semicongine/rendering.nim	Wed Jul 17 21:00:37 2024 +0700
+++ b/semicongine/rendering.nim	Wed Jul 17 21:00:54 2024 +0700
@@ -47,8 +47,8 @@
     samples*: VkSampleCountFlagBits
     # populated through InitSwapchain proc
     vk: VkSwapchainKHR
-    width: uint32
-    height: uint32
+    width*: uint32
+    height*: uint32
     msaaImage: VkImage
     msaaMemory: VkDeviceMemory
     msaaImageView: VkImageView
@@ -72,7 +72,7 @@
 type
   # type aliases
   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]
-  TextureType = TVec1[uint8] | TVec2[uint8] | TVec3[uint8] | TVec4[uint8]
+  TextureType = TVec1[uint8] | TVec4[uint8]
 
   # shader related types
   DescriptorSet*[T: object] = object
@@ -108,9 +108,10 @@
     height*: uint32
     interpolation*: VkFilter = VK_FILTER_LINEAR
     data*: seq[T]
-    vk: VkImage
-    imageview: VkImageView
-    sampler: VkSampler
+    vk*: VkImage
+    imageview*: VkImageView
+    sampler*: VkSampler
+    isRenderTarget*: bool = false
   GPUArray*[T: SupportedGPUType, TBuffer: static BufferType] = object
     data*: seq[T]
     buffer*: Buffer
--- a/semicongine/rendering/renderer.nim	Wed Jul 17 21:00:37 2024 +0700
+++ b/semicongine/rendering/renderer.nim	Wed Jul 17 21:00:54 2024 +0700
@@ -1,6 +1,3 @@
-func depth(texture: Texture): int =
-  default(elementType(texture.data)).len
-
 func pointerAddOffset[T: SomeInteger](p: pointer, offset: T): pointer =
   cast[pointer](cast[T](p) + offset)
 
@@ -13,17 +10,12 @@
     of UniformBuffer: @[VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT]
     of UniformBufferMapped: @[VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT]
 
-proc GetVkFormat(depth: int, usage: openArray[VkImageUsageFlagBits]): VkFormat =
-  const DEPTH_FORMAT_MAP = [
-    0: [VK_FORMAT_UNDEFINED, VK_FORMAT_UNDEFINED],
-    1: [VK_FORMAT_R8_SRGB, VK_FORMAT_R8_UNORM],
-    2: [VK_FORMAT_R8G8_SRGB, VK_FORMAT_R8G8_UNORM],
-    3: [VK_FORMAT_R8G8B8_SRGB, VK_FORMAT_R8G8B8_UNORM],
-    4: [VK_FORMAT_R8G8B8A8_SRGB, VK_FORMAT_R8G8B8A8_UNORM],
-  ]
+proc GetVkFormat(grayscale: bool, usage: openArray[VkImageUsageFlagBits]): VkFormat =
+  let formats = if grayscale: [VK_FORMAT_R8_SRGB, VK_FORMAT_R8_UNORM]
+                else: [VK_FORMAT_B8G8R8A8_SRGB, VK_FORMAT_B8G8R8A8_UNORM]
 
   var formatProperties = VkImageFormatProperties2(sType: VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2)
-  for format in DEPTH_FORMAT_MAP[depth]:
+  for format in formats:
     var formatInfo = VkPhysicalDeviceImageFormatInfo2(
       sType: VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2,
       format: format,
@@ -446,9 +438,11 @@
   checkVkResult vkCreateSampler(vulkan.device, addr(samplerInfo), nil, addr(result))
 
 proc createTextureImage(renderData: var RenderData, texture: var Texture) =
-  assert texture.vk == VkImage(0)
-  const usage = [VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_USAGE_SAMPLED_BIT]
-  let format = GetVkFormat(texture.depth, usage = usage)
+  assert texture.vk == VkImage(0), "Texture has already been created"
+  var usage = @[VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_USAGE_SAMPLED_BIT]
+  if texture.isRenderTarget:
+    usage.add VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
+  let format = GetVkFormat(elementType(texture.data) is TVec1[uint8], usage = usage)
 
   texture.vk = svkCreate2DImage(texture.width, texture.height, format, usage)
   renderData.images.add texture.vk
--- a/semicongine/rendering/renderpasses.nim	Wed Jul 17 21:00:37 2024 +0700
+++ b/semicongine/rendering/renderpasses.nim	Wed Jul 17 21:00:54 2024 +0700
@@ -1,4 +1,4 @@
-proc CreatePresentationRenderPass*(samples = VK_SAMPLE_COUNT_1_BIT): VkRenderPass =
+proc CreateDirectPresentationRenderPass*(samples = VK_SAMPLE_COUNT_1_BIT): VkRenderPass =
   assert vulkan.instance.Valid, "Vulkan not initialized"
 
   let format = DefaultSurfaceFormat()
@@ -24,14 +24,16 @@
       finalLayout: VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
     )
   var
-    dependencies = @[VkSubpassDependency(
-      srcSubpass: VK_SUBPASS_EXTERNAL,
-      dstSubpass: 0,
-      srcStageMask: toBits [VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT],
-      srcAccessMask: toBits [VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT],
-      dstStageMask: toBits [VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT],
-      dstAccessMask: toBits [VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT],
-    )]
+    dependencies = @[
+      VkSubpassDependency(
+        srcSubpass: VK_SUBPASS_EXTERNAL,
+        dstSubpass: 0,
+        srcStageMask: toBits [VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT],
+        dstStageMask: toBits [VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT],
+        srcAccessMask: toBits [VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT],
+        dstAccessMask: toBits [VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT],
+      )
+    ]
     colorAttachment = VkAttachmentReference(
       attachment: 0,
       layout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
@@ -41,28 +43,88 @@
       layout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
     )
 
-  var subpass = VkSubpassDescription(
-    flags: VkSubpassDescriptionFlags(0),
-    pipelineBindPoint: VK_PIPELINE_BIND_POINT_GRAPHICS,
-    inputAttachmentCount: 0,
-    pInputAttachments: nil,
-    colorAttachmentCount: 1,
-    pColorAttachments: addr(colorAttachment),
-    pResolveAttachments: if samples == VK_SAMPLE_COUNT_1_BIT: nil else: addr(resolveAttachment),
-    pDepthStencilAttachment: nil,
-    preserveAttachmentCount: 0,
-    pPreserveAttachments: nil,
+  if samples == VK_SAMPLE_COUNT_1_BIT:
+    return svkCreateRenderPass(attachments, [colorAttachment], [], dependencies)
+  else:
+    return svkCreateRenderPass(attachments, [colorAttachment], [resolveAttachment], dependencies)
+
+proc CreateIndirectPresentationRenderPass*(): (VkRenderPass, VkRenderPass) =
+  assert vulkan.instance.Valid, "Vulkan not initialized"
+
+  # first renderpass, drawing
+  let format = DefaultSurfaceFormat()
+  var
+    attachments = @[VkAttachmentDescription(
+      format: format,
+      samples: VK_SAMPLE_COUNT_1_BIT,
+      loadOp: VK_ATTACHMENT_LOAD_OP_CLEAR,
+      storeOp: VK_ATTACHMENT_STORE_OP_STORE,
+      stencilLoadOp: VK_ATTACHMENT_LOAD_OP_DONT_CARE,
+      stencilStoreOp: VK_ATTACHMENT_STORE_OP_DONT_CARE,
+      initialLayout: VK_IMAGE_LAYOUT_UNDEFINED,
+      finalLayout: VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+    )]
+
+    dependencies = @[
+      VkSubpassDependency(
+        srcSubpass: VK_SUBPASS_EXTERNAL,
+        dstSubpass: 0,
+        srcStageMask: toBits [VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT],
+        dstStageMask: toBits [VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT],
+        srcAccessMask: VkAccessFlags(0),
+        dstAccessMask: toBits [VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT],
+      ),
+      VkSubpassDependency(
+        srcSubpass: VK_SUBPASS_EXTERNAL,
+        dstSubpass: 0,
+        srcStageMask: toBits [VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT],
+        dstStageMask: toBits [VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT],
+        srcAccessMask: toBits [VK_ACCESS_SHADER_READ_BIT],
+        dstAccessMask: toBits [VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT],
+      ),
+      VkSubpassDependency(
+        srcSubpass: 0,
+        dstSubpass: VK_SUBPASS_EXTERNAL,
+        srcStageMask: toBits [VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT],
+        dstStageMask: toBits [VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT],
+        srcAccessMask: toBits [VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT],
+        dstAccessMask: toBits [VK_ACCESS_SHADER_READ_BIT],
+      ),
+    ]
+    colorAttachment = VkAttachmentReference(
+      attachment: 0,
+      layout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+    )
+
+  # second renderpass, presentation
+  var
+    presentAttachments = @[VkAttachmentDescription(
+      format: format,
+      samples: VK_SAMPLE_COUNT_1_BIT,
+      loadOp: VK_ATTACHMENT_LOAD_OP_CLEAR,
+      storeOp: VK_ATTACHMENT_STORE_OP_STORE,
+      stencilLoadOp: VK_ATTACHMENT_LOAD_OP_DONT_CARE,
+      stencilStoreOp: VK_ATTACHMENT_STORE_OP_DONT_CARE,
+      initialLayout: VK_IMAGE_LAYOUT_UNDEFINED,
+      finalLayout: VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+    )]
+    presentDependencies = @[VkSubpassDependency(
+      srcSubpass: VK_SUBPASS_EXTERNAL,
+      dstSubpass: 0,
+      srcStageMask: toBits [VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT],
+      dstStageMask: toBits [VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT],
+      srcAccessMask: VkAccessFlags(0),
+      dstAccessMask: toBits [VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT],
+    )]
+    presentColorAttachment = VkAttachmentReference(
+      attachment: 0,
+      layout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+    )
+
+  result = (
+    svkCreateRenderPass(attachments, [colorAttachment], [], dependencies),
+    svkCreateRenderPass(presentAttachments, [presentColorAttachment], [], presentDependencies)
   )
-  var createInfo = VkRenderPassCreateInfo(
-      sType: VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
-      attachmentCount: uint32(attachments.len),
-      pAttachments: attachments.ToCPointer,
-      subpassCount: 1,
-      pSubpasses: addr(subpass),
-      dependencyCount: uint32(dependencies.len),
-      pDependencies: dependencies.ToCPointer,
-    )
-  checkVkResult vulkan.device.vkCreateRenderPass(addr(createInfo), nil, addr(result))
 
 template WithRenderPass*(
   theRenderpass: VkRenderPass,
--- a/semicongine/rendering/vulkan_wrappers.nim	Wed Jul 17 21:00:37 2024 +0700
+++ b/semicongine/rendering/vulkan_wrappers.nim	Wed Jul 17 21:00:54 2024 +0700
@@ -225,6 +225,37 @@
     pDynamicOffsets = nil
   )
 
+
+proc svkCreateRenderPass(
+  attachments: openArray[VkAttachmentDescription],
+  colorAttachments: openArray[VkAttachmentReference],
+  resolveAttachments: openArray[VkAttachmentReference],
+  dependencies: openArray[VkSubpassDependency],
+): VkRenderPass =
+  assert colorAttachments.len == resolveAttachments.len or resolveAttachments.len == 0
+  var subpass = VkSubpassDescription(
+    flags: VkSubpassDescriptionFlags(0),
+    pipelineBindPoint: VK_PIPELINE_BIND_POINT_GRAPHICS,
+    inputAttachmentCount: 0,
+    pInputAttachments: nil,
+    colorAttachmentCount: colorAttachments.len.uint32,
+    pColorAttachments: colorAttachments.ToCPointer,
+    pResolveAttachments: resolveAttachments.ToCPointer,
+    pDepthStencilAttachment: nil,
+    preserveAttachmentCount: 0,
+    pPreserveAttachments: nil,
+  )
+  var createInfo = VkRenderPassCreateInfo(
+      sType: VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
+      attachmentCount: uint32(attachments.len),
+      pAttachments: attachments.ToCPointer,
+      subpassCount: 1,
+      pSubpasses: addr(subpass),
+      dependencyCount: uint32(dependencies.len),
+      pDependencies: dependencies.ToCPointer,
+    )
+  checkVkResult vkCreateRenderPass(vulkan.device, addr(createInfo), nil, addr(result))
+
 proc BestMemory*(mappable: bool, filter: seq[uint32] = @[]): uint32 =
   var physicalProperties: VkPhysicalDeviceMemoryProperties
   vkGetPhysicalDeviceMemoryProperties(vulkan.physicalDevice, addr(physicalProperties))
--- a/semiconginev2.nim	Wed Jul 17 21:00:37 2024 +0700
+++ b/semiconginev2.nim	Wed Jul 17 21:00:54 2024 +0700
@@ -6,6 +6,7 @@
 import std/marshal
 import std/math
 import std/macros
+import std/monotimes
 import std/os
 import std/options
 import std/paths
@@ -14,6 +15,7 @@
 import std/strformat
 import std/strutils
 import std/tables
+import std/times
 import std/typetraits
 
 include ./semicongine/rendering/vulkan/api
--- a/tests/test_rendering.nim	Wed Jul 17 21:00:37 2024 +0700
+++ b/tests/test_rendering.nim	Wed Jul 17 21:00:54 2024 +0700
@@ -7,7 +7,7 @@
   mainRenderpass: VkRenderPass
   swapchain: Swapchain
 
-proc test_01_triangle(nFrames: int) =
+proc test_01_triangle(nFrames: int, renderPass: VkRenderPass, samples = VK_SAMPLE_COUNT_1_BIT) =
   var renderdata = InitRenderData()
 
   type
@@ -33,12 +33,12 @@
   renderdata.FlushAllMemory()
 
   var
-    pipeline = CreatePipeline[TrianglShader](renderPass = mainRenderpass, samples = swapchain.samples)
+    pipeline = CreatePipeline[TrianglShader](renderPass = renderPass, samples = samples)
 
   var c = 0
   while UpdateInputs() and c < nFrames:
     WithNextFrame(swapchain, framebuffer, commandbuffer):
-      WithRenderPass(mainRenderpass, framebuffer, commandbuffer, swapchain.width, swapchain.height, NewVec4f(0, 0, 0, 0)):
+      WithRenderPass(renderPass, framebuffer, commandbuffer, swapchain.width, swapchain.height, NewVec4f(0, 0, 0, 0)):
         WithPipeline(commandbuffer, pipeline):
           Render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = mesh)
     inc c
@@ -49,7 +49,7 @@
   DestroyRenderData(renderdata)
 
 
-proc test_02_triangle_quad_instanced(nFrames: int) =
+proc test_02_triangle_quad_instanced(nFrames: int, renderPass: VkRenderPass, samples = VK_SAMPLE_COUNT_1_BIT) =
   var renderdata = InitRenderData()
 
   type
@@ -101,12 +101,12 @@
   AssignBuffers(renderdata, instancesB)
   renderdata.FlushAllMemory()
 
-  var pipeline = CreatePipeline[SomeShader](renderPass = mainRenderpass, samples = swapchain.samples)
+  var pipeline = CreatePipeline[SomeShader](renderPass = renderPass, samples = samples)
 
   var c = 0
   while UpdateInputs() and c < nFrames:
     WithNextFrame(swapchain, framebuffer, commandbuffer):
-      WithRenderPass(mainRenderpass, framebuffer, commandbuffer, swapchain.width, swapchain.height, NewVec4f(0, 0, 0, 0)):
+      WithRenderPass(renderPass, framebuffer, commandbuffer, swapchain.width, swapchain.height, NewVec4f(0, 0, 0, 0)):
         WithPipeline(commandbuffer, pipeline):
           Render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = quad, instances = instancesA)
           Render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = quad, instances = instancesB)
@@ -119,7 +119,7 @@
   DestroyPipeline(pipeline)
   DestroyRenderData(renderdata)
 
-proc test_03_simple_descriptorset(nFrames: int) =
+proc test_03_simple_descriptorset(nFrames: int, renderPass: VkRenderPass, samples = VK_SAMPLE_COUNT_1_BIT) =
   var renderdata = InitRenderData()
 
   type
@@ -128,7 +128,7 @@
 
     Uniforms = object
       material: GPUValue[Material, UniformBuffer]
-      texture1: Texture[TVec3[uint8]]
+      texture1: Texture[TVec4[uint8]]
 
     QuadShader = object
       position {.VertexAttribute.}: Vec3f
@@ -149,10 +149,10 @@
       position: GPUArray[Vec3f, VertexBuffer]
       indices: GPUArray[uint16, IndexBuffer]
 
-  let R = TVec3[uint8]([255'u8, 0'u8, 0'u8])
-  let G = TVec3[uint8]([0'u8, 255'u8, 0'u8])
-  let B = TVec3[uint8]([0'u8, 0'u8, 255'u8])
-  let W = TVec3[uint8]([255'u8, 255'u8, 255'u8])
+  let R = TVec4[uint8]([255'u8, 0'u8, 0'u8, 255'u8])
+  let G = TVec4[uint8]([0'u8, 255'u8, 0'u8, 255'u8])
+  let B = TVec4[uint8]([0'u8, 0'u8, 255'u8, 255'u8])
+  let W = TVec4[uint8]([255'u8, 255'u8, 255'u8, 255'u8])
   var
     quad = QuadMesh(
       position: asGPUArray([NewVec3f(-0.5, -0.5), NewVec3f(-0.5, 0.5), NewVec3f(0.5, 0.5), NewVec3f(0.5, -0.5)], VertexBuffer),
@@ -161,14 +161,14 @@
     uniforms1 = asDescriptorSet(
       Uniforms(
         material: asGPUValue(Material(baseColor: NewVec3f(1, 1, 1)), UniformBuffer),
-        texture1: Texture[TVec3[uint8]](width: 3, height: 3, data: @[R, G, B, G, B, R, B, R, G], interpolation: VK_FILTER_NEAREST),
+        texture1: Texture[TVec4[uint8]](width: 3, height: 3, data: @[R, G, B, G, B, R, B, R, G], interpolation: VK_FILTER_NEAREST),
       )
     )
     uniforms2 = asDescriptorSet(
       Uniforms(
         material: asGPUValue(Material(baseColor: NewVec3f(0.5, 0.5, 0.5)), UniformBuffer),
-        texture1: Texture[TVec3[uint8]](width: 2, height: 2, data: @[R, G, B, W]),
-      )
+        texture1: Texture[TVec4[uint8]](width: 2, height: 2, data: @[R, G, B, W]),
+    )
     )
 
   AssignBuffers(renderdata, quad)
@@ -178,7 +178,7 @@
   UploadTextures(renderdata, uniforms2)
   renderdata.FlushAllMemory()
 
-  var pipeline = CreatePipeline[QuadShader](renderPass = mainRenderpass, samples = swapchain.samples)
+  var pipeline = CreatePipeline[QuadShader](renderPass = renderPass, samples = samples)
 
   InitDescriptorSet(renderdata, pipeline.descriptorSetLayouts[0], uniforms1)
   InitDescriptorSet(renderdata, pipeline.descriptorSetLayouts[0], uniforms2)
@@ -186,7 +186,7 @@
   var c = 0
   while UpdateInputs() and c < nFrames:
     WithNextFrame(swapchain, framebuffer, commandbuffer):
-      WithRenderPass(mainRenderpass, framebuffer, commandbuffer, swapchain.width, swapchain.height, NewVec4f(0, 0, 0, 0)):
+      WithRenderPass(renderPass, framebuffer, commandbuffer, swapchain.width, swapchain.height, NewVec4f(0, 0, 0, 0)):
         WithPipeline(commandbuffer, pipeline):
           WithBind(commandbuffer, (uniforms1, ), pipeline, swapchain.currentFiF):
             Render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = quad)
@@ -199,7 +199,7 @@
   DestroyPipeline(pipeline)
   DestroyRenderData(renderdata)
 
-proc test_04_multiple_descriptorsets(nFrames: int) =
+proc test_04_multiple_descriptorsets(nFrames: int, renderPass: VkRenderPass, samples = VK_SAMPLE_COUNT_1_BIT) =
   var renderdata = InitRenderData()
 
   type
@@ -286,7 +286,7 @@
   UploadTextures(renderdata, mainset)
   renderdata.FlushAllMemory()
 
-  var pipeline = CreatePipeline[QuadShader](renderPass = mainRenderpass, samples = swapchain.samples)
+  var pipeline = CreatePipeline[QuadShader](renderPass = renderPass, samples = samples)
 
   InitDescriptorSet(renderdata, pipeline.descriptorSetLayouts[0], constset)
   InitDescriptorSet(renderdata, pipeline.descriptorSetLayouts[1], mainset)
@@ -295,13 +295,14 @@
 
   var c = 0
   while UpdateInputs() and c < nFrames:
-    WithNextFrame(swapchain, framebuffer, commandbuffer):
-      WithRenderPass(mainRenderpass, framebuffer, commandbuffer, swapchain.width, swapchain.height, NewVec4f(0, 0, 0, 0)):
-        WithPipeline(commandbuffer, pipeline):
-          WithBind(commandbuffer, (constset, mainset, otherset1), pipeline, swapchain.currentFiF):
-            Render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = quad)
-          WithBind(commandbuffer, (constset, mainset, otherset2), pipeline, swapchain.currentFiF):
-            Render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = quad)
+    TimeAndLog:
+      WithNextFrame(swapchain, framebuffer, commandbuffer):
+        WithRenderPass(renderPass, framebuffer, commandbuffer, swapchain.width, swapchain.height, NewVec4f(0, 0, 0, 0)):
+          WithPipeline(commandbuffer, pipeline):
+            WithBind(commandbuffer, (constset, mainset, otherset1), pipeline, swapchain.currentFiF):
+              Render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = quad)
+            WithBind(commandbuffer, (constset, mainset, otherset2), pipeline, swapchain.currentFiF):
+              Render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = quad)
     mainset.data.renderSettings.data.brigthness = (c.float32 / nFrames.float32)
     otherset1.data.objectSettings.data.scale = 0.5 + (c.float32 / nFrames.float32)
     UpdateGPUBuffer(mainset.data.renderSettings)
@@ -314,26 +315,134 @@
   DestroyPipeline(pipeline)
   DestroyRenderData(renderdata)
 
+proc test_05_triangle_2pass(nFrames: int, samples = VK_SAMPLE_COUNT_1_BIT) =
+  var
+    (offscreenRP, presentRP) = CreateIndirectPresentationRenderPass()
+    swapchain = InitSwapchain(renderpass = presentRP).get()
+
+  var renderdata = InitRenderData()
+
+  type
+    Uniforms = object
+      frameTexture: Texture[TVec4[uint8]]
+    TriangleShader = object
+      position {.VertexAttribute.}: Vec3f
+      color {.VertexAttribute.}: Vec3f
+      fragmentColor {.Pass.}: Vec3f
+      outColor {.ShaderOutput.}: Vec4f
+      # code
+      vertexCode: string = """void main() {
+      fragmentColor = color;
+      gl_Position = vec4(position, 1);}"""
+      fragmentCode: string = """void main() {
+      outColor = vec4(fragmentColor, 1);}"""
+    PresentShader = object
+      position {.VertexAttribute.}: Vec2f
+      uv {.Pass.}: Vec2f
+      outColor {.ShaderOutput.}: Vec4f
+      descriptorSets {.DescriptorSets.}: (Uniforms, )
+      # code
+      vertexCode: string = """void main() {
+      uv = ((position + 1) * 0.5) * vec2(1, -1);
+      gl_Position = vec4(position, 0, 1);}"""
+      fragmentCode: string = """void main() {
+      vec2 uv1 = uv + vec2(0.001, 0.001);
+      vec2 uv2 = uv + vec2(0.001, -0.001);
+      vec2 uv3 = uv + vec2(-0.001, 0.001);
+      vec2 uv4 = uv + vec2(-0.001, -0.001);
+      outColor = (
+        texture(frameTexture, uv1) +
+        texture(frameTexture, uv2) +
+        texture(frameTexture, uv3) +
+        texture(frameTexture, uv4)
+      ) / 4;
+      }"""
+    TriangleMesh = object
+      position: GPUArray[Vec3f, VertexBuffer]
+      color: GPUArray[Vec3f, VertexBuffer]
+    QuadMesh = object
+      position: GPUArray[Vec2f, VertexBuffer]
+      indices: GPUArray[uint16, IndexBuffer]
+  var mesh = TriangleMesh(
+    position: asGPUArray([NewVec3f(-0.5, -0.5), NewVec3f(0, 0.5), NewVec3f(0.5, -0.5)], VertexBuffer),
+    color: asGPUArray([NewVec3f(0, 0, 1), NewVec3f(0, 1, 0), NewVec3f(1, 0, 0)], VertexBuffer),
+  )
+  var quad = QuadMesh(
+    position: asGPUArray([NewVec2f(-1, -1), NewVec2f(-1, 1), NewVec2f(1, 1), NewVec2f(1, -1)], VertexBuffer),
+    indices: asGPUArray([0'u16, 1'u16, 2'u16, 2'u16, 3'u16, 0'u16], IndexBuffer),
+  )
+  var uniforms1 = asDescriptorSet(
+    Uniforms(
+      frameTexture: Texture[TVec4[uint8]](width: swapchain.width, height: swapchain.height, isRenderTarget: true),
+    )
+  )
+  var uniforms2 = asDescriptorSet(
+    Uniforms(
+      frameTexture: Texture[TVec4[uint8]](width: swapchain.width, height: swapchain.height, isRenderTarget: true),
+    )
+  )
+  AssignBuffers(renderdata, mesh)
+  AssignBuffers(renderdata, quad)
+  UploadTextures(renderdata, uniforms1)
+  UploadTextures(renderdata, uniforms2)
+  renderdata.FlushAllMemory()
+
+  var
+    drawPipeline = CreatePipeline[TriangleShader](renderPass = offscreenRP, samples = samples)
+    presentPipeline = CreatePipeline[PresentShader](renderPass = presentRP, samples = samples)
+
+  InitDescriptorSet(renderdata, presentPipeline.descriptorSetLayouts[0], uniforms1)
+  InitDescriptorSet(renderdata, presentPipeline.descriptorSetLayouts[0], uniforms2)
+
+  var offscreenFB = svkCreateFramebuffer(offscreenRP, swapchain.width, swapchain.height, [uniforms1.data.frameTexture.imageview])
+
+  var c = 0
+  while UpdateInputs() and c < nFrames:
+
+    TimeAndLog:
+      WithNextFrame(swapchain, framebuffer, commandbuffer):
+
+        WithRenderPass(offscreenRP, offscreenFB, commandbuffer, swapchain.width, swapchain.height, NewVec4f(0, 0, 0, 0)):
+          WithPipeline(commandbuffer, drawPipeline):
+            Render(commandbuffer = commandbuffer, pipeline = drawPipeline, mesh = mesh)
+
+        WithRenderPass(presentRP, framebuffer, commandbuffer, swapchain.width, swapchain.height, NewVec4f(0, 0, 0, 0)):
+          WithPipeline(commandbuffer, presentPipeline):
+            WithBind(commandbuffer, (uniforms1, ), presentPipeline, swapchain.currentFiF):
+              Render(commandbuffer = commandbuffer, pipeline = presentPipeline, mesh = quad)
+    inc c
+
+  # cleanup
+  checkVkResult vkDeviceWaitIdle(vulkan.device)
+  DestroyPipeline(presentPipeline)
+  DestroyPipeline(drawPipeline)
+  DestroyRenderData(renderdata)
+  vkDestroyRenderPass(vulkan.device, offscreenRP, nil)
+  vkDestroyRenderPass(vulkan.device, presentRP, nil)
+  vkDestroyFramebuffer(vulkan.device, offscreenFB, nil)
+  DestroySwapchain(swapchain)
+
 when isMainModule:
   var nFrames = 2000
   InitVulkan()
 
+
   # test normal
   block:
-    mainRenderpass = CreatePresentationRenderPass()
+    mainRenderpass = CreateDirectPresentationRenderPass()
     swapchain = InitSwapchain(renderpass = mainRenderpass).get()
 
     # tests a simple triangle with minimalistic shader and vertex format
-    test_01_triangle(nFrames)
+    test_01_triangle(nFrames, renderPass = mainRenderpass)
 
     # tests instanced triangles and quads, mixing meshes and instances
-    test_02_triangle_quad_instanced(nFrames)
+    test_02_triangle_quad_instanced(nFrames, renderPass = mainRenderpass)
 
     # teste descriptor sets
-    test_03_simple_descriptorset(nFrames)
+    test_03_simple_descriptorset(nFrames, renderPass = mainRenderpass)
 
     # tests multiple descriptor sets and arrays
-    test_04_multiple_descriptorsets(nFrames)
+    test_04_multiple_descriptorsets(nFrames, renderPass = mainRenderpass)
 
     checkVkResult vkDeviceWaitIdle(vulkan.device)
     vkDestroyRenderPass(vulkan.device, mainRenderpass, nil)
@@ -341,13 +450,17 @@
 
   # test MSAA
   block:
-    mainRenderpass = CreatePresentationRenderPass(samples = VK_SAMPLE_COUNT_4_BIT)
+    mainRenderpass = CreateDirectPresentationRenderPass(samples = VK_SAMPLE_COUNT_4_BIT)
     swapchain = InitSwapchain(renderpass = mainRenderpass, samples = VK_SAMPLE_COUNT_4_BIT).get()
 
-    test_01_triangle(99999999)
+    test_01_triangle(nFrames, renderPass = mainRenderpass, VK_SAMPLE_COUNT_4_BIT)
 
     checkVkResult vkDeviceWaitIdle(vulkan.device)
     vkDestroyRenderPass(vulkan.device, mainRenderpass, nil)
     DestroySwapchain(swapchain)
 
+  # test multiple render passes
+  block:
+    test_05_triangle_2pass(999999999)
+
   DestroyVulkan()