diff svk/api.nim @ 1489:e6bd1f553c1b default tip main

add: quite a bit more wrapper
author sam <sam@basx.dev>
date Sun, 18 May 2025 23:47:16 +0700
parents 3ce7c132fdac
children
line wrap: on
line diff
--- a/svk/api.nim	Sun May 18 16:36:52 2025 +0700
+++ b/svk/api.nim	Sun May 18 23:47:16 2025 +0700
@@ -7,7 +7,27 @@
 
 include ./vkapi
 
-const VULKAN_VERSION = VK_MAKE_API_VERSION(0, 1, 3, 0)
+# =============================================================================
+# UTILS =======================================================================
+# =============================================================================
+
+if not defined(release):
+  addHandler(newConsoleLogger())
+  addHandler(newFileLogger("svk.log"))
+
+# log level
+when defined(release):
+  const LOGLEVEL {.strdefine.}: string = "Warn"
+else:
+  const LOGLEVEL {.strdefine.}: string = "Debug"
+setLogFilter(parseEnum[Level]("lvl" & LOGLEVEL))
+
+template debugAssert(arg: untyped): untyped =
+  when not defined(release):
+    assert arg
+
+# we will support vulkan 1.2 for maximum portability
+const VULKAN_VERSION = VK_MAKE_API_VERSION(0, 1, 2, 0)
 
 iterator items*[T: HoleyEnum](E: typedesc[T]): T =
   for a in enumFullRange(E):
@@ -29,17 +49,53 @@
         Exception, "Vulkan error: " & astToStr(call) & " returned " & $value
       )
 
+# =============================================================================
+# PLATFORM TYPES ==============================================================
+# =============================================================================
+
+when defined(windows):
+  type NativeWindow* = object
+    hinstance*: HINSTANCE
+    hwnd*: HWND
+    g_wpPrev*: WINDOWPLACEMENT
+else:
+  type NativeWindow* = object
+    display*: ptr Display
+    window*: Window
+    emptyCursor*: Cursor
+    ic*: XIC
+
+
+
+
+# =============================================================================
+# VULKAN INSTANCE =============================================================
+# =============================================================================
+
 type SVkInstance* = object
   vkInstance: VkInstance
   debugMessenger: VkDebugUtilsMessengerEXT
+  window: NativeWindow
+  vkSurface*: VkSurfaceKHR
+
+proc createWindow(title: string): NativeWindow
 
 proc `=copy`(a: var SVkInstance, b: SVkInstance) {.error.}
 
 proc `=destroy`(a: SVkInstance) =
+  debugAssert a.vkInstance.pointer == nil
+  debugAssert a.vkSurface.pointer == nil
+  debugAssert a.debugMessenger.pointer == nil
+
+proc destroy*(a: var SVkInstance) =
   if a.vkInstance.pointer != nil:
     if a.debugMessenger.pointer != nil:
       vkDestroyDebugUtilsMessengerEXT(a.vkInstance, a.debugMessenger, nil)
+      a.debugMessenger = VkDebugUtilsMessengerEXT(nil)
+    vkDestroySurfaceKHR(a.vkInstance, a.vkSurface, nil)
+    a.vkSurface = VkSurfaceKHR(nil)
     a.vkInstance.vkDestroyInstance(nil)
+    a.vkInstance = VkInstance(nil)
 
 proc debugCallback(
     messageSeverity: VkDebugUtilsMessageSeverityFlagBitsEXT,
@@ -53,10 +109,8 @@
     VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: lvlWarn,
     VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: lvlError,
   }.toTable
-  log LOG_LEVEL_MAPPING[messageSeverity]
+  log LOG_LEVEL_MAPPING[messageSeverity], "SVK-LOG: ", $pCallbackData.pMessage
   if messageSeverity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:
-    # stderr.write getStackTrace()
-    # stderr.writeLine LOG_LEVEL_MAPPING[messageSeverity], &"{toEnums messageTypes}: {pCallbackData.pMessage}"
     let errorMsg = $pCallbackData.pMessage & ": " & getStackTrace()
     raise newException(Exception, errorMsg)
   return VK_FALSE
@@ -66,11 +120,16 @@
     enabledLayers: openArray[string] = [],
     enabledExtensions: openArray[string] =
       if defined(release):
-        @["VK_KHR_surface"]
+        if defined(windows):
+          @["VK_KHR_surface", "VK_KHR_win32_surface"]
+        else:
+          @["VK_KHR_surface", "VK_KHR_xlib_surface"]
       else:
-        @["VK_KHR_surface", "VK_EXT_debug_utils"],
+        if defined(windows):
+          @["VK_KHR_surface", "VK_EXT_debug_utils", "VK_KHR_win32_surface"]
+        else:
+          @["VK_KHR_surface", "VK_EXT_debug_utils", "VK_KHR_xlib_surface"],
     engineName = "semicongine",
-    withSwapchain = true,
 ): SVkInstance =
   putEnv("VK_LOADER_LAYERS_ENABLE", "*validation")
   putEnv(
@@ -79,17 +138,21 @@
   )
   initVulkanLoader()
 
+  var allLayers = @enabledLayers
+  when not defined(release):
+    allLayers.add "VK_LAYER_KHRONOS_validation"
+
   let
     appinfo = VkApplicationInfo(
       pApplicationName: applicationName,
       pEngineName: engineName,
       apiVersion: VULKAN_VERSION,
     )
-    enabledLayersC = allocCStringArray(enabledLayers)
+    enabledLayersC = allocCStringArray(allLayers)
     enabledExtensionsC = allocCStringArray(enabledExtensions)
     createinfo = VkInstanceCreateInfo(
       pApplicationInfo: addr appinfo,
-      enabledLayerCount: enabledLayers.len.uint32,
+      enabledLayerCount: allLayers.len.uint32,
       ppEnabledLayerNames: enabledLayersC,
       enabledExtensionCount: enabledExtensions.len.uint32,
       ppEnabledExtensionNames: enabledExtensionsC,
@@ -99,22 +162,224 @@
   enabledLayersC.deallocCStringArray()
   enabledExtensionsC.deallocCStringArray()
 
+  # only support up to vulkan 1.2 for maximum portability
   load_VK_VERSION_1_0(result.vkInstance)
   load_VK_VERSION_1_1(result.vkInstance)
   load_VK_VERSION_1_2(result.vkInstance)
-  load_VK_VERSION_1_3(result.vkInstance)
 
   for extension in enabledExtensions:
     loadExtension(result.vkInstance, extension)
-  if withSwapchain:
-    load_VK_KHR_swapchain(result.vkInstance)
+  load_VK_KHR_swapchain(result.vkInstance)
 
+  var allTypes: VkDebugUtilsMessageTypeFlagsEXT
+  for t in VkDebugUtilsMessageTypeFlagBitsEXT:
+    allTypes = allTypes or t
   when not defined(release):
     var debugMessengerCreateInfo = VkDebugUtilsMessengerCreateInfoEXT(
-      messageSeverity: VkDebugUtilsMessageSeverityFlagBitsEXT.items.toSeq,
-      messageType: VkDebugUtilsMessageTypeFlagBitsEXT.items.toSeq,
+      messageSeverity: VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT or VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT or VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT or VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT,
+      messageType: VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT or VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT or VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT,
       pfnUserCallback: debugCallback,
     )
     checkVkResult vkCreateDebugUtilsMessengerEXT(
       result.vkInstance, addr debugMessengerCreateInfo, nil, addr result.debugMessenger
     )
+
+
+  result.window = createWindow(applicationName)
+  when defined(windows):
+    var surfaceCreateInfo = VkWin32SurfaceCreateInfoKHR(hinstance: window.hinstance, hwnd: window.hwnd)
+    checkVkResult vkCreateWin32SurfaceKHR(instance, addr surfaceCreateInfo, nil, addr result.vkSurface)
+  else:
+    var surfaceCreateInfo = VkXlibSurfaceCreateInfoKHR(dpy: result.window.display, window: result.window.window)
+    checkVkResult result.vkInstance.vkCreateXlibSurfaceKHR(addr surfaceCreateInfo, nil, addr result.vkSurface)
+
+
+
+# =============================================================================
+# PHYSICAL DEVICES ============================================================
+# =============================================================================
+
+type
+  SVkMemoryType* = object
+    size: uint64
+    deviceLocal: bool # fast for gpu access
+    hostCached: bool # fast for host access
+    hostVisible: bool # can use vkMapMemory
+    hostCohorent: bool # does *not* require vkFlushMappedMemoryRanges and vkInvalidateMappedMemoryRanges 
+  SVkQueueFamilies* = object
+    count: int
+    hasGraphics: bool # implies "hasTransfer"
+    hasCompute: bool # implies "hasTransfer"
+
+  SVkPhysicalDevice* = object
+    name*: string
+    vkPhysicalDevice*: VkPhysicalDevice
+    vkPhysicalDeviceFeatures*: VkPhysicalDeviceFeatures
+    vkPhysicalDeviceProperties*: VkPhysicalDeviceProperties
+    memoryTypes*: seq[SVkMemoryType]
+    queueFamily*: uint32
+
+proc getUsablePhysicalDevices*(instance: SVkInstance): seq[SVkPhysicalDevice] =
+  var nDevices: uint32
+  checkVkResult instance.vkInstance.vkEnumeratePhysicalDevices(addr nDevices, nil)
+  var devices = newSeq[VkPhysicalDevice](nDevices)
+  checkVkResult instance.vkInstance.vkEnumeratePhysicalDevices(addr nDevices, addr devices[0])
+  for d in devices:
+    var dev = SVkPhysicalDevice(vkPhysicalDevice: d)
+    d.vkGetPhysicalDeviceFeatures(addr dev.vkPhysicalDeviceFeatures)
+    d.vkGetPhysicalDeviceProperties(addr dev.vkPhysicalDeviceProperties)
+
+    if dev.vkPhysicalDeviceProperties.deviceType notin [VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU]:
+      continue
+    dev.name = $cast[cstring](addr dev.vkPhysicalDeviceProperties.deviceName[0])
+
+    var memoryProperties: VkPhysicalDeviceMemoryProperties
+    d.vkGetPhysicalDeviceMemoryProperties(addr memoryProperties)
+    for i in 0 ..< memoryProperties.memoryTypeCount:
+      let heapI = memoryProperties.memoryTypes[i].heapIndex
+      dev.memoryTypes.add SVkMemoryType(
+        size: memoryProperties.memoryHeaps[heapI].size.uint64,
+        deviceLocal: VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT in memoryProperties.memoryTypes[i].propertyFlags,
+        hostCached: VK_MEMORY_PROPERTY_HOST_CACHED_BIT in memoryProperties.memoryTypes[i].propertyFlags,
+        hostVisible: VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT in memoryProperties.memoryTypes[i].propertyFlags,
+        hostCohorent: VK_MEMORY_PROPERTY_HOST_COHERENT_BIT  in memoryProperties.memoryTypes[i].propertyFlags,
+      )
+      
+
+    var familyQueueCount: uint32
+    var vkQueueFamilyProperties: seq[VkQueueFamilyProperties]
+    d.vkGetPhysicalDeviceQueueFamilyProperties(addr familyQueueCount, nil)
+    vkQueueFamilyProperties.setLen(familyQueueCount)
+    d.vkGetPhysicalDeviceQueueFamilyProperties(addr familyQueueCount, addr vkQueueFamilyProperties[0])
+    dev.queueFamily = high(uint32)
+    for i in 0 ..< familyQueueCount:
+      let hasGraphics = VK_QUEUE_GRAPHICS_BIT in vkQueueFamilyProperties[i].queueFlags
+      let hasCompute = VK_QUEUE_COMPUTE_BIT in vkQueueFamilyProperties[i].queueFlags
+      let hasPresentation = VK_FALSE
+      checkVkResult dev.vkPhysicalDevice.vkGetPhysicalDeviceSurfaceSupportKHR(i, instance.vkSurface, addr hasPresentation)
+
+      if hasGraphics and hasCompute and bool(hasPresentation):
+        dev.queueFamily = i
+        break
+    if dev.queueFamily == high(uint32):
+      raise newException(Exception, "Did not find queue family with graphics and compute support!")
+
+    result.add dev
+
+when defined(windows):
+  proc createWindow(title: string): NativeWindow =
+    result.hInstance = HINSTANCE(GetModuleHandle(nil))
+    var
+      windowClassName = T"EngineWindowClass"
+      windowName = T(title)
+      windowClass = WNDCLASSEX(
+        cbSize: UINT(WNDCLASSEX.sizeof),
+        lpfnWndProc: windowHandler,
+        hInstance: result.hInstance,
+        lpszClassName: windowClassName,
+        hcursor: currentCursor,
+      )
+
+    if (RegisterClassEx(addr(windowClass)) == 0):
+      raise newException(Exception, "Unable to register window class")
+
+    result.hwnd = CreateWindowEx(
+      DWORD(0),
+      windowClassName,
+      windowName,
+      DWORD(WS_OVERLAPPEDWINDOW),
+      CW_USEDEFAULT,
+      CW_USEDEFAULT,
+      CW_USEDEFAULT,
+      CW_USEDEFAULT,
+      HMENU(0),
+      HINSTANCE(0),
+      result.hInstance,
+      nil,
+    )
+
+    result.g_wpPrev.length = UINT(sizeof(WINDOWPLACEMENT))
+    discard result.hwnd.ShowWindow(SW_SHOW)
+    discard result.hwnd.SetForegroundWindow()
+    discard result.hwnd.SetFocus()
+
+  proc destroyWindow*(window: NativeWindow) =
+    DestroyWindow(window.hwnd)
+
+else:
+  import ../semicongine/thirdparty/x11/xkblib
+  import ../semicongine/thirdparty/x11/xutil
+
+  var deleteMessage* {.hint[GlobalVar]: off.}: x.Atom # one internal use, not serious
+
+  proc XErrorLogger(display: PDisplay, event: PXErrorEvent): cint {.cdecl.} =
+    logging.error "Xlib: " & $event[]
+
+  proc createWindow(title: string): NativeWindow =
+    doAssert XInitThreads() != 0
+    let display = XOpenDisplay(nil)
+    if display == nil:
+      quit "Failed to open display"
+    discard XSetErrorHandler(XErrorLogger)
+
+    let screen = display.XDefaultScreen()
+    let rootWindow = display.XRootWindow(screen)
+    let vis = display.XDefaultVisual(screen)
+    discard display.XkbSetDetectableAutoRepeat(true, nil)
+    var
+      attribs: XWindowAttributes
+      width = cuint(800)
+      height = cuint(600)
+    doAssert display.XGetWindowAttributes(rootWindow, addr(attribs)) != 0
+
+    var attrs = XSetWindowAttributes(
+      event_mask:
+        FocusChangeMask or KeyPressMask or KeyReleaseMask or ExposureMask or
+        VisibilityChangeMask or StructureNotifyMask or ButtonMotionMask or ButtonPressMask or
+        ButtonReleaseMask
+    )
+    let window = display.XCreateWindow(
+      rootWindow,
+      (attribs.width - cint(width)) div 2,
+      (attribs.height - cint(height)) div 2,
+      width,
+      height,
+      0,
+      display.XDefaultDepth(screen),
+      InputOutput,
+      vis,
+      CWEventMask,
+      addr(attrs),
+    )
+    doAssert display.XSetStandardProperties(window, title, "window", 0, nil, 0, nil) != 0
+    # get an input context, to allow encoding of key-events to characters
+
+    let im = XOpenIM(display, nil, nil, nil)
+    assert im != nil
+    let ic = im.XCreateIC(XNInputStyle, XIMPreeditNothing or XIMStatusNothing, nil)
+    assert ic != nil
+
+    doAssert display.XMapWindow(window) != 0
+
+    deleteMessage = display.XInternAtom("WM_DELETE_WINDOW", XBool(false))
+    doAssert display.XSetWMProtocols(window, addr(deleteMessage), 1) != 0
+
+    var data = "\0".cstring
+    var pixmap = display.XCreateBitmapFromData(window, data, 1, 1)
+    var color: XColor
+    var empty_cursor =
+      display.XCreatePixmapCursor(pixmap, pixmap, addr(color), addr(color), 0, 0)
+    doAssert display.XFreePixmap(pixmap) != 0
+
+    discard display.XSync(0)
+
+    # wait until window is shown
+    var ev: XEvent
+    while ev.theType != MapNotify:
+      discard display.XNextEvent(addr(ev))
+
+    result = NativeWindow(display: display, window: window, emptyCursor: empty_cursor, ic: ic)
+
+  proc destroyWindow*(window: NativeWindow) =
+    doAssert XDestroyWindow(window.display, window.window) != 0
+