changeset 188:de9dff24c422

add: image/texture creation, refactoring of some unclean parts
author Sam <sam@basx.dev>
date Thu, 04 May 2023 23:44:15 +0700
parents b14e078690dc
children df92519d4d68
files src/semicongine/engine.nim src/semicongine/platform/linux/window.nim src/semicongine/vulkan/buffer.nim src/semicongine/vulkan/commandbuffer.nim src/semicongine/vulkan/device.nim src/semicongine/vulkan/image.nim src/semicongine/vulkan/memory.nim src/semicongine/vulkan/physicaldevice.nim src/semicongine/vulkan/swapchain.nim tests/test_vulkan_wrapper.nim
diffstat 10 files changed, 262 insertions(+), 55 deletions(-) [+]
line wrap: on
line diff
--- a/src/semicongine/engine.nim	Wed May 03 23:57:25 2023 +0700
+++ b/src/semicongine/engine.nim	Thu May 04 23:44:15 2023 +0700
@@ -1,4 +1,6 @@
 import std/sequtils
+import std/os
+
 import ./platform/window
 
 import ./vulkan/api
@@ -75,6 +77,8 @@
   if debug:
     instanceExtensions.add "VK_EXT_debug_utils"
     enabledLayers.add "VK_LAYER_KHRONOS_validation"
+    putEnv("VK_LAYER_ENABLES", "VK_VALIDATION_FEATURE_ENABLE_BEST_PRACTICES_EXT")
+
     if defined(linux):
       enabledLayers.add "VK_LAYER_MESA_overlay"
   result.instance = result.window.createInstance(
--- a/src/semicongine/platform/linux/window.nim	Wed May 03 23:57:25 2023 +0700
+++ b/src/semicongine/platform/linux/window.nim	Thu May 04 23:44:15 2023 +0700
@@ -5,8 +5,7 @@
 import
   x11/xlib,
   x11/xutil,
-  x11/keysym,
-  x11/x11pragma
+  x11/keysym
 import x11/x
 
 import ../../events
@@ -81,7 +80,6 @@
 proc fullscreen*(window: var NativeWindow, enable: bool) =
   var
     wm_state = window.display.XInternAtom("_NET_WM_STATE", 0)
-    op = (if enable: "_NET_WM_STATE_ADD" else: "_NET_WM_STATE_REMOVE")
     wm_fullscreen = window.display.XInternAtom("_NET_WM_STATE_FULLSCREEN", 0)
   var
     xev: XEvent
--- a/src/semicongine/vulkan/buffer.nim	Wed May 03 23:57:25 2023 +0700
+++ b/src/semicongine/vulkan/buffer.nim	Thu May 04 23:44:15 2023 +0700
@@ -20,10 +20,6 @@
       of false: discard
       of true:
         memory*: DeviceMemory
-  MemoryRequirements = object
-    size: uint64
-    alignment: uint64
-    memoryTypes: seq[MemoryType]
 
 
 proc `==`*(a, b: Buffer): bool =
@@ -102,42 +98,18 @@
   assert VK_BUFFER_USAGE_TRANSFER_SRC_BIT in src.usage
   assert VK_BUFFER_USAGE_TRANSFER_DST_BIT in dst.usage
 
-  var queue: Queue
-  for q in src.device.queues.values:
-    if q.family.canDoTransfer:
-      queue = q
-  if not queue.vk.valid:
-    raise newException(Exception, "No queue that supports buffer transfer")
-
-  var
-    commandBufferPool = src.device.createCommandBufferPool(family=queue.family, nBuffers=1)
-    commandBuffer = commandBufferPool.buffers[0]
-
-    beginInfo = VkCommandBufferBeginInfo(
-      sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
-      flags: VkCommandBufferUsageFlags(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT),
-    )
-    copyRegion = VkBufferCopy(size: VkDeviceSize(src.size), dstOffset: dstOffset)
-  checkVkResult commandBuffer.vkBeginCommandBuffer(addr(beginInfo))
-  commandBuffer.vkCmdCopyBuffer(src.vk, dst.vk, 1, addr(copyRegion))
-  checkVkResult commandBuffer.vkEndCommandBuffer()
-
-  var submitInfo = VkSubmitInfo(
-    sType: VK_STRUCTURE_TYPE_SUBMIT_INFO,
-    commandBufferCount: 1,
-    pCommandBuffers: addr(commandBuffer),
-  )
-  checkVkResult queue.vk.vkQueueSubmit(1, addr(submitInfo), VkFence(0))
-  checkVkResult queue.vk.vkQueueWaitIdle()
-  commandBufferPool.destroy()
+  var copyRegion = VkBufferCopy(size: VkDeviceSize(src.size), dstOffset: dstOffset)
+  withSingleUseCommandBuffer(src.device, true, commandBuffer):
+    commandBuffer.vkCmdCopyBuffer(src.vk, dst.vk, 1, addr(copyRegion))
 
 proc destroy*(buffer: var Buffer) =
   assert buffer.device.vk.valid
   assert buffer.vk.valid
+  buffer.device.vk.vkDestroyBuffer(buffer.vk, nil)
   if buffer.memoryAllocated:
     assert buffer.memory.vk.valid
     buffer.memory.free
-  buffer.device.vk.vkDestroyBuffer(buffer.vk, nil)
+    buffer.memoryAllocated = false
   buffer.vk.reset
 
 proc setData*(dst: Buffer, src: pointer, size: uint64, bufferOffset=0'u64) =
--- a/src/semicongine/vulkan/commandbuffer.nim	Wed May 03 23:57:25 2023 +0700
+++ b/src/semicongine/vulkan/commandbuffer.nim	Thu May 04 23:44:15 2023 +0700
@@ -31,6 +31,39 @@
   checkVkResult device.vk.vkAllocateCommandBuffers(addr(allocInfo), result.buffers.toCPointer)
 
 
+template withSingleUseCommandBuffer*(device: Device, needsTransfer: bool, commandBuffer, body: untyped): untyped =
+  assert device.vk.valid
+
+  var queue: Queue
+  for q in device.queues.values:
+    if q.family.canDoTransfer or not needsTransfer:
+      queue = q
+      break
+  if not queue.vk.valid:
+    raise newException(Exception, "No queue that supports buffer transfer")
+
+  var
+    commandBufferPool = createCommandBufferPool(device, queue.family, 1)
+    commandBuffer = commandBufferPool.buffers[0]
+    beginInfo = VkCommandBufferBeginInfo(
+      sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+      flags: VkCommandBufferUsageFlags(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT),
+    )
+  checkVkResult commandBuffer.vkBeginCommandBuffer(addr(beginInfo))
+
+  block:
+    body
+
+  checkVkResult commandBuffer.vkEndCommandBuffer()
+  var submitInfo = VkSubmitInfo(
+    sType: VK_STRUCTURE_TYPE_SUBMIT_INFO,
+    commandBufferCount: 1,
+    pCommandBuffers: addr(commandBuffer),
+  )
+  checkVkResult queue.vk.vkQueueSubmit(1, addr(submitInfo), VkFence(0))
+  checkVkResult queue.vk.vkQueueWaitIdle()
+  commandBufferPool.destroy()
+
 proc destroy*(commandpool: var CommandBufferPool) =
   assert commandpool.device.vk.valid
   assert commandpool.vk.valid
--- a/src/semicongine/vulkan/device.nim	Wed May 03 23:57:25 2023 +0700
+++ b/src/semicongine/vulkan/device.nim	Thu May 04 23:44:15 2023 +0700
@@ -12,6 +12,7 @@
     physicalDevice*: PhysicalDevice
     vk*: VkDevice
     queues*: Table[QueueFamily, Queue]
+    enabledFeatures*: VkPhysicalDeviceFeatures
   Queue* = object
     vk*: VkQueue
     family*: QueueFamily
@@ -48,7 +49,6 @@
       queueCount: 1,
       pQueuePriorities: addr(priority),
     )
-
   var queueList = deviceQueues.values.toSeq
   var createInfo = VkDeviceCreateInfo(
     sType: VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
@@ -58,7 +58,7 @@
     ppEnabledLayerNames: enabledLayersC,
     enabledExtensionCount: uint32(allExtensions.len),
     ppEnabledExtensionNames: enabledExtensionsC,
-    pEnabledFeatures: nil,
+    pEnabledFeatures: addr result.enabledFeatures,
   )
 
   checkVkResult vkCreateDevice(
--- a/src/semicongine/vulkan/image.nim	Wed May 03 23:57:25 2023 +0700
+++ b/src/semicongine/vulkan/image.nim	Thu May 04 23:44:15 2023 +0700
@@ -1,15 +1,201 @@
+import std/tables
+import std/logging
+
 import ./api
 import ./device
+import ./physicaldevice
+import ./buffer
+import ./memory
+import ./commandbuffer
 
 type
+  PixelDepth = 1'u32 .. 4'u32
   Image* = object
+    device*: Device
     vk*: VkImage
+    width*: uint32 # pixel
+    height*: uint32 # pixel
+    depth*: PixelDepth
     format*: VkFormat
-    device*: Device
+    usage*: seq[VkImageUsageFlagBits]
+    case memoryAllocated*: bool
+      of false: discard
+      of true:
+        memory*: DeviceMemory
   ImageView* = object
     vk*: VkImageView
     image*: Image
 
+const DEPTH_FORMAT_MAP = {
+  PixelDepth(1): VK_FORMAT_R8_SRGB,
+  PixelDepth(2): VK_FORMAT_R8G8_SRGB,
+  PixelDepth(3): VK_FORMAT_R8G8B8_SRGB,
+  PixelDepth(4): VK_FORMAT_R8G8B8A8_SRGB,
+}.toTable
+
+
+proc requirements(image: Image): MemoryRequirements =
+  assert image.vk.valid
+  assert image.device.vk.valid
+  var req: VkMemoryRequirements
+  image.device.vk.vkGetImageMemoryRequirements(image.vk, addr req)
+  result.size = req.size
+  result.alignment = req.alignment
+  let memorytypes = image.device.physicaldevice.vk.getMemoryProperties().types
+  for i in 0 ..< sizeof(req.memoryTypeBits) * 8:
+    if ((req.memoryTypeBits shr i) and 1) == 1:
+      result.memoryTypes.add memorytypes[i]
+
+proc allocateMemory(image: var Image, requireMappable: bool, preferVRAM: bool, preferAutoFlush: bool) =
+  assert image.device.vk.valid
+  assert image.memoryAllocated == false
+
+  let requirements = image.requirements()
+  let memoryType = requirements.memoryTypes.selectBestMemoryType(
+    requireMappable=requireMappable,
+    preferVRAM=preferVRAM,
+    preferAutoFlush=preferAutoFlush
+  )
+  image.memoryAllocated = true
+  debug "Allocating memory for image: ", image.width, "x", image.height, "x", image.depth, " bytes of type ", memoryType
+  image.memory = image.device.allocate(requirements.size, memoryType)
+  checkVkResult image.device.vk.vkBindImageMemory(image.vk, image.memory.vk, VkDeviceSize(0))
+
+proc transitionImageLayout*(image: Image, oldLayout, newLayout: VkImageLayout) =
+  var barrier = VkImageMemoryBarrier(
+    sType: VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+    oldLayout: oldLayout,
+    newLayout: newLayout,
+    srcQueueFamilyIndex: VK_QUEUE_FAMILY_IGNORED,
+    dstQueueFamilyIndex: VK_QUEUE_FAMILY_IGNORED,
+    image: image.vk,
+    subresourceRange: VkImageSubresourceRange(
+      aspectMask: toBits [VK_IMAGE_ASPECT_COLOR_BIT],
+      baseMipLevel: 0,
+      levelCount: 1,
+      baseArrayLayer: 0,
+      layerCount: 1,
+    ),
+  )
+  var
+    sourceStage, destinationStage: VkPipelineStageFlagBits
+  if oldLayout == VK_IMAGE_LAYOUT_UNDEFINED and newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
+    barrier.srcAccessMask = VkAccessFlags(0)
+    barrier.dstAccessMask = toBits [VK_ACCESS_TRANSFER_WRITE_BIT]
+    sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT
+    destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT
+  elif oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL and newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
+      barrier.srcAccessMask = toBits [VK_ACCESS_TRANSFER_WRITE_BIT]
+      barrier.dstAccessMask = toBits [VK_ACCESS_SHADER_READ_BIT]
+      sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT
+      destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
+  else:
+    raise newException(Exception, "Unsupported layout transition!")
+
+  withSingleUseCommandBuffer(image.device, false, commandBuffer):
+    vkCmdPipelineBarrier(
+      commandBuffer,
+      toBits [sourceStage], toBits [destinationStage],
+      VkDependencyFlags(0),
+      0, nil,
+      0, nil,
+      1, addr barrier
+    )
+
+proc copy*(src: Buffer, dst: Image) =
+  assert src.device.vk.valid
+  assert dst.device.vk.valid
+  assert src.device == dst.device
+  assert VK_BUFFER_USAGE_TRANSFER_SRC_BIT in src.usage
+  assert VK_IMAGE_USAGE_TRANSFER_DST_BIT in dst.usage
+
+  var region = VkBufferImageCopy(
+    bufferOffset: 0,
+    bufferRowLength: 0,
+    bufferImageHeight: 0,
+    imageSubresource: VkImageSubresourceLayers(
+      aspectMask: toBits [VK_IMAGE_ASPECT_COLOR_BIT],
+      mipLevel: 0,
+      baseArrayLayer: 0,
+      layerCount: 1,
+    ),
+    imageOffset: VkOffset3D(x: 0, y: 0, z: 0),
+    imageExtent: VkExtent3D(width: dst.width, height: dst.height, depth: 1)
+  )
+  withSingleUseCommandBuffer(src.device, true, commandBuffer):
+    commandBuffer.vkCmdCopyBufferToImage(
+      src.vk,
+      dst.vk,
+      VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+      1,
+      addr region
+    )
+
+# currently only usable for texture access from shader
+proc createImage(device: Device, width, height: uint32, depth: PixelDepth, data: pointer): Image =
+  assert device.vk.valid
+
+  let size = width * height * depth
+  result.width = width
+  result.height = height
+  result.depth = depth
+  result.format = DEPTH_FORMAT_MAP[depth]
+  result.usage = @[VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_USAGE_SAMPLED_BIT]
+
+  var imageInfo = VkImageCreateInfo(
+    sType: VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+    imageType: VK_IMAGE_TYPE_2D,
+    extent: VkExtent3D(width: width, height: height, depth: 1),
+    mipLevels: 1,
+    arrayLayers: 1,
+    format: result.format,
+    tiling: VK_IMAGE_TILING_OPTIMAL,
+    initialLayout: VK_IMAGE_LAYOUT_UNDEFINED,
+    usage: toBits result.usage,
+    sharingMode: VK_SHARING_MODE_EXCLUSIVE,
+    samples: VK_SAMPLE_COUNT_1_BIT,
+  )
+  checkVkResult device.vk.vkCreateImage(addr imageInfo, nil, addr result.vk)
+  result.transitionImageLayout(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
+
+  var stagingBuffer = device.createBuffer(size=size, usage=[VK_BUFFER_USAGE_TRANSFER_SRC_BIT], requireMappable=true, preferVRAM=false, preferAutoFlush=true)
+  stagingBuffer.setData(src=data, size=size)
+  stagingBuffer.copy(result)
+  result.transitionImageLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
+  stagingBuffer.destroy()
+
+proc destroy*(image: var Image) =
+  assert image.device.vk.valid
+  assert image.vk.valid
+  image.device.vk.vkDestroyImage(image.vk, nil)
+  if image.memoryAllocated:
+    assert image.memory.vk.valid
+    image.memory.free
+    image.memoryAllocated = false
+  image.vk.reset
+
+proc createSampler(device: Device): VkSampler =
+  var samplerInfo = VkSamplerCreateInfo(
+    sType: VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
+    magFilter: VK_FILTER_LINEAR,
+    minFilter: VK_FILTER_LINEAR,
+    addressModeU: VK_SAMPLER_ADDRESS_MODE_REPEAT,
+    addressModeV: VK_SAMPLER_ADDRESS_MODE_REPEAT,
+    addressModeW: VK_SAMPLER_ADDRESS_MODE_REPEAT,
+    anisotropyEnable: device.enabledFeatures.samplerAnisotropy,
+    maxAnisotropy: device.physicalDevice.properties.limits.maxSamplerAnisotropy,
+    borderColor: VK_BORDER_COLOR_INT_OPAQUE_BLACK,
+    unnormalizedCoordinates: VK_FALSE,
+    compareEnable: VK_FALSE,
+    compareOp: VK_COMPARE_OP_ALWAYS,
+    mipmapMode: VK_SAMPLER_MIPMAP_MODE_LINEAR,
+    mipLodBias: 0,
+    minLod: 0,
+    maxLod: 0,
+  )
+  checkVkResult device.vk.vkCreateSampler(addr samplerInfo, nil, addr result)
+
+
 proc createImageView*(
   image: Image,
   imageviewtype=VK_IMAGE_VIEW_TYPE_2D,
--- a/src/semicongine/vulkan/memory.nim	Wed May 03 23:57:25 2023 +0700
+++ b/src/semicongine/vulkan/memory.nim	Thu May 04 23:44:15 2023 +0700
@@ -24,6 +24,10 @@
       of false: discard
       of true: data*: pointer
     needsFlushing*: bool
+  MemoryRequirements* = object
+    size*: uint64
+    alignment*: uint64
+    memoryTypes*: seq[MemoryType]
 
 func `$`*(memoryType: MemoryType): string =
   &"Memorytype {memoryType.flags} (heap size: {memoryType.heap.size}, heap flags: {memoryType.heap.flags})"
--- a/src/semicongine/vulkan/physicaldevice.nim	Wed May 03 23:57:25 2023 +0700
+++ b/src/semicongine/vulkan/physicaldevice.nim	Thu May 04 23:44:15 2023 +0700
@@ -12,16 +12,14 @@
     name*: string
     devicetype*: VkPhysicalDeviceType
     surface*: VkSurfaceKHR
+    properties*: VkPhysicalDeviceProperties
+    features*: VkPhysicalDeviceFeatures
   QueueFamily* = object
     device: PhysicalDevice
     properties*: VkQueueFamilyProperties
     index*: uint32
     flags*: seq[VkQueueFlagBits]
 
-proc getProperties*(device: PhysicalDevice): VkPhysicalDeviceProperties =
-  assert device.vk.valid
-  device.vk.vkGetPhysicalDeviceProperties(addr result)
-
 proc getPhysicalDevices*(instance: Instance): seq[PhysicalDevice] =
   assert instance.vk.valid
   assert instance.surface.valid
@@ -31,9 +29,10 @@
   checkVkResult vkEnumeratePhysicalDevices(instance.vk, addr(nDevices), devices.toCPointer)
   for i in 0 ..< nDevices:
     var device = PhysicalDevice(vk: devices[i], surface: instance.surface)
-    let props = device.getProperties()
-    device.name = props.deviceName.cleanString()
-    device.devicetype = props.deviceType
+    device.vk.vkGetPhysicalDeviceProperties(addr device.properties)
+    device.vk.vkGetPhysicalDeviceFeatures(addr device.features)
+    device.name = device.properties.deviceName.cleanString()
+    device.devicetype = device.properties.deviceType
     result.add device
 
 proc getExtensions*(device: PhysicalDevice): seq[string] =
@@ -46,10 +45,6 @@
     for extension in extensions:
       result.add(cleanString(extension.extensionName))
 
-proc getFeatures*(device: PhysicalDevice): VkPhysicalDeviceFeatures =
-  assert device.vk.valid
-  device.vk.vkGetPhysicalDeviceFeatures(addr result)
-
 proc getSurfaceCapabilities*(device: PhysicalDevice): VkSurfaceCapabilitiesKHR =
   assert device.vk.valid
   assert device.surface.valid
--- a/src/semicongine/vulkan/swapchain.nim	Wed May 03 23:57:25 2023 +0700
+++ b/src/semicongine/vulkan/swapchain.nim	Thu May 04 23:44:15 2023 +0700
@@ -43,7 +43,7 @@
   surfaceFormat: VkSurfaceFormatKHR,
   queueFamily: QueueFamily,
   desiredNumberOfImages=3'u32,
-  presentMode: VkPresentModeKHR=VK_PRESENT_MODE_MAILBOX_KHR,
+  preferedPresentMode: VkPresentModeKHR=VK_PRESENT_MODE_MAILBOX_KHR,
   inFlightFrames=2,
   oldSwapchain=VkSwapchainKHR(0)
 ): Option[Swapchain] =
@@ -57,6 +57,22 @@
     return none(Swapchain)
 
   var imageCount = desiredNumberOfImages
+
+  const PRESENTMODES_BY_PREFERENCE = [
+    VK_PRESENT_MODE_MAILBOX_KHR,
+    VK_PRESENT_MODE_FIFO_RELAXED_KHR,
+    VK_PRESENT_MODE_FIFO_KHR,
+    VK_PRESENT_MODE_IMMEDIATE_KHR,
+    VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR,
+    VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR,
+  ]
+  var supportedModes = device.physicalDevice.getSurfacePresentModes()
+  var presentMode: VkPresentModeKHR
+  for mode in PRESENTMODES_BY_PREFERENCE:
+    if mode in supportedModes:
+      presentMode = mode
+      break
+
   # following is according to vulkan specs
   if presentMode in [VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR, VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR]:
     imageCount = 1
@@ -214,7 +230,6 @@
     surfaceFormat=swapchain.surfaceFormat,
     queueFamily=swapchain.queueFamily,
     desiredNumberOfImages=swapchain.imageCount,
-    presentMode=swapchain.presentMode,
     inFlightFrames=swapchain.inFlightFrames,
     oldSwapchain=swapchain.vk,
   )
--- a/tests/test_vulkan_wrapper.nim	Wed May 03 23:57:25 2023 +0700
+++ b/tests/test_vulkan_wrapper.nim	Thu May 04 23:44:15 2023 +0700
@@ -20,9 +20,9 @@
     for extension in device.getExtensions():
       echo "    " & $extension
     echo "  Properties"
-    echo "  " & $device.getProperties()
+    echo "  " & $device.properties
     echo "  Features"
-    echo "  " & $device.getFeatures()
+    echo "  " & $device.features
     echo "  Queue families"
     for queueFamily in device.getQueueFamilies():
       echo "    " & $queueFamily
@@ -130,7 +130,7 @@
       inputs=vertexOutput,
       uniforms=uniforms,
       outputs=fragOutput,
-      main="color = vec4(outcolor, 1);"
+      main="color = vec4(outcolor, 0.8);"
     )
   var renderPass = engine.gpuDevice.simpleForwardRenderPass(vertexCode, fragmentCode)
   engine.setRenderer(renderPass)