Mercurial > games > semicongine
diff src/zamikongine/vulkan_helpers.nim @ 480:14e5151f68d1
did: introduce scene graph, meshs and generic vertex buffers
author | Sam <sam@basx.dev> |
---|---|
date | Mon, 09 Jan 2023 11:04:19 +0700 |
parents | |
children | 73a0954beabd |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/zamikongine/vulkan_helpers.nim Mon Jan 09 11:04:19 2023 +0700 @@ -0,0 +1,245 @@ +import std/tables +import std/strutils +import std/strformat +import std/logging +import std/macros + +import ./vulkan +import ./window + + +const ENABLEVULKANVALIDATIONLAYERS* = not defined(release) + + +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) + +func addrOrNil[T](obj: var openArray[T]): ptr T = + if obj.len > 0: addr(obj[0]) else: nil + +func VK_MAKE_API_VERSION*(variant: uint32, major: uint32, minor: uint32, patch: uint32): uint32 {.compileTime.} = + (variant shl 29) or (major shl 22) or (minor shl 12) or patch + + +func filterForSurfaceFormat*(formats: seq[VkSurfaceFormatKHR]): seq[VkSurfaceFormatKHR] = + for format in formats: + if format.format == VK_FORMAT_B8G8R8A8_SRGB and format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR: + result.add(format) + +func getSuitableSurfaceFormat*(formats: seq[VkSurfaceFormatKHR]): VkSurfaceFormatKHR = + let usableSurfaceFormats = filterForSurfaceFormat(formats) + if len(usableSurfaceFormats) == 0: + raise newException(Exception, "No suitable surface formats found") + return usableSurfaceFormats[0] + + +func cleanString*(str: openArray[char]): string = + for i in 0 ..< len(str): + if str[i] == char(0): + result = join(str[0 ..< i]) + break + +proc getInstanceExtensions*(): seq[string] = + var extensionCount: uint32 + checkVkResult vkEnumerateInstanceExtensionProperties(nil, addr(extensionCount), nil) + var extensions = newSeq[VkExtensionProperties](extensionCount) + checkVkResult vkEnumerateInstanceExtensionProperties(nil, addr(extensionCount), addrOrNil(extensions)) + + for extension in extensions: + result.add(cleanString(extension.extensionName)) + + +proc getDeviceExtensions*(device: VkPhysicalDevice): seq[string] = + var extensionCount: uint32 + checkVkResult vkEnumerateDeviceExtensionProperties(device, nil, addr(extensionCount), nil) + var extensions = newSeq[VkExtensionProperties](extensionCount) + checkVkResult vkEnumerateDeviceExtensionProperties(device, nil, addr(extensionCount), addrOrNil(extensions)) + + for extension in extensions: + result.add(cleanString(extension.extensionName)) + + +proc getValidationLayers*(): seq[string] = + var n_layers: uint32 + checkVkResult vkEnumerateInstanceLayerProperties(addr(n_layers), nil) + var layers = newSeq[VkLayerProperties](n_layers) + checkVkResult vkEnumerateInstanceLayerProperties(addr(n_layers), addrOrNil(layers)) + + for layer in layers: + result.add(cleanString(layer.layerName)) + + +proc getVulkanPhysicalDevices*(instance: VkInstance): seq[VkPhysicalDevice] = + var n_devices: uint32 + checkVkResult vkEnumeratePhysicalDevices(instance, addr(n_devices), nil) + result = newSeq[VkPhysicalDevice](n_devices) + checkVkResult vkEnumeratePhysicalDevices(instance, addr(n_devices), addrOrNil(result)) + + +proc getQueueFamilies*(device: VkPhysicalDevice): seq[VkQueueFamilyProperties] = + var n_queuefamilies: uint32 + vkGetPhysicalDeviceQueueFamilyProperties(device, addr(n_queuefamilies), nil) + result = newSeq[VkQueueFamilyProperties](n_queuefamilies) + vkGetPhysicalDeviceQueueFamilyProperties(device, addr(n_queuefamilies), addrOrNil(result)) + + +proc getDeviceSurfaceFormats*(device: VkPhysicalDevice, surface: VkSurfaceKHR): seq[VkSurfaceFormatKHR] = + var n_formats: uint32 + checkVkResult vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, addr(n_formats), nil); + result = newSeq[VkSurfaceFormatKHR](n_formats) + checkVkResult vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, addr(n_formats), addrOrNil(result)) + + +proc getDeviceSurfacePresentModes*(device: VkPhysicalDevice, surface: VkSurfaceKHR): seq[VkPresentModeKHR] = + var n_modes: uint32 + checkVkResult vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, addr(n_modes), nil); + result = newSeq[VkPresentModeKHR](n_modes) + checkVkResult vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, addr(n_modes), addrOrNil(result)) + + +proc getSwapChainImages*(device: VkDevice, swapChain: VkSwapchainKHR): seq[VkImage] = + var n_images: uint32 + checkVkResult vkGetSwapchainImagesKHR(device, swapChain, addr(n_images), nil); + result = newSeq[VkImage](n_images) + checkVkResult vkGetSwapchainImagesKHR(device, swapChain, addr(n_images), addrOrNil(result)); + + +func getPresentMode*(modes: seq[VkPresentModeKHR]): VkPresentModeKHR = + let preferredModes = [ + VK_PRESENT_MODE_MAILBOX_KHR, # triple buffering + VK_PRESENT_MODE_FIFO_RELAXED_KHR, # double duffering + VK_PRESENT_MODE_FIFO_KHR, # double duffering + VK_PRESENT_MODE_IMMEDIATE_KHR, # single buffering + ] + for preferredMode in preferredModes: + for mode in modes: + if preferredMode == mode: + return mode + # should never be reached, but seems to be garuanteed by vulkan specs to always be available + return VK_PRESENT_MODE_FIFO_KHR + + +proc createVulkanInstance*(vulkanVersion: uint32): VkInstance = + + var requiredExtensions = @["VK_KHR_surface".cstring] + when defined(linux): + requiredExtensions.add("VK_KHR_xlib_surface".cstring) + when defined(windows): + requiredExtensions.add("VK_KHR_win32_surface".cstring) + when ENABLEVULKANVALIDATIONLAYERS: + requiredExtensions.add("VK_EXT_debug_utils".cstring) + + let availableExtensions = getInstanceExtensions() + for extension in requiredExtensions: + assert $extension in availableExtensions, $extension + + let availableLayers = getValidationLayers() + var usableLayers = newSeq[cstring]() + + when ENABLEVULKANVALIDATIONLAYERS: + const desiredLayers = ["VK_LAYER_KHRONOS_validation".cstring, "VK_LAYER_MESA_overlay".cstring] + for layer in desiredLayers: + if $layer in availableLayers: + usableLayers.add(layer) + + echo "Available validation layers: ", availableLayers + echo "Using validation layers: ", usableLayers + echo "Available extensions: ", availableExtensions + echo "Using extensions: ", requiredExtensions + + var appinfo = VkApplicationInfo( + sType: VK_STRUCTURE_TYPE_APPLICATION_INFO, + pApplicationName: "Hello Triangle", + pEngineName: "Custom engine", + apiVersion: vulkanVersion, + ) + var createinfo = VkInstanceCreateInfo( + sType: VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + pApplicationInfo: addr(appinfo), + enabledLayerCount: usableLayers.len.uint32, + ppEnabledLayerNames: cast[ptr UncheckedArray[cstring]](addrOrNil(usableLayers)), + enabledExtensionCount: requiredExtensions.len.uint32, + ppEnabledExtensionNames: cast[ptr UncheckedArray[cstring]](addr(requiredExtensions[0])) + ) + checkVkResult vkCreateInstance(addr(createinfo), nil, addr(result)) + + loadVK_KHR_surface() + when defined(linux): + loadVK_KHR_xlib_surface() + when defined(windows): + loadVK_KHR_win32_surface() + loadVK_KHR_swapchain() + when ENABLEVULKANVALIDATIONLAYERS: + loadVK_EXT_debug_utils(result) + + +proc getVulcanDevice*( + physicalDevice: var VkPhysicalDevice, + features: var VkPhysicalDeviceFeatures, + graphicsQueueFamily: uint32, + presentationQueueFamily: uint32, +): (VkDevice, VkQueue, VkQueue) = + # setup queue and device + # TODO: need check this, possibly wrong logic, see Vulkan tutorial + var priority = 1.0'f32 + var queueCreateInfo = [ + VkDeviceQueueCreateInfo( + sType: VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + queueFamilyIndex: graphicsQueueFamily, + queueCount: 1, + pQueuePriorities: addr(priority), + ), + ] + + var requiredExtensions = ["VK_KHR_swapchain".cstring] + var deviceCreateInfo = VkDeviceCreateInfo( + sType: VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + queueCreateInfoCount: uint32(queueCreateInfo.len), + pQueueCreateInfos: addrOrNil(queueCreateInfo), + pEnabledFeatures: addr(features), + enabledExtensionCount: requiredExtensions.len.uint32, + ppEnabledExtensionNames: cast[ptr UncheckedArray[cstring]](addr(requiredExtensions)) + ) + checkVkResult vkCreateDevice(physicalDevice, addr(deviceCreateInfo), nil, addr(result[0])) + vkGetDeviceQueue(result[0], graphicsQueueFamily, 0'u32, addr(result[1])); + vkGetDeviceQueue(result[0], presentationQueueFamily, 0'u32, addr(result[2])); + +proc debugCallback*( + messageSeverity: VkDebugUtilsMessageSeverityFlagBitsEXT, + messageTypes: VkDebugUtilsMessageTypeFlagsEXT, + pCallbackData: VkDebugUtilsMessengerCallbackDataEXT, + userData: pointer +): VkBool32 {.cdecl.} = + echo &"{messageSeverity}: {VkDebugUtilsMessageTypeFlagBitsEXT(messageTypes)}: {pCallbackData.pMessage}" + return VK_FALSE + +proc getSurfaceCapabilities*(device: VkPhysicalDevice, surface: VkSurfaceKHR): VkSurfaceCapabilitiesKHR = + checkVkResult device.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(surface, addr(result)) + +when defined(linux): + proc createVulkanSurface*(instance: VkInstance, window: NativeWindow): VkSurfaceKHR = + var surfaceCreateInfo = VkXlibSurfaceCreateInfoKHR( + sType: VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, + dpy: window.display, + window: window.window, + ) + checkVkResult vkCreateXlibSurfaceKHR(instance, addr(surfaceCreateInfo), nil, addr(result)) +when defined(windows): + proc createVulkanSurface*(instance: VkInstance, window: NativeWindow): VkSurfaceKHR = + var surfaceCreateInfo = VkWin32SurfaceCreateInfoKHR( + sType: VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, + hinstance: window.hinstance, + hwnd: window.hwnd, + ) + checkVkResult vkCreateWin32SurfaceKHR(instance, addr(surfaceCreateInfo), nil, addr(result))