changeset 131:11666d28e04d

add: recreation of swapchain (at least on linux, windows will likely fail, needs testing
author Sam <sam@basx.dev>
date Wed, 19 Apr 2023 01:45:16 +0700
parents ff345f9e4eb7
children 25fe4972ef9c
files src/semicongine/engine.nim src/semicongine/renderer.nim src/semicongine/vulkan/swapchain.nim tests/test_vulkan_wrapper.nim
diffstat 4 files changed, 95 insertions(+), 55 deletions(-) [+]
line wrap: on
line diff
--- a/src/semicongine/engine.nim	Tue Apr 18 03:06:14 2023 +0700
+++ b/src/semicongine/engine.nim	Wed Apr 19 01:45:16 2023 +0700
@@ -81,13 +81,13 @@
     selectedPhysicalDevice.filterForGraphicsPresentationQueues()
   )
 
-proc setRenderer*(engine: var Engine, renderPasses: openArray[RenderPass]) =
-  engine.renderer = engine.device.initRenderer(renderPasses)
+proc setRenderer*(engine: var Engine, renderPass: RenderPass) =
+  engine.renderer = engine.device.initRenderer(renderPass)
 
 proc addScene*(engine: var Engine, entity: Entity, vertexInput: seq[ShaderAttribute]) =
   engine.renderer.setupDrawableBuffers(entity, vertexInput)
 
-proc renderScene*(engine: var Engine, entity: Entity): auto =
+proc renderScene*(engine: var Engine, entity: Entity) =
   assert engine.running
   engine.renderer.render(entity)
 
--- a/src/semicongine/renderer.nim	Tue Apr 18 03:06:14 2023 +0700
+++ b/src/semicongine/renderer.nim	Wed Apr 19 01:45:16 2023 +0700
@@ -25,24 +25,23 @@
   Renderer* = object
     device: Device
     surfaceFormat: VkSurfaceFormatKHR
-    renderPasses: seq[RenderPass]
+    renderPass: RenderPass
     swapchain: Swapchain
     scenedata: Table[Entity, SceneData]
 
 
-proc initRenderer*(device: Device, renderPasses: openArray[RenderPass]): Renderer =
+proc initRenderer*(device: Device, renderPass: RenderPass): Renderer =
   assert device.vk.valid
-  assert renderPasses.len > 0
-  for renderPass in renderPasses:
-    assert renderPass.vk.valid
+  assert renderPass.vk.valid
 
   result.device = device
-  result.renderPasses = renderPasses.toSeq
+  result.renderPass = renderPass
   result.surfaceFormat = device.physicalDevice.getSurfaceFormats().filterSurfaceFormat()
-  let (swapchain, res) = device.createSwapchain(result.renderPasses[^1], result.surfaceFormat, device.firstGraphicsQueue().get().family, 2)
-  if res != VK_SUCCESS:
+  # use last renderpass as output for swapchain
+  let swapchain = device.createSwapchain(result.renderPass.vk, result.surfaceFormat, device.firstGraphicsQueue().get().family)
+  if not swapchain.isSome:
     raise newException(Exception, "Unable to create swapchain")
-  result.swapchain = swapchain
+  result.swapchain = swapchain.get()
 
 proc setupDrawableBuffers*(renderer: var Renderer, tree: Entity, inputs: seq[ShaderAttribute]) =
   assert not (tree in renderer.scenedata)
@@ -128,14 +127,25 @@
 
   renderer.scenedata[tree] = data
 
-proc render*(renderer: var Renderer, entity: Entity): bool =
-  # TODO: check if nextFrame had any problems
-  var commandBuffer = renderer.swapchain.nextFrame()
+proc render*(renderer: var Renderer, entity: Entity) =
+  var
+    commandBufferResult = renderer.swapchain.nextFrame()
+    commandBuffer: VkCommandBuffer
 
-  commandBuffer.beginRenderCommands(renderer.swapchain.renderPass, renderer.swapchain.currentFramebuffer())
+  if not commandBufferResult.isSome:
+    let res = renderer.swapchain.recreate()
+    if res.isSome:
+      renderer.swapchain = res.get()
+      commandBufferResult = renderer.swapchain.nextFrame()
+      assert commandBufferResult.isSome
+    else:
+      raise newException(Exception, "Unable to recreate swapchain")
+  commandBuffer = commandBufferResult.get()
 
-  for i in 0 ..< renderer.swapchain.renderPass.subpasses.len:
-    let subpass = renderer.swapchain.renderPass.subpasses[i]
+  commandBuffer.beginRenderCommands(renderer.renderPass, renderer.swapchain.currentFramebuffer())
+
+  for i in 0 ..< renderer.renderPass.subpasses.len:
+    let subpass = renderer.renderPass.subpasses[i]
     for pipeline in subpass.pipelines:
       var mpipeline = pipeline
       commandBuffer.vkCmdBindPipeline(subpass.pipelineBindPoint, mpipeline.vk)
@@ -144,18 +154,24 @@
 
       debug "Scene buffers:"
       for (location, buffer) in renderer.scenedata[entity].vertexBuffers.pairs:
-        echo "  ", location, ": ", buffer
-      echo "  Index buffer: ", renderer.scenedata[entity].indexBuffer
+        debug "  ", location, ": ", buffer
+      debug "  Index buffer: ", renderer.scenedata[entity].indexBuffer
 
       for drawable in renderer.scenedata[entity].drawables:
         commandBuffer.draw(drawable, vertexBuffers=renderer.scenedata[entity].vertexBuffers, indexBuffer=renderer.scenedata[entity].indexBuffer)
 
-    if i < renderer.swapchain.renderPass.subpasses.len - 1:
+    if i < renderer.renderPass.subpasses.len - 1:
       commandBuffer.vkCmdNextSubpass(VK_SUBPASS_CONTENTS_INLINE)
 
   commandBuffer.endRenderCommands()
 
-  return renderer.swapchain.swap()
+  if not renderer.swapchain.swap():
+    let res = renderer.swapchain.recreate()
+    if res.isSome:
+      renderer.swapchain = res.get()
+    else:
+      raise newException(Exception, "Unable to recreate swapchain")
+
 
 func framesRendered*(renderer: Renderer): uint64 =
   renderer.swapchain.framesRendered
@@ -166,6 +182,5 @@
       buffer.destroy()
     if data.indexBuffer.vk.valid:
       data.indexBuffer.destroy()
-  for renderpass in renderer.renderPasses.mitems:
-    renderpass.destroy()
+  renderer.renderPass.destroy()
   renderer.swapchain.destroy()
--- a/src/semicongine/vulkan/swapchain.nim	Tue Apr 18 03:06:14 2023 +0700
+++ b/src/semicongine/vulkan/swapchain.nim	Wed Apr 19 01:45:16 2023 +0700
@@ -1,4 +1,5 @@
 import std/options
+import std/strformat
 import std/logging
 
 import ./api
@@ -17,9 +18,7 @@
   Swapchain* = object
     device*: Device
     vk*: VkSwapchainKHR
-    format*: VkFormat
     dimension*: TVec2[uint32]
-    renderPass*: RenderPass
     nImages*: uint32
     imageviews*: seq[ImageView]
     framebuffers*: seq[Framebuffer]
@@ -30,28 +29,36 @@
     imageAvailableSemaphore*: seq[Semaphore]
     renderFinishedSemaphore*: seq[Semaphore]
     commandBufferPool: CommandBufferPool
+    # required for recreation:
+    renderPass: VkRenderPass
+    surfaceFormat: VkSurfaceFormatKHR
+    queueFamily: QueueFamily
+    imageCount: uint32
+    presentMode: VkPresentModeKHR
     inFlightFrames: int
 
 
 proc createSwapchain*(
   device: Device,
-  renderPass: RenderPass,
+  renderPass: VkRenderPass,
   surfaceFormat: VkSurfaceFormatKHR,
   queueFamily: QueueFamily,
   desiredNumberOfImages=3'u32,
-  presentationMode: VkPresentModeKHR=VK_PRESENT_MODE_MAILBOX_KHR,
+  presentMode: VkPresentModeKHR=VK_PRESENT_MODE_MAILBOX_KHR,
   inFlightFrames=2
-): (Swapchain, VkResult) =
+): Option[Swapchain] =
   assert device.vk.valid
   assert device.physicalDevice.vk.valid
-  assert renderPass.vk.valid
+  assert renderPass.valid
   assert inFlightFrames > 0
 
   var capabilities = device.physicalDevice.getSurfaceCapabilities()
+  if capabilities.currentExtent.width == 0 or capabilities.currentExtent.height == 0:
+    return none(Swapchain)
 
   var imageCount = desiredNumberOfImages
   # following is according to vulkan specs
-  if presentationMode in [VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR, VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR]:
+  if presentMode in [VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR, VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR]:
     imageCount = 1
   else:
     imageCount = max(imageCount, capabilities.minImageCount)
@@ -71,20 +78,20 @@
     imageSharingMode: VK_SHARING_MODE_EXCLUSIVE,
     preTransform: capabilities.currentTransform,
     compositeAlpha: VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, # only used for blending with other windows, can be opaque
-    presentMode: presentationMode,
+    presentMode: presentMode,
     clipped: true,
   )
   var
     swapchain = Swapchain(
       device: device,
-      format: surfaceFormat.format,
+      surfaceFormat: surfaceFormat,
       dimension: TVec2[uint32]([capabilities.currentExtent.width, capabilities.currentExtent.height]),
-      renderPass: renderPass,
-      inFlightFrames: inFlightFrames
+      inFlightFrames: inFlightFrames,
+      queueFamily: queueFamily,
+      renderPass: renderPass
     )
-    createResult = device.vk.vkCreateSwapchainKHR(addr(createInfo), nil, addr(swapchain.vk))
 
-  if createResult == VK_SUCCESS:
+  if device.vk.vkCreateSwapchainKHR(addr(createInfo), nil, addr(swapchain.vk)) == VK_SUCCESS:
     var nImages: uint32
     checkVkResult device.vk.vkGetSwapchainImagesKHR(swapChain.vk, addr(nImages), nil)
     swapchain.nImages = nImages
@@ -94,23 +101,25 @@
       let image = Image(vk: vkimage, format: surfaceFormat.format, device: device)
       let imageview = image.createImageView()
       swapChain.imageviews.add imageview
-      swapChain.framebuffers.add swapchain.device.createFramebuffer(renderPass.vk, [imageview], swapchain.dimension)
+      swapChain.framebuffers.add swapchain.device.createFramebuffer(renderPass, [imageview], swapchain.dimension)
     for i in 0 ..< swapchain.inFlightFrames:
       swapchain.queueFinishedFence.add device.createFence()
       swapchain.imageAvailableSemaphore.add device.createSemaphore()
       swapchain.renderFinishedSemaphore.add device.createSemaphore()
     swapchain.commandBufferPool = device.createCommandBufferPool(queueFamily, swapchain.inFlightFrames)
-
-  return (swapchain, createResult)
+    debug &"Created swapchain with: {nImages} framebuffers, {inFlightFrames} in-flight frames, {swapchain.dimension.x}x{swapchain.dimension.y}"
+    result = some(swapchain)
+  else:
+    result = none(Swapchain)
 
 proc currentFramebuffer*(swapchain: Swapchain): Framebuffer =
+  assert swapchain.device.vk.valid
+  assert swapchain.vk.valid
   swapchain.framebuffers[swapchain.currentFramebufferIndex]
 
-proc nextFrame*(swapchain: var Swapchain): VkCommandBuffer =
+proc nextFrame*(swapchain: var Swapchain): Option[VkCommandBuffer] =
   assert swapchain.device.vk.valid
   assert swapchain.vk.valid
-  assert swapchain.device.firstGraphicsQueue().isSome
-  assert swapchain.device.firstPresentationQueue().isSome
 
   swapchain.currentInFlight = (swapchain.currentInFlight + 1) mod swapchain.inFlightFrames
   swapchain.queueFinishedFence[swapchain.currentInFlight].wait()
@@ -122,16 +131,19 @@
     VkFence(0),
     addr(swapchain.currentFramebufferIndex)
   )
-  # TODO: resize swapchain on VK_SUBOPTIMAL_KHR
-  echo "HIIIIIIIIIIII next image result", nextImageResult
-  if not (nextImageResult in [VK_SUCCESS, VK_TIMEOUT, VK_NOT_READY, VK_SUBOPTIMAL_KHR]):
-    return
 
-  swapchain.queueFinishedFence[swapchain.currentInFlight].reset()
-
-  return swapchain.commandBufferPool.buffers[swapchain.currentInFlight]
+  if nextImageResult == VK_SUCCESS:
+    swapchain.queueFinishedFence[swapchain.currentInFlight].reset()
+    result = some(swapchain.commandBufferPool.buffers[swapchain.currentInFlight])
+  else:
+    result = none(VkCommandBuffer)
 
 proc swap*(swapchain: var Swapchain): bool =
+  assert swapchain.device.vk.valid
+  assert swapchain.vk.valid
+  assert swapchain.device.firstGraphicsQueue().isSome
+  assert swapchain.device.firstPresentationQueue().isSome
+
   var
     commandBuffer = swapchain.commandBufferPool.buffers[swapchain.currentInFlight]
     waitSemaphores = [swapchain.imageAvailableSemaphore[swapchain.currentInFlight].vk]
@@ -163,9 +175,7 @@
     pResults: nil,
   )
   let presentResult = vkQueuePresentKHR(swapchain.device.firstPresentationQueue().get().vk, addr(presentInfo))
-  # TODO: resize swapchain on VK_SUBOPTIMAL_KHR
-  echo "HIIIIIIIIIIII Present Result:", presentResult
-  if not (presentResult in [VK_SUCCESS, VK_SUBOPTIMAL_KHR]):
+  if presentResult != VK_SUCCESS:
     return false
 
   inc swapchain.framesRendered
@@ -193,3 +203,18 @@
 
   swapchain.device.vk.vkDestroySwapchainKHR(swapchain.vk, nil)
   swapchain.vk.reset()
+
+proc recreate*(swapchain: var Swapchain): Option[Swapchain] =
+  assert swapchain.vk.valid
+  assert swapchain.device.vk.valid
+  checkVkResult swapchain.device.vk.vkDeviceWaitIdle()
+  result = createSwapchain(
+    device=swapchain.device,
+    renderPass=swapchain.renderPass,
+    surfaceFormat=swapchain.surfaceFormat,
+    queueFamily=swapchain.queueFamily,
+    desiredNumberOfImages=swapchain.imageCount,
+    presentMode=swapchain.presentMode,
+    inFlightFrames=swapchain.inFlightFrames,
+  )
+  swapchain.destroy()
--- a/tests/test_vulkan_wrapper.nim	Tue Apr 18 03:06:14 2023 +0700
+++ b/tests/test_vulkan_wrapper.nim	Wed Apr 19 01:45:16 2023 +0700
@@ -135,7 +135,7 @@
   var
     surfaceFormat = engine.gpuDevice.physicalDevice.getSurfaceFormats().filterSurfaceFormat()
     renderPass = engine.gpuDevice.simpleForwardRenderPass(surfaceFormat.format, vertexCode, fragmentCode, 2)
-  engine.setRenderer([renderPass])
+  engine.setRenderer(renderPass)
 
   # INIT SCENES
   var scenes = [scene_simple(), scene_different_mesh_types(), scene_primitives()]
@@ -154,7 +154,7 @@
         if not engine.running or engine.keyIsDown(Escape):
           engine.destroy()
           return
-        discard engine.renderScene(scene)
+        engine.renderScene(scene)
   echo "Rendered ", engine.framesRendered, " frames"
   echo "Processed ", engine.eventsProcessed, " events"