# HG changeset patch # User sam # Date 1747586836 -25200 # Node ID e6bd1f553c1b85b92dcfaf88eb2bb142e1b5a213 # Parent 3ce7c132fdac480f7b2634f551413a59300c6b0f add: quite a bit more wrapper diff -r 3ce7c132fdac -r e6bd1f553c1b svk/api.nim --- 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 + diff -r 3ce7c132fdac -r e6bd1f553c1b svk/generate.nim --- a/svk/generate.nim Sun May 18 16:36:52 2025 +0700 +++ b/svk/generate.nim Sun May 18 23:47:16 2025 +0700 @@ -55,6 +55,7 @@ name: string values: seq[EnumEntry] isBitmask: bool + nBytes: int = 4 ConstantDef = object name: string @@ -203,7 +204,8 @@ edef.addValue(ee) enums[edef.name] = edef elif e.attr("type") == "bitmask": - var edef = EnumDef(name: e.attr("name"), isBitmask: true) + let nBytes = if e.attr("bitwidth") == "": 4 else: parseInt(e.attr("bitwidth")) div 8 + var edef = EnumDef(name: e.attr("name"), isBitmask: true, nBytes: nBytes) for ee in e.findAll("enum"): edef.addValue(ee) enums[edef.name] = edef @@ -236,8 +238,6 @@ outFile.writeLine """ import std/dynlib -import std/strutils -import std/tables import std/macros import std/typetraits @@ -255,16 +255,6 @@ macro enumFullRange(a: typed): untyped = newNimNode(nnkBracket).add(a.getType[1][1 ..^ 1]) -func asBits[T, S](flags: openArray[T]): S = - for flag in flags: - let a = distinctBase(S)(result) - let b = distinctBase(S)(flag) - result = S(a or b) - -func toEnums*[T, S](number: T): seq[S] = - for value in enumFullRange(S): - if (value.ord and cint(number)) > 0: - result.add value """ outFile.writeLine "type" @@ -352,7 +342,7 @@ for edef in enums.values(): if edef.values.len > 0: - outFile.writeLine &"type {edef.name}* {{.size: 4.}} = enum" + outFile.writeLine &"type {edef.name}* {{.size: {edef.nBytes}.}} = enum" for ee in edef.values: # due to the nim identifier-system, there might be collisions between typenames and enum-member names if ee.name in nameCollisions: @@ -432,14 +422,25 @@ for edef in enums.values(): if edef.values.len > 0: if edef.isBitmask: - let bitsName = edef.name - let p = bitsName.rfind("FlagBits") - let flagsName = bitsName[0 ..< p] & "Flags" & bitsName[p + 8 .. ^1] + let enumName = edef.name + let p = enumName.rfind("FlagBits") + let flagSetType = enumName[0 ..< p] & "Flags" & enumName[p + 8 .. ^1] - outFile.writeLine &"converter {bitsName}ToBits*(flags: openArray[{bitsName}]): {flagsName} =" - outFile.writeLine &" asBits[{bitsName}, {flagsName}](flags)" - outFile.writeLine &"func `$`*(bits: {flagsName}): string =" - outFile.writeLine &" $toEnums[{flagsName}, {bitsName}](bits)" + outFile.writeLine &""" +func `or`(a: {flagSetType}, b: {enumName}): {flagSetType} {{.hint[XDeclaredButNotUsed]: off.}} = + {flagSetType}(distinctBase({flagSetType})(a) or distinctBase({flagSetType})(b)) +func `or`(a, b: {enumName}): {flagSetType} {{.hint[XDeclaredButNotUsed]: off.}} = + {flagSetType}(distinctBase({flagSetType})(a) or distinctBase({flagSetType})(b)) +func contains(flags: {flagSetType}, flag: {enumName}): bool {{.hint[XDeclaredButNotUsed]: off.}} = + (distinctBase({flagSetType})(flags) and distinctBase({flagSetType})(flag)) > 0 +""" + outFile.writeLine &"func `$`*(bits: {flagSetType}): string =" + outFile.writeLine &""" + for value in enumFullRange({enumName}): + if (value.ord and cint(bits)) > 0: + result &= $value & "|" + result.setLen(max(0, result.len - 1)) +""" outFile.writeLine "" for command in commands: diff -r 3ce7c132fdac -r e6bd1f553c1b svk/test.nim --- a/svk/test.nim Sun May 18 16:36:52 2025 +0700 +++ b/svk/test.nim Sun May 18 23:47:16 2025 +0700 @@ -1,4 +1,12 @@ import ./api -let vk = svkCreateInstance("test") +var vk = svkCreateInstance("test") echo vk +for d in vk.getUsablePhysicalDevices(): + echo d.name, " queue familye: ", d.queueFamily + echo "memory types:" + for mt in d.memoryTypes: + echo " ", mt + echo "" + +vk.destroy()