1488
|
1 import std/sequtils
|
|
2 import std/enumutils
|
1487
|
3 import std/tables
|
1485
|
4 import std/strutils
|
|
5 import std/logging
|
1486
|
6 import std/os
|
1485
|
7
|
|
8 include ./vkapi
|
|
9
|
1489
|
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)
|
1486
|
31
|
1488
|
32 iterator items*[T: HoleyEnum](E: typedesc[T]): T =
|
|
33 for a in enumFullRange(E):
|
|
34 yield a
|
|
35
|
1485
|
36 template checkVkResult*(call: untyped) =
|
|
37 when defined(release):
|
|
38 discard call
|
|
39 else:
|
|
40 # yes, a bit cheap, but this is only for nice debug output
|
|
41 var callstr = astToStr(call).replace("\n", "")
|
|
42 while callstr.find(" ") >= 0:
|
|
43 callstr = callstr.replace(" ", " ")
|
|
44 debug "Calling vulkan: ", callstr
|
|
45 let value = call
|
|
46 if value != VK_SUCCESS:
|
|
47 error "Vulkan error: ", astToStr(call), " returned ", $value
|
|
48 raise newException(
|
|
49 Exception, "Vulkan error: " & astToStr(call) & " returned " & $value
|
|
50 )
|
|
51
|
1489
|
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
|
1485
|
75 type SVkInstance* = object
|
|
76 vkInstance: VkInstance
|
1486
|
77 debugMessenger: VkDebugUtilsMessengerEXT
|
1489
|
78 window: NativeWindow
|
|
79 vkSurface*: VkSurfaceKHR
|
|
80
|
|
81 proc createWindow(title: string): NativeWindow
|
1485
|
82
|
|
83 proc `=copy`(a: var SVkInstance, b: SVkInstance) {.error.}
|
|
84
|
|
85 proc `=destroy`(a: SVkInstance) =
|
1489
|
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) =
|
1485
|
91 if a.vkInstance.pointer != nil:
|
1486
|
92 if a.debugMessenger.pointer != nil:
|
|
93 vkDestroyDebugUtilsMessengerEXT(a.vkInstance, a.debugMessenger, nil)
|
1489
|
94 a.debugMessenger = VkDebugUtilsMessengerEXT(nil)
|
|
95 vkDestroySurfaceKHR(a.vkInstance, a.vkSurface, nil)
|
|
96 a.vkSurface = VkSurfaceKHR(nil)
|
1485
|
97 a.vkInstance.vkDestroyInstance(nil)
|
1489
|
98 a.vkInstance = VkInstance(nil)
|
1485
|
99
|
1486
|
100 proc debugCallback(
|
|
101 messageSeverity: VkDebugUtilsMessageSeverityFlagBitsEXT,
|
|
102 messageTypes: VkDebugUtilsMessageTypeFlagsEXT,
|
|
103 pCallbackData: ptr VkDebugUtilsMessengerCallbackDataEXT,
|
|
104 userData: pointer,
|
|
105 ): VkBool32 {.cdecl.} =
|
|
106 const LOG_LEVEL_MAPPING = {
|
|
107 VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: lvlDebug,
|
|
108 VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: lvlInfo,
|
|
109 VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: lvlWarn,
|
|
110 VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: lvlError,
|
|
111 }.toTable
|
1489
|
112 log LOG_LEVEL_MAPPING[messageSeverity], "SVK-LOG: ", $pCallbackData.pMessage
|
1486
|
113 if messageSeverity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:
|
|
114 let errorMsg = $pCallbackData.pMessage & ": " & getStackTrace()
|
|
115 raise newException(Exception, errorMsg)
|
|
116 return VK_FALSE
|
|
117
|
1485
|
118 proc svkCreateInstance*(
|
|
119 applicationName: string,
|
|
120 enabledLayers: openArray[string] = [],
|
1486
|
121 enabledExtensions: openArray[string] =
|
|
122 if defined(release):
|
1489
|
123 if defined(windows):
|
|
124 @["VK_KHR_surface", "VK_KHR_win32_surface"]
|
|
125 else:
|
|
126 @["VK_KHR_surface", "VK_KHR_xlib_surface"]
|
1486
|
127 else:
|
1489
|
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"],
|
1485
|
132 engineName = "semicongine",
|
|
133 ): SVkInstance =
|
1486
|
134 putEnv("VK_LOADER_LAYERS_ENABLE", "*validation")
|
|
135 putEnv(
|
|
136 "VK_LAYER_ENABLES",
|
|
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",
|
|
138 )
|
|
139 initVulkanLoader()
|
|
140
|
1489
|
141 var allLayers = @enabledLayers
|
|
142 when not defined(release):
|
|
143 allLayers.add "VK_LAYER_KHRONOS_validation"
|
|
144
|
1485
|
145 let
|
|
146 appinfo = VkApplicationInfo(
|
|
147 pApplicationName: applicationName,
|
|
148 pEngineName: engineName,
|
1486
|
149 apiVersion: VULKAN_VERSION,
|
1485
|
150 )
|
1489
|
151 enabledLayersC = allocCStringArray(allLayers)
|
1486
|
152 enabledExtensionsC = allocCStringArray(enabledExtensions)
|
1485
|
153 createinfo = VkInstanceCreateInfo(
|
|
154 pApplicationInfo: addr appinfo,
|
1489
|
155 enabledLayerCount: allLayers.len.uint32,
|
1486
|
156 ppEnabledLayerNames: enabledLayersC,
|
1485
|
157 enabledExtensionCount: enabledExtensions.len.uint32,
|
1486
|
158 ppEnabledExtensionNames: enabledExtensionsC,
|
1485
|
159 )
|
|
160 checkVkResult vkCreateInstance(addr createinfo, nil, addr result.vkInstance)
|
1486
|
161
|
|
162 enabledLayersC.deallocCStringArray()
|
|
163 enabledExtensionsC.deallocCStringArray()
|
|
164
|
1489
|
165 # only support up to vulkan 1.2 for maximum portability
|
1486
|
166 load_VK_VERSION_1_0(result.vkInstance)
|
|
167 load_VK_VERSION_1_1(result.vkInstance)
|
|
168 load_VK_VERSION_1_2(result.vkInstance)
|
|
169
|
|
170 for extension in enabledExtensions:
|
|
171 loadExtension(result.vkInstance, extension)
|
1489
|
172 load_VK_KHR_swapchain(result.vkInstance)
|
1486
|
173
|
1489
|
174 var allTypes: VkDebugUtilsMessageTypeFlagsEXT
|
|
175 for t in VkDebugUtilsMessageTypeFlagBitsEXT:
|
|
176 allTypes = allTypes or t
|
1486
|
177 when not defined(release):
|
|
178 var debugMessengerCreateInfo = VkDebugUtilsMessengerCreateInfoEXT(
|
1489
|
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,
|
|
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,
|
1486
|
181 pfnUserCallback: debugCallback,
|
|
182 )
|
|
183 checkVkResult vkCreateDebugUtilsMessengerEXT(
|
|
184 result.vkInstance, addr debugMessengerCreateInfo, nil, addr result.debugMessenger
|
|
185 )
|
1489
|
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
|