comparison 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
comparison
equal deleted inserted replaced
1488:3ce7c132fdac 1489:e6bd1f553c1b
5 import std/logging 5 import std/logging
6 import std/os 6 import std/os
7 7
8 include ./vkapi 8 include ./vkapi
9 9
10 const VULKAN_VERSION = VK_MAKE_API_VERSION(0, 1, 3, 0) 10 # =============================================================================
11 # UTILS =======================================================================
12 # =============================================================================
13
14 if not defined(release):
15 addHandler(newConsoleLogger())
16 addHandler(newFileLogger("svk.log"))
17
18 # log level
19 when defined(release):
20 const LOGLEVEL {.strdefine.}: string = "Warn"
21 else:
22 const LOGLEVEL {.strdefine.}: string = "Debug"
23 setLogFilter(parseEnum[Level]("lvl" & LOGLEVEL))
24
25 template debugAssert(arg: untyped): untyped =
26 when not defined(release):
27 assert arg
28
29 # we will support vulkan 1.2 for maximum portability
30 const VULKAN_VERSION = VK_MAKE_API_VERSION(0, 1, 2, 0)
11 31
12 iterator items*[T: HoleyEnum](E: typedesc[T]): T = 32 iterator items*[T: HoleyEnum](E: typedesc[T]): T =
13 for a in enumFullRange(E): 33 for a in enumFullRange(E):
14 yield a 34 yield a
15 35
27 error "Vulkan error: ", astToStr(call), " returned ", $value 47 error "Vulkan error: ", astToStr(call), " returned ", $value
28 raise newException( 48 raise newException(
29 Exception, "Vulkan error: " & astToStr(call) & " returned " & $value 49 Exception, "Vulkan error: " & astToStr(call) & " returned " & $value
30 ) 50 )
31 51
52 # =============================================================================
53 # PLATFORM TYPES ==============================================================
54 # =============================================================================
55
56 when defined(windows):
57 type NativeWindow* = object
58 hinstance*: HINSTANCE
59 hwnd*: HWND
60 g_wpPrev*: WINDOWPLACEMENT
61 else:
62 type NativeWindow* = object
63 display*: ptr Display
64 window*: Window
65 emptyCursor*: Cursor
66 ic*: XIC
67
68
69
70
71 # =============================================================================
72 # VULKAN INSTANCE =============================================================
73 # =============================================================================
74
32 type SVkInstance* = object 75 type SVkInstance* = object
33 vkInstance: VkInstance 76 vkInstance: VkInstance
34 debugMessenger: VkDebugUtilsMessengerEXT 77 debugMessenger: VkDebugUtilsMessengerEXT
78 window: NativeWindow
79 vkSurface*: VkSurfaceKHR
80
81 proc createWindow(title: string): NativeWindow
35 82
36 proc `=copy`(a: var SVkInstance, b: SVkInstance) {.error.} 83 proc `=copy`(a: var SVkInstance, b: SVkInstance) {.error.}
37 84
38 proc `=destroy`(a: SVkInstance) = 85 proc `=destroy`(a: SVkInstance) =
86 debugAssert a.vkInstance.pointer == nil
87 debugAssert a.vkSurface.pointer == nil
88 debugAssert a.debugMessenger.pointer == nil
89
90 proc destroy*(a: var SVkInstance) =
39 if a.vkInstance.pointer != nil: 91 if a.vkInstance.pointer != nil:
40 if a.debugMessenger.pointer != nil: 92 if a.debugMessenger.pointer != nil:
41 vkDestroyDebugUtilsMessengerEXT(a.vkInstance, a.debugMessenger, nil) 93 vkDestroyDebugUtilsMessengerEXT(a.vkInstance, a.debugMessenger, nil)
94 a.debugMessenger = VkDebugUtilsMessengerEXT(nil)
95 vkDestroySurfaceKHR(a.vkInstance, a.vkSurface, nil)
96 a.vkSurface = VkSurfaceKHR(nil)
42 a.vkInstance.vkDestroyInstance(nil) 97 a.vkInstance.vkDestroyInstance(nil)
98 a.vkInstance = VkInstance(nil)
43 99
44 proc debugCallback( 100 proc debugCallback(
45 messageSeverity: VkDebugUtilsMessageSeverityFlagBitsEXT, 101 messageSeverity: VkDebugUtilsMessageSeverityFlagBitsEXT,
46 messageTypes: VkDebugUtilsMessageTypeFlagsEXT, 102 messageTypes: VkDebugUtilsMessageTypeFlagsEXT,
47 pCallbackData: ptr VkDebugUtilsMessengerCallbackDataEXT, 103 pCallbackData: ptr VkDebugUtilsMessengerCallbackDataEXT,
51 VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: lvlDebug, 107 VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: lvlDebug,
52 VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: lvlInfo, 108 VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: lvlInfo,
53 VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: lvlWarn, 109 VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: lvlWarn,
54 VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: lvlError, 110 VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: lvlError,
55 }.toTable 111 }.toTable
56 log LOG_LEVEL_MAPPING[messageSeverity] 112 log LOG_LEVEL_MAPPING[messageSeverity], "SVK-LOG: ", $pCallbackData.pMessage
57 if messageSeverity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: 113 if messageSeverity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:
58 # stderr.write getStackTrace()
59 # stderr.writeLine LOG_LEVEL_MAPPING[messageSeverity], &"{toEnums messageTypes}: {pCallbackData.pMessage}"
60 let errorMsg = $pCallbackData.pMessage & ": " & getStackTrace() 114 let errorMsg = $pCallbackData.pMessage & ": " & getStackTrace()
61 raise newException(Exception, errorMsg) 115 raise newException(Exception, errorMsg)
62 return VK_FALSE 116 return VK_FALSE
63 117
64 proc svkCreateInstance*( 118 proc svkCreateInstance*(
65 applicationName: string, 119 applicationName: string,
66 enabledLayers: openArray[string] = [], 120 enabledLayers: openArray[string] = [],
67 enabledExtensions: openArray[string] = 121 enabledExtensions: openArray[string] =
68 if defined(release): 122 if defined(release):
69 @["VK_KHR_surface"] 123 if defined(windows):
124 @["VK_KHR_surface", "VK_KHR_win32_surface"]
125 else:
126 @["VK_KHR_surface", "VK_KHR_xlib_surface"]
70 else: 127 else:
71 @["VK_KHR_surface", "VK_EXT_debug_utils"], 128 if defined(windows):
129 @["VK_KHR_surface", "VK_EXT_debug_utils", "VK_KHR_win32_surface"]
130 else:
131 @["VK_KHR_surface", "VK_EXT_debug_utils", "VK_KHR_xlib_surface"],
72 engineName = "semicongine", 132 engineName = "semicongine",
73 withSwapchain = true,
74 ): SVkInstance = 133 ): SVkInstance =
75 putEnv("VK_LOADER_LAYERS_ENABLE", "*validation") 134 putEnv("VK_LOADER_LAYERS_ENABLE", "*validation")
76 putEnv( 135 putEnv(
77 "VK_LAYER_ENABLES", 136 "VK_LAYER_ENABLES",
78 "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", 137 "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",
79 ) 138 )
80 initVulkanLoader() 139 initVulkanLoader()
81 140
141 var allLayers = @enabledLayers
142 when not defined(release):
143 allLayers.add "VK_LAYER_KHRONOS_validation"
144
82 let 145 let
83 appinfo = VkApplicationInfo( 146 appinfo = VkApplicationInfo(
84 pApplicationName: applicationName, 147 pApplicationName: applicationName,
85 pEngineName: engineName, 148 pEngineName: engineName,
86 apiVersion: VULKAN_VERSION, 149 apiVersion: VULKAN_VERSION,
87 ) 150 )
88 enabledLayersC = allocCStringArray(enabledLayers) 151 enabledLayersC = allocCStringArray(allLayers)
89 enabledExtensionsC = allocCStringArray(enabledExtensions) 152 enabledExtensionsC = allocCStringArray(enabledExtensions)
90 createinfo = VkInstanceCreateInfo( 153 createinfo = VkInstanceCreateInfo(
91 pApplicationInfo: addr appinfo, 154 pApplicationInfo: addr appinfo,
92 enabledLayerCount: enabledLayers.len.uint32, 155 enabledLayerCount: allLayers.len.uint32,
93 ppEnabledLayerNames: enabledLayersC, 156 ppEnabledLayerNames: enabledLayersC,
94 enabledExtensionCount: enabledExtensions.len.uint32, 157 enabledExtensionCount: enabledExtensions.len.uint32,
95 ppEnabledExtensionNames: enabledExtensionsC, 158 ppEnabledExtensionNames: enabledExtensionsC,
96 ) 159 )
97 checkVkResult vkCreateInstance(addr createinfo, nil, addr result.vkInstance) 160 checkVkResult vkCreateInstance(addr createinfo, nil, addr result.vkInstance)
98 161
99 enabledLayersC.deallocCStringArray() 162 enabledLayersC.deallocCStringArray()
100 enabledExtensionsC.deallocCStringArray() 163 enabledExtensionsC.deallocCStringArray()
101 164
165 # only support up to vulkan 1.2 for maximum portability
102 load_VK_VERSION_1_0(result.vkInstance) 166 load_VK_VERSION_1_0(result.vkInstance)
103 load_VK_VERSION_1_1(result.vkInstance) 167 load_VK_VERSION_1_1(result.vkInstance)
104 load_VK_VERSION_1_2(result.vkInstance) 168 load_VK_VERSION_1_2(result.vkInstance)
105 load_VK_VERSION_1_3(result.vkInstance)
106 169
107 for extension in enabledExtensions: 170 for extension in enabledExtensions:
108 loadExtension(result.vkInstance, extension) 171 loadExtension(result.vkInstance, extension)
109 if withSwapchain: 172 load_VK_KHR_swapchain(result.vkInstance)
110 load_VK_KHR_swapchain(result.vkInstance) 173
111 174 var allTypes: VkDebugUtilsMessageTypeFlagsEXT
175 for t in VkDebugUtilsMessageTypeFlagBitsEXT:
176 allTypes = allTypes or t
112 when not defined(release): 177 when not defined(release):
113 var debugMessengerCreateInfo = VkDebugUtilsMessengerCreateInfoEXT( 178 var debugMessengerCreateInfo = VkDebugUtilsMessengerCreateInfoEXT(
114 messageSeverity: VkDebugUtilsMessageSeverityFlagBitsEXT.items.toSeq, 179 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,
115 messageType: VkDebugUtilsMessageTypeFlagBitsEXT.items.toSeq, 180 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,
116 pfnUserCallback: debugCallback, 181 pfnUserCallback: debugCallback,
117 ) 182 )
118 checkVkResult vkCreateDebugUtilsMessengerEXT( 183 checkVkResult vkCreateDebugUtilsMessengerEXT(
119 result.vkInstance, addr debugMessengerCreateInfo, nil, addr result.debugMessenger 184 result.vkInstance, addr debugMessengerCreateInfo, nil, addr result.debugMessenger
120 ) 185 )
186
187
188 result.window = createWindow(applicationName)
189 when defined(windows):
190 var surfaceCreateInfo = VkWin32SurfaceCreateInfoKHR(hinstance: window.hinstance, hwnd: window.hwnd)
191 checkVkResult vkCreateWin32SurfaceKHR(instance, addr surfaceCreateInfo, nil, addr result.vkSurface)
192 else:
193 var surfaceCreateInfo = VkXlibSurfaceCreateInfoKHR(dpy: result.window.display, window: result.window.window)
194 checkVkResult result.vkInstance.vkCreateXlibSurfaceKHR(addr surfaceCreateInfo, nil, addr result.vkSurface)
195
196
197
198 # =============================================================================
199 # PHYSICAL DEVICES ============================================================
200 # =============================================================================
201
202 type
203 SVkMemoryType* = object
204 size: uint64
205 deviceLocal: bool # fast for gpu access
206 hostCached: bool # fast for host access
207 hostVisible: bool # can use vkMapMemory
208 hostCohorent: bool # does *not* require vkFlushMappedMemoryRanges and vkInvalidateMappedMemoryRanges
209 SVkQueueFamilies* = object
210 count: int
211 hasGraphics: bool # implies "hasTransfer"
212 hasCompute: bool # implies "hasTransfer"
213
214 SVkPhysicalDevice* = object
215 name*: string
216 vkPhysicalDevice*: VkPhysicalDevice
217 vkPhysicalDeviceFeatures*: VkPhysicalDeviceFeatures
218 vkPhysicalDeviceProperties*: VkPhysicalDeviceProperties
219 memoryTypes*: seq[SVkMemoryType]
220 queueFamily*: uint32
221
222 proc getUsablePhysicalDevices*(instance: SVkInstance): seq[SVkPhysicalDevice] =
223 var nDevices: uint32
224 checkVkResult instance.vkInstance.vkEnumeratePhysicalDevices(addr nDevices, nil)
225 var devices = newSeq[VkPhysicalDevice](nDevices)
226 checkVkResult instance.vkInstance.vkEnumeratePhysicalDevices(addr nDevices, addr devices[0])
227 for d in devices:
228 var dev = SVkPhysicalDevice(vkPhysicalDevice: d)
229 d.vkGetPhysicalDeviceFeatures(addr dev.vkPhysicalDeviceFeatures)
230 d.vkGetPhysicalDeviceProperties(addr dev.vkPhysicalDeviceProperties)
231
232 if dev.vkPhysicalDeviceProperties.deviceType notin [VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU, VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU]:
233 continue
234 dev.name = $cast[cstring](addr dev.vkPhysicalDeviceProperties.deviceName[0])
235
236 var memoryProperties: VkPhysicalDeviceMemoryProperties
237 d.vkGetPhysicalDeviceMemoryProperties(addr memoryProperties)
238 for i in 0 ..< memoryProperties.memoryTypeCount:
239 let heapI = memoryProperties.memoryTypes[i].heapIndex
240 dev.memoryTypes.add SVkMemoryType(
241 size: memoryProperties.memoryHeaps[heapI].size.uint64,
242 deviceLocal: VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT in memoryProperties.memoryTypes[i].propertyFlags,
243 hostCached: VK_MEMORY_PROPERTY_HOST_CACHED_BIT in memoryProperties.memoryTypes[i].propertyFlags,
244 hostVisible: VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT in memoryProperties.memoryTypes[i].propertyFlags,
245 hostCohorent: VK_MEMORY_PROPERTY_HOST_COHERENT_BIT in memoryProperties.memoryTypes[i].propertyFlags,
246 )
247
248
249 var familyQueueCount: uint32
250 var vkQueueFamilyProperties: seq[VkQueueFamilyProperties]
251 d.vkGetPhysicalDeviceQueueFamilyProperties(addr familyQueueCount, nil)
252 vkQueueFamilyProperties.setLen(familyQueueCount)
253 d.vkGetPhysicalDeviceQueueFamilyProperties(addr familyQueueCount, addr vkQueueFamilyProperties[0])
254 dev.queueFamily = high(uint32)
255 for i in 0 ..< familyQueueCount:
256 let hasGraphics = VK_QUEUE_GRAPHICS_BIT in vkQueueFamilyProperties[i].queueFlags
257 let hasCompute = VK_QUEUE_COMPUTE_BIT in vkQueueFamilyProperties[i].queueFlags
258 let hasPresentation = VK_FALSE
259 checkVkResult dev.vkPhysicalDevice.vkGetPhysicalDeviceSurfaceSupportKHR(i, instance.vkSurface, addr hasPresentation)
260
261 if hasGraphics and hasCompute and bool(hasPresentation):
262 dev.queueFamily = i
263 break
264 if dev.queueFamily == high(uint32):
265 raise newException(Exception, "Did not find queue family with graphics and compute support!")
266
267 result.add dev
268
269 when defined(windows):
270 proc createWindow(title: string): NativeWindow =
271 result.hInstance = HINSTANCE(GetModuleHandle(nil))
272 var
273 windowClassName = T"EngineWindowClass"
274 windowName = T(title)
275 windowClass = WNDCLASSEX(
276 cbSize: UINT(WNDCLASSEX.sizeof),
277 lpfnWndProc: windowHandler,
278 hInstance: result.hInstance,
279 lpszClassName: windowClassName,
280 hcursor: currentCursor,
281 )
282
283 if (RegisterClassEx(addr(windowClass)) == 0):
284 raise newException(Exception, "Unable to register window class")
285
286 result.hwnd = CreateWindowEx(
287 DWORD(0),
288 windowClassName,
289 windowName,
290 DWORD(WS_OVERLAPPEDWINDOW),
291 CW_USEDEFAULT,
292 CW_USEDEFAULT,
293 CW_USEDEFAULT,
294 CW_USEDEFAULT,
295 HMENU(0),
296 HINSTANCE(0),
297 result.hInstance,
298 nil,
299 )
300
301 result.g_wpPrev.length = UINT(sizeof(WINDOWPLACEMENT))
302 discard result.hwnd.ShowWindow(SW_SHOW)
303 discard result.hwnd.SetForegroundWindow()
304 discard result.hwnd.SetFocus()
305
306 proc destroyWindow*(window: NativeWindow) =
307 DestroyWindow(window.hwnd)
308
309 else:
310 import ../semicongine/thirdparty/x11/xkblib
311 import ../semicongine/thirdparty/x11/xutil
312
313 var deleteMessage* {.hint[GlobalVar]: off.}: x.Atom # one internal use, not serious
314
315 proc XErrorLogger(display: PDisplay, event: PXErrorEvent): cint {.cdecl.} =
316 logging.error "Xlib: " & $event[]
317
318 proc createWindow(title: string): NativeWindow =
319 doAssert XInitThreads() != 0
320 let display = XOpenDisplay(nil)
321 if display == nil:
322 quit "Failed to open display"
323 discard XSetErrorHandler(XErrorLogger)
324
325 let screen = display.XDefaultScreen()
326 let rootWindow = display.XRootWindow(screen)
327 let vis = display.XDefaultVisual(screen)
328 discard display.XkbSetDetectableAutoRepeat(true, nil)
329 var
330 attribs: XWindowAttributes
331 width = cuint(800)
332 height = cuint(600)
333 doAssert display.XGetWindowAttributes(rootWindow, addr(attribs)) != 0
334
335 var attrs = XSetWindowAttributes(
336 event_mask:
337 FocusChangeMask or KeyPressMask or KeyReleaseMask or ExposureMask or
338 VisibilityChangeMask or StructureNotifyMask or ButtonMotionMask or ButtonPressMask or
339 ButtonReleaseMask
340 )
341 let window = display.XCreateWindow(
342 rootWindow,
343 (attribs.width - cint(width)) div 2,
344 (attribs.height - cint(height)) div 2,
345 width,
346 height,
347 0,
348 display.XDefaultDepth(screen),
349 InputOutput,
350 vis,
351 CWEventMask,
352 addr(attrs),
353 )
354 doAssert display.XSetStandardProperties(window, title, "window", 0, nil, 0, nil) != 0
355 # get an input context, to allow encoding of key-events to characters
356
357 let im = XOpenIM(display, nil, nil, nil)
358 assert im != nil
359 let ic = im.XCreateIC(XNInputStyle, XIMPreeditNothing or XIMStatusNothing, nil)
360 assert ic != nil
361
362 doAssert display.XMapWindow(window) != 0
363
364 deleteMessage = display.XInternAtom("WM_DELETE_WINDOW", XBool(false))
365 doAssert display.XSetWMProtocols(window, addr(deleteMessage), 1) != 0
366
367 var data = "\0".cstring
368 var pixmap = display.XCreateBitmapFromData(window, data, 1, 1)
369 var color: XColor
370 var empty_cursor =
371 display.XCreatePixmapCursor(pixmap, pixmap, addr(color), addr(color), 0, 0)
372 doAssert display.XFreePixmap(pixmap) != 0
373
374 discard display.XSync(0)
375
376 # wait until window is shown
377 var ev: XEvent
378 while ev.theType != MapNotify:
379 discard display.XNextEvent(addr(ev))
380
381 result = NativeWindow(display: display, window: window, emptyCursor: empty_cursor, ic: ic)
382
383 proc destroyWindow*(window: NativeWindow) =
384 doAssert XDestroyWindow(window.display, window.window) != 0
385