Mercurial > games > semicongine
view 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 source
import std/sequtils import std/enumutils import std/tables import std/strutils import std/logging import std/os include ./vkapi # ============================================================================= # 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): yield a template checkVkResult*(call: untyped) = when defined(release): discard call else: # yes, a bit cheap, but this is only for nice debug output var callstr = astToStr(call).replace("\n", "") while callstr.find(" ") >= 0: callstr = callstr.replace(" ", " ") debug "Calling vulkan: ", callstr let value = call if value != VK_SUCCESS: error "Vulkan error: ", astToStr(call), " returned ", $value raise newException( 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, messageTypes: VkDebugUtilsMessageTypeFlagsEXT, pCallbackData: ptr VkDebugUtilsMessengerCallbackDataEXT, userData: pointer, ): VkBool32 {.cdecl.} = const LOG_LEVEL_MAPPING = { VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: lvlDebug, VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: lvlInfo, VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: lvlWarn, VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: lvlError, }.toTable log LOG_LEVEL_MAPPING[messageSeverity], "SVK-LOG: ", $pCallbackData.pMessage if messageSeverity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: let errorMsg = $pCallbackData.pMessage & ": " & getStackTrace() raise newException(Exception, errorMsg) return VK_FALSE proc svkCreateInstance*( applicationName: string, enabledLayers: openArray[string] = [], enabledExtensions: openArray[string] = if defined(release): if defined(windows): @["VK_KHR_surface", "VK_KHR_win32_surface"] else: @["VK_KHR_surface", "VK_KHR_xlib_surface"] else: 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", ): SVkInstance = putEnv("VK_LOADER_LAYERS_ENABLE", "*validation") putEnv( "VK_LAYER_ENABLES", "VALIDATION_CHECK_ENABLE_VENDOR_SPECIFIC_AMD,VALIDATION_CHECK_ENABLE_VENDOR_SPECIFIC_NVIDIA,VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXTVK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_EXT,VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT", ) 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(allLayers) enabledExtensionsC = allocCStringArray(enabledExtensions) createinfo = VkInstanceCreateInfo( pApplicationInfo: addr appinfo, enabledLayerCount: allLayers.len.uint32, ppEnabledLayerNames: enabledLayersC, enabledExtensionCount: enabledExtensions.len.uint32, ppEnabledExtensionNames: enabledExtensionsC, ) checkVkResult vkCreateInstance(addr createinfo, nil, addr result.vkInstance) 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) for extension in enabledExtensions: loadExtension(result.vkInstance, extension) 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: 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