changeset 1195:cfba2b7e00d0 compiletime-tests

did: most of swapchain, swap still needs to be done
author sam <sam@basx.dev>
date Mon, 08 Jul 2024 23:47:33 +0700
parents 397c681f9c0c
children 82feceae80b1
files semicongine/rendering.nim semicongine/rendering/renderer.nim semicongine/rendering/swapchain.nim semicongine/rendering/vulkan_wrappers.nim
diffstat 4 files changed, 192 insertions(+), 53 deletions(-) [+]
line wrap: on
line diff
--- a/semicongine/rendering.nim	Mon Jul 08 20:13:11 2024 +0700
+++ b/semicongine/rendering.nim	Mon Jul 08 23:47:33 2024 +0700
@@ -27,16 +27,31 @@
 
 type
   VulkanGlobals* = object
+    # populated through InitVulkan proc
     instance*: VkInstance
     device*: VkDevice
     physicalDevice*: VkPhysicalDevice
     surface: VkSurfaceKHR
-    swapchain: VkSwapchainKHR
-    msaaImage: VkImage
-    msaaImageView: VkImageView
     window: NativeWindow
     graphicsQueueFamily*: uint32
     graphicsQueue*: VkQueue
+  Swapchain = object
+    # parameters to InitSwapchain, required for swapchain recreation
+    renderPass: VkRenderPass
+    vSync: bool
+    samples: VkSampleCountFlagBits
+    # populated through InitSwapchain proc
+    vk: VkSwapchainKHR
+    msaaImage: VkImage
+    msaaMemory: VkDeviceMemory
+    msaaImageView: VkImageView
+    framebuffers: seq[VkFramebuffer]
+    framebufferViews: seq[VkImageView]
+    queueFinishedFence*: array[INFLIGHTFRAMES, VkFence]
+    imageAvailableSemaphore*: array[INFLIGHTFRAMES, VkSemaphore]
+    renderFinishedSemaphore*: array[INFLIGHTFRAMES, VkSemaphore]
+    currentFiF: int[0 .. INFLIGHTFRAMES - 1]
+    # unclear as of yet
     anisotropy*: float32 = 0 # needs to be enable during device creation
 
 var vulkan*: VulkanGlobals
@@ -146,7 +161,7 @@
 include ./rendering/renderer
 
 
-proc initVulkan(appName: string = "semicongine app") =
+proc InitVulkan(appName: string = "semicongine app"): VulkanGlobals =
 
   include ./platform/vulkan_extensions # for REQUIRED_PLATFORM_EXTENSIONS
 
@@ -181,15 +196,15 @@
       enabledExtensionCount: requiredExtensions.len.uint32,
       ppEnabledExtensionNames: instanceExtensionsC
     )
-  checkVkResult vkCreateInstance(addr(createinfo), nil, addr(vulkan.instance))
-  loadVulkan(vulkan.instance)
+  checkVkResult vkCreateInstance(addr(createinfo), nil, addr(result.instance))
+  loadVulkan(result.instance)
 
   # load extensions
   #
   for extension in requiredExtensions:
-    loadExtension(vulkan.instance, $extension)
-  vulkan.window = CreateWindow(appName)
-  vulkan.surface = CreateNativeSurface(vulkan.instance, vulkan.window)
+    loadExtension(result.instance, $extension)
+  result.window = CreateWindow(appName)
+  result.surface = CreateNativeSurface(result.instance, result.window)
 
   # logical device creation
 
@@ -200,17 +215,17 @@
   # var deviceExtensions  = @["VK_KHR_swapchain", "VK_KHR_uniform_buffer_standard_layout"]
   var deviceExtensions = @["VK_KHR_swapchain"]
   for extension in deviceExtensions:
-    loadExtension(vulkan.instance, extension)
+    loadExtension(result.instance, extension)
 
   # get physical device and graphics queue family
-  vulkan.physicalDevice = GetBestPhysicalDevice(vulkan.instance)
-  vulkan.graphicsQueueFamily = GetQueueFamily(vulkan.physicalDevice, VK_QUEUE_GRAPHICS_BIT)
+  result.physicalDevice = GetBestPhysicalDevice(result.instance)
+  result.graphicsQueueFamily = GetQueueFamily(result.physicalDevice, VK_QUEUE_GRAPHICS_BIT)
 
   let
     priority = cfloat(1)
     queueInfo = VkDeviceQueueCreateInfo(
       sType: VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
-      queueFamilyIndex: vulkan.graphicsQueueFamily,
+      queueFamilyIndex: result.graphicsQueueFamily,
       queueCount: 1,
       pQueuePriorities: addr(priority),
     )
@@ -227,11 +242,11 @@
     pEnabledFeatures: nil,
   )
   checkVkResult vkCreateDevice(
-    physicalDevice = vulkan.physicalDevice,
+    physicalDevice = result.physicalDevice,
     pCreateInfo = addr createDeviceInfo,
     pAllocator = nil,
-    pDevice = addr vulkan.device
+    pDevice = addr result.device
   )
-  vulkan.graphicsQueue = svkGetDeviceQueue(vulkan.device, vulkan.graphicsQueueFamily, VK_QUEUE_GRAPHICS_BIT)
+  result.graphicsQueue = svkGetDeviceQueue(result.device, result.graphicsQueueFamily, VK_QUEUE_GRAPHICS_BIT)
 
-initVulkan()
+vulkan = InitVulkan()
--- a/semicongine/rendering/renderer.nim	Mon Jul 08 20:13:11 2024 +0700
+++ b/semicongine/rendering/renderer.nim	Mon Jul 08 23:47:33 2024 +0700
@@ -1,6 +1,3 @@
-
-# some globals that will (likely?) never change during the life time of the engine
-
 func depth(texture: Texture): int =
   default(elementType(texture.data)).len
 
--- a/semicongine/rendering/swapchain.nim	Mon Jul 08 20:13:11 2024 +0700
+++ b/semicongine/rendering/swapchain.nim	Mon Jul 08 23:47:33 2024 +0700
@@ -1,27 +1,41 @@
-const N_FRAMEBUFFERS = 3'32
+const N_FRAMEBUFFERS = 3'u32
 
-proc svkCreateSwapchainKHR(vSync: bool, oldSwapchain = VkSwapchainKHR(0)): VkSwapchainKHR =
+proc InitSwapchain*(
+  renderPass: VkRenderPass,
+  vSync: bool,
+  samples = VK_SAMPLE_COUNT_1_BIT,
+  nFramebuffers = N_FRAMEBUFFERS,
+  oldSwapchain = VkSwapchainKHR(0),
+): Swapchain =
+  assert vulkan.instance.Valid
+
+  result.renderPass = renderPass
+  result.vSync = vSync
+  result.samples = samples
 
   var capabilities: VkSurfaceCapabilitiesKHR
-  checkVkResult device.vk.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(vulkan.surface, addr(capabilities))
+  checkVkResult vkGetPhysicalDeviceSurfaceCapabilitiesKHR(vulkan.physicalDevice, vulkan.surface, addr(capabilities))
+  let
+    format = DefaultSurfaceFormat()
+    width = capabilities.currentExtent.width
+    height = capabilities.currentExtent.height
 
-  if capabilities.currentExtent.width == 0 or capabilities.currentExtent.height == 0:
+  if width == 0 or height == 0:
     return VkSwapchainKHR(0)
 
-  # following is according to vulkan specs
+  # following "count" is established according to vulkan specs
   var minFramebufferCount = N_FRAMEBUFFERS
   minFramebufferCount = max(minFramebufferCount, capabilities.minImageCount)
   if capabilities.maxImageCount != 0:
     minFramebufferCount = min(minFramebufferCount, capabilities.maxImageCount)
 
-  svkGetPhysicalDeviceSurfaceFormatsKHR()
-
+  # create swapchain
   let hasTripleBuffering = VK_PRESENT_MODE_MAILBOX_KHR in svkGetPhysicalDeviceSurfacePresentModesKHR()
   var createInfo = VkSwapchainCreateInfoKHR(
     sType: VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
-    surface: device.physicalDevice.surface,
+    surface: vulkan.surface,
     minImageCount: minFramebufferCount,
-    imageFormat: DefaultSurfaceFormat(),
+    imageFormat: format,
     imageColorSpace: VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, # only one supported without special extensions
     imageExtent: capabilities.currentExtent,
     imageArrayLayers: 1,
@@ -33,25 +47,101 @@
     clipped: true,
     oldSwapchain: oldSwapchain,
   )
-  if device.vk.vkCreateSwapchainKHR(addr(createInfo), nil, addr(result)) != VK_SUCCESS:
+  if vkCreateSwapchainKHR(vulkan.device, addr(createInfo), nil, addr(result.vk)) != VK_SUCCESS:
     return VkSwapchainKHR(0)
 
+  # create msaa image+view if desired
   if samples != VK_SAMPLE_COUNT_1_BIT:
-    vulkan.msaaImage = svkCreate2DImage(
-      width = capabilities.currentExtent.width,
-      height = capabilities.currentExtent.height,
-      format = DefaultSurfaceFormat(),
+    let imgSize = width * height * format.size
+    result.msaaImage = svkCreate2DImage(
+      width = width,
+      height = height,
+      format = format,
       usage = [VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT],
     )
-    # TODO: memory
-    vk: svkAllocateMemory(size, mType),
+    result.msaaMemory = svkAllocateMemory(imgSize, BestMemory(mappable = false))
     checkVkResult vkBindImageMemory(
       vulkan.device,
-      vulkan.msaaImage,
-      selectedBlock.vk,
+      result.msaaImage,
+      result.msaaMemory,
       0,
     )
+    result.msaaImageView = svkCreate2DImageView(result.msaaImage, format)
 
+    # create framebuffers
+    var actualNFramebuffers: uint32
+    checkVkResult vkGetSwapchainImagesKHR(vulkan.device, result.vk, addr(actualNFramebuffers), nil)
+    var framebuffers = newSeq[VkImage](actualNFramebuffers)
+    checkVkResult vkGetSwapchainImagesKHR(vulkan.device, result.vk, addr(actualNFramebuffers), framebuffers.ToCPointer)
+
+    for framebuffer in framebuffers:
+      result.framebufferViews.add svkCreate2DImageView(framebuffer, format)
+      if samples == VK_SAMPLE_COUNT_1_BIT:
+        svkCreateFramebuffer(renderPass, width, height, [result.framebufferViews[^1]])
+      else:
+        svkCreateFramebuffer(renderPass, width, height, [result.msaaImageView, result.framebufferViews[^1]])
+
+    # create sync primitives
+    for i in 0 ..< INFLIGHTFRAMES:
+      result.queueFinishedFence[i] = svkCreateFence(signaled = true)
+      result.imageAvailableSemaphore[i] = svkCreateSemaphore()
+      result.renderFinishedSemaphore[i] = svkCreateSemaphore()
+
+proc TryAcquireNextImage*(swapchain: var Swapchain): bool =
+  swapchain.queueFinishedFence[swapchain.currentFiF].Await()
+
+  let nextImageResult = vkAcquireNextImageKHR(
+    vulkan.device,
+    swapchain.vk,
+    high(uint64),
+    swapchain.imageAvailableSemaphore[swapchain.currentFiF],
+    VkFence(0),
+    addr(swapchain.currentFramebufferIndex),
+  )
+
+  swapchain.queueFinishedFence[swapchain.currentFiF].Reset()
+
+  return nextImageResult == VK_SUCCESS
 
+proc Swap*(swapchain: var Swapchain, queue: Queue, commandBuffer: VkCommandBuffer): bool =
+  var
+    waitStage = VkPipelineStageFlags(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT)
+    submitInfo = VkSubmitInfo(
+      sType: VK_STRUCTURE_TYPE_SUBMIT_INFO,
+      waitSemaphoreCount: 1,
+      pWaitSemaphores: addr(swapchain.imageAvailableSemaphore[swapchain.currentFiF]),
+      pWaitDstStageMask: addr(waitStage),
+      commandBufferCount: 1,
+      pCommandBuffers: addr(commandBuffer),
+      signalSemaphoreCount: 1,
+      pSignalSemaphores: addr(swapchain.renderFinishedSemaphore[swapchain.currentFiF]),
+    )
+  checkVkResult queue.vk.vkQueueSubmit(
+    submitCount = 1,
+    pSubmits = addr submitInfo,
+    fence = swapchain.queueFinishedFence[swapchain.currentInFlight].vk
+  )
 
-    vulkan.msaaImageView = svkCreate2DImageView(vulkan.msaaImageView, DefaultSurfaceFormat())
+  var presentInfo = VkPresentInfoKHR(
+    sType: VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
+    waitSemaphoreCount: 1,
+    pWaitSemaphores: addr swapchain.renderFinishedSemaphore[swapchain.currentInFlight].vk,
+    swapchainCount: 1,
+    pSwapchains: addr swapchain.vk,
+    pImageIndices: addr swapchain.currentFramebufferIndex,
+    pResults: nil,
+  )
+  let presentResult = vkQueuePresentKHR(swapchain.presentQueue.vk, addr presentInfo)
+  if presentResult != VK_SUCCESS:
+    return false
+
+  return true
+
+proc Recreate*(swapchain: Swapchain): Swapchain =
+  initSwapchain(
+    renderPass = swapchain.renderPass,
+    vSync = swapchain.vSync,
+    samples = swapchain.samples,
+    nFramebuffers = swapchain.framebuffers.len.uint32,
+    oldSwapchain = swapchain.vk,
+  )
--- a/semicongine/rendering/vulkan_wrappers.nim	Mon Jul 08 20:13:11 2024 +0700
+++ b/semicongine/rendering/vulkan_wrappers.nim	Mon Jul 08 23:47:33 2024 +0700
@@ -24,6 +24,12 @@
 
   assert score > 0, "Unable to find integrated or discrete GPU"
 
+proc svkGetPhysicalDeviceSurfaceSupportKHR*(queueFamily: uint32): bool =
+  assert surface.Valid
+  var presentation = VkBool32(false)
+  checkVkResult vkGetPhysicalDeviceSurfaceSupportKHR(vulkan.device, queueFamily, vulkan.surface, addr(presentation))
+  return bool(presentation)
+
 proc GetQueueFamily(pDevice: VkPhysicalDevice, qType: VkQueueFlagBits): uint32 =
   var nQueuefamilies: uint32
   vkGetPhysicalDeviceQueueFamilyProperties(pDevice, addr nQueuefamilies, nil)
@@ -31,7 +37,9 @@
   vkGetPhysicalDeviceQueueFamilyProperties(pDevice, addr nQueuefamilies, queuFamilies.ToCPointer)
   for i in 0'u32 ..< nQueuefamilies:
     if qType in toEnums(queuFamilies[i].queueFlags):
-      return i
+      # for graphics queues we always also want prsentation, they seem never to be separated in practice
+      if svkGetPhysicalDeviceSurfaceSupportKHR(i) or qType != VK_QUEUE_GRAPHICS_BIT:
+        return i
   assert false, &"Queue of type {qType} not found"
 
 proc svkGetDeviceQueue*(device: VkDevice, queueFamilyIndex: uint32, qType: VkQueueFlagBits): VkQueue =
@@ -46,17 +54,23 @@
   # EVERY windows driver and almost every linux driver should support this
   VK_FORMAT_B8G8R8A8_SRGB
 
+func size(format: VkFormat): uint64 =
+  const formatSize = [
+    VK_FORMAT_B8G8R8A8_SRGB.int: 4'u64,
+  ]
+  return formatSize[format.int]
+
 proc svkGetPhysicalDeviceSurfacePresentModesKHR*(): seq[VkPresentModeKHR] =
   var n_modes: uint32
-  checkVkResult vkGetPhysicalDeviceSurfacePresentModesKHR(vulkan.device, vulkan.surface, addr(n_modes), nil)
+  checkVkResult vkGetPhysicalDeviceSurfacePresentModesKHR(vulkan.physicalDevice, vulkan.surface, addr(n_modes), nil)
   result = newSeq[VkPresentModeKHR](n_modes)
-  checkVkResult vkGetPhysicalDeviceSurfacePresentModesKHR(vulkan.device, vulkan.surface, addr(n_modes), result.ToCPointer)
+  checkVkResult vkGetPhysicalDeviceSurfacePresentModesKHR(vulkan.physicalDevice, vulkan.surface, addr(n_modes), result.ToCPointer)
 
-proc svkGetPhysicalDeviceSurfaceFormatsKHR()
+proc svkGetPhysicalDeviceSurfaceFormatsKHR(): seq[VkSurfaceFormatKHR] =
   var n_formats: uint32
-  checkVkResult vkGetPhysicalDeviceSurfaceFormatsKHR(vulkan.device, vulkan.surface, addr(n_formats), nil)
+  checkVkResult vkGetPhysicalDeviceSurfaceFormatsKHR(vulkan.physicalDevice, vulkan.surface, addr(n_formats), nil)
   result = newSeq[VkSurfaceFormatKHR](n_formats)
-  checkVkResult vkGetPhysicalDeviceSurfaceFormatsKHR(vulkan.device, vulkan.surface, addr(n_formats), result.ToCPointer)
+  checkVkResult vkGetPhysicalDeviceSurfaceFormatsKHR(vulkan.physicalDevice, vulkan.surface, addr(n_formats), result.ToCPointer)
 
 proc hasValidationLayer*(): bool =
   var n_layers: uint32
@@ -149,6 +163,18 @@
   )
   checkVkResult vkCreateImageView(vulkan.device, addr(createInfo), nil, addr(result))
 
+proc svkCreateFramebuffer*(renderpass: VkRenderPass, width, height: uint32, attachments: openArray[VkImageView]): VkFramebuffer =
+  var framebufferInfo = VkFramebufferCreateInfo(
+    sType: VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
+    renderPass: renderpass,
+    attachmentCount: attachments.len.uint32,
+    pAttachments: attachments.ToCPointer,
+    width: width,
+    height: height,
+    layers: 1,
+  )
+  checkVkResult vkCreateFramebuffer(vulkan.device, addr(framebufferInfo), nil, addr(result))
+
 proc svkGetBufferMemoryRequirements*(buffer: VkBuffer): tuple[size: uint64, alignment: uint64, memoryTypes: seq[uint32]] =
   var reqs: VkMemoryRequirements
   vkGetBufferMemoryRequirements(vulkan.device, buffer, addr(reqs))
@@ -167,6 +193,23 @@
     if ((1'u32 shl i) and reqs.memoryTypeBits) > 0:
       result.memoryTypes.add i
 
+proc svkCreateFence*(signaled = false): VkFence =
+  var fenceInfo = VkFenceCreateInfo(
+    sType: VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
+    flags: if signaled: toBits [VK_FENCE_CREATE_SIGNALED_BIT] else: VkFenceCreateFlags(0)
+  )
+  checkVkResult vkCreateFence(vulkan.device, addr(fenceInfo), nil, addr(result))
+
+proc svkCreateSemaphore*(): VkSemaphore =
+  var semaphoreInfo = VkSemaphoreCreateInfo(sType: VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO)
+  checkVkResult vkCreateSemaphore(vulkan.deivce, addr(semaphoreInfo), nil, addr(result))
+
+proc Await*(fence: VkFence, timeout = high(uint64)) =
+  checkVkResult vkWaitForFences(vulkan.device, 1, addr(fence), false, timeout)
+
+proc Reset*(fence: VkFence) =
+  checkVkResult vkResetFences(vulkan.device, 1, addr(fence))
+
 proc BestMemory*(mappable: bool, filter: seq[uint32] = @[]): uint32 =
   var physicalProperties: VkPhysicalDeviceMemoryProperties
   vkGetPhysicalDeviceMemoryProperties(vulkan.physicalDevice, addr(physicalProperties))
@@ -221,15 +264,9 @@
       pCommandBuffers: addr(`cmd`),
     )
 
-    var
-      fence: VkFence
-      fenceInfo = VkFenceCreateInfo(
-        sType: VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
-        # flags: toBits [VK_FENCE_CREATE_SIGNALED_BIT]
-      )
-    checkVkResult vulkan.device.vkCreateFence(addr(fenceInfo), nil, addr(fence))
+    var fence = svkCreateFence()
     checkVkResult vkQueueSubmit(vulkan.graphicsQueue, 1, addr(submitInfo), fence)
-    checkVkResult vkWaitForFences(vulkan.device, 1, addr fence, false, high(uint64))
+    fence.Await()
     vkDestroyCommandPool(vulkan.device, commandBufferPool, nil)
 
 template WithStagingBuffer*[T: (VkBuffer, uint64)|(VkImage, uint32, uint32)](