]> git.tdb.fi Git - libs/gui.git/commitdiff
Add a class for creating Vulkan graphics contexts
authorMikko Rasa <tdb@tdb.fi>
Sat, 20 Nov 2021 10:36:07 +0000 (12:36 +0200)
committerMikko Rasa <tdb@tdb.fi>
Sat, 20 Nov 2021 10:36:07 +0000 (12:36 +0200)
Build
source/graphics/vkxlib/vulkancontext.cpp [new file with mode: 0644]
source/graphics/vkxlib/vulkancontext_platform.h [new file with mode: 0644]
source/graphics/vulkancontext.cpp [new file with mode: 0644]
source/graphics/vulkancontext.h [new file with mode: 0644]

diff --git a/Build b/Build
index d9d7fc73ac6c795094bcc672a74999b253d2d20c..6069ada557da19a75053dd630cb458ee7a255b8a 100644 (file)
--- a/Build
+++ b/Build
@@ -84,6 +84,15 @@ package "mspgui"
                };
        };
 
                };
        };
 
+       feature "vulkan" "Include support for Vulkan contexts"
+       {
+               default "no";
+       };
+       if_feature "vulkan"
+       {
+               require "vulkan";
+       };
+
        if_arch "!windows & !darwin & !android"
        {
                feature "xrandr" "Include support for video mode switching with XRandR"
        if_arch "!windows & !darwin & !android"
        {
                feature "xrandr" "Include support for video mode switching with XRandR"
@@ -155,6 +164,10 @@ package "mspgui"
                        {
                                overlay "glx";
                        };
                        {
                                overlay "glx";
                        };
+                       if_feature "vulkan"
+                       {
+                               overlay "vkxlib";
+                       };
                };
                overlay "generic";
                install true;
                };
                overlay "generic";
                install true;
diff --git a/source/graphics/vkxlib/vulkancontext.cpp b/source/graphics/vkxlib/vulkancontext.cpp
new file mode 100644 (file)
index 0000000..34c0c51
--- /dev/null
@@ -0,0 +1,257 @@
+#include <stdexcept>
+#include <vector>
+#define VK_USE_PLATFORM_XLIB_KHR
+#include <vulkan/vulkan.h>
+#include <msp/core/application.h>
+#include <msp/io/print.h>
+#include "display_private.h"
+#include "vulkancontext.h"
+#include "vulkancontext_platform.h"
+#include "window_private.h"
+
+using namespace std;
+
+namespace Msp {
+namespace Graphics {
+
+string vulkan_error::get_error_message(unsigned code)
+{
+       switch(static_cast<VkResult>(code))
+       {
+       case VK_SUCCESS: return "success";
+       case VK_NOT_READY: return "not ready";
+       case VK_TIMEOUT: return "timeout";
+       case VK_EVENT_SET: return "event set";
+       case VK_EVENT_RESET: return "event reset";
+       case VK_INCOMPLETE: return "incomplete";
+       case VK_ERROR_OUT_OF_HOST_MEMORY: return "out of host memory";
+       case VK_ERROR_OUT_OF_DEVICE_MEMORY: return "out of device memory";
+       case VK_ERROR_INITIALIZATION_FAILED: return "initialization failed";
+       case VK_ERROR_DEVICE_LOST: return "device lost";
+       case VK_ERROR_MEMORY_MAP_FAILED: return "memory map failed";
+       case VK_ERROR_LAYER_NOT_PRESENT: return "layer not present";
+       case VK_ERROR_EXTENSION_NOT_PRESENT: return "extension not present";
+       case VK_ERROR_FEATURE_NOT_PRESENT: return "feature not present";
+       case VK_ERROR_INCOMPATIBLE_DRIVER: return "incompatible driver";
+       case VK_ERROR_TOO_MANY_OBJECTS: return "too many objects";
+       case VK_ERROR_FORMAT_NOT_SUPPORTED: return "format not supported";
+       case VK_ERROR_FRAGMENTED_POOL: return "fragmented pool";
+       case VK_ERROR_UNKNOWN: return "unknown";
+       case VK_ERROR_OUT_OF_POOL_MEMORY: return "out of pool memory";
+       case VK_ERROR_INVALID_EXTERNAL_HANDLE: return "invalidernal handle";
+       case VK_ERROR_FRAGMENTATION: return "fragmentation";
+       case VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS: return "invalid opaque capture address";
+       case VK_ERROR_SURFACE_LOST_KHR: return "surface lost";
+       case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR: return "native window in use";
+       case VK_SUBOPTIMAL_KHR: return "suboptimal";
+       case VK_ERROR_OUT_OF_DATE_KHR: return "out of date";
+       case VK_ERROR_INCOMPATIBLE_DISPLAY_KHR: return "incompatible display";
+       case VK_ERROR_VALIDATION_FAILED_EXT: return "validation failed";
+       case VK_ERROR_INVALID_SHADER_NV: return "invalid shader";
+       case VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT: return "invalid drm format modifier plane layout";
+       case VK_ERROR_NOT_PERMITTED_EXT: return "not permitted";
+       case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT: return "full screen exclusive mode lost";
+       case VK_THREAD_IDLE_KHR: return "thread idle";
+       case VK_THREAD_DONE_KHR: return "thread done";
+       case VK_OPERATION_DEFERRED_KHR: return "operation deferred";
+       case VK_OPERATION_NOT_DEFERRED_KHR: return "operation not deferred";
+       case VK_PIPELINE_COMPILE_REQUIRED_EXT: return "pipeline compile required";
+       default: return format("VkResult(%d)", code);
+       }
+}
+
+VulkanOptions::VulkanOptions():
+       enable_validation(false),
+       enable_debug_report(false)
+{ }
+
+
+void VulkanContext::platform_init(const VulkanOptions &opts)
+{
+       priv = new Private;
+       VulkanFunctions &f = priv->functions;
+
+       try
+       {
+               f.vkCreateInstance = get_function<PFN_vkCreateInstance>("vkCreateInstance");
+
+               vector<const char *> layers;
+               if(opts.enable_validation)
+                       layers.push_back("VK_LAYER_KHRONOS_validation");
+
+               vector<const char *> extensions;
+               extensions.push_back("VK_KHR_surface");
+               extensions.push_back("VK_KHR_xlib_surface");
+               extensions.push_back("VK_EXT_debug_utils");
+               if(opts.enable_debug_report)
+                       extensions.push_back("VK_EXT_debug_report");
+
+               VkApplicationInfo app_info = { };
+               app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
+               app_info.pApplicationName = Application::get_name().c_str();
+               app_info.pEngineName = "MSP";
+               app_info.apiVersion = (1<<22)|(2<<12);
+
+               VkInstanceCreateInfo instance_create_info = { };
+               instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+               instance_create_info.pApplicationInfo = &app_info;
+               instance_create_info.enabledLayerCount = layers.size();
+               instance_create_info.ppEnabledLayerNames = layers.data();
+               instance_create_info.enabledExtensionCount = extensions.size();
+               instance_create_info.ppEnabledExtensionNames = extensions.data();
+
+               VkResult result = f.vkCreateInstance(&instance_create_info, 0, &priv->instance);
+               if(result!=VK_SUCCESS)
+                       throw vulkan_error(result, "vkCreateInstance");
+
+               f.vkDestroyInstance = get_function<PFN_vkDestroyInstance>("vkDestroyInstance");
+               f.vkEnumeratePhysicalDevices = get_function<PFN_vkEnumeratePhysicalDevices>("vkEnumeratePhysicalDevices");
+               f.vkGetPhysicalDeviceQueueFamilyProperties = get_function<PFN_vkGetPhysicalDeviceQueueFamilyProperties>("vkGetPhysicalDeviceQueueFamilyProperties");
+               f.vkGetPhysicalDeviceSurfaceSupport = get_function<PFN_vkGetPhysicalDeviceSurfaceSupportKHR>("vkGetPhysicalDeviceSurfaceSupportKHR");
+               f.vkCreateDevice = get_function<PFN_vkCreateDevice>("vkCreateDevice");
+               f.vkDestroyDevice = get_function<PFN_vkDestroyDevice>("vkDestroyDevice");
+               f.vkGetDeviceQueue = get_function<PFN_vkGetDeviceQueue>("vkGetDeviceQueue");
+               f.vkCreateXlibSurface = get_function<PFN_vkCreateXlibSurfaceKHR>("vkCreateXlibSurfaceKHR");
+               f.vkDestroySurface = get_function<PFN_vkDestroySurfaceKHR>("vkDestroySurfaceKHR");
+               f.vkCreateDebugReportCallback = get_function<PFN_vkCreateDebugReportCallbackEXT>("vkCreateDebugReportCallbackEXT");
+               f.vkDestroyDebugReportCallback = get_function<PFN_vkDestroyDebugReportCallbackEXT>("vkDestroyDebugReportCallbackEXT");
+
+               if(opts.enable_debug_report)
+               {
+                       VkDebugReportCallbackCreateInfoEXT debug_report_create_info = { };
+                       debug_report_create_info.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;
+                       debug_report_create_info.flags = VK_DEBUG_REPORT_WARNING_BIT_EXT|VK_DEBUG_REPORT_ERROR_BIT_EXT|VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT;
+                       debug_report_create_info.pfnCallback = &Private::debug_report_func;
+                       debug_report_create_info.pUserData = this;
+
+                       result = f.vkCreateDebugReportCallback(priv->instance, &debug_report_create_info, 0, &priv->debug_report_callback);
+                       if(result!=VK_SUCCESS)
+                               throw vulkan_error(result, "vkCreateDebugReportCallback");
+               }
+               else
+                       priv->debug_report_callback = 0;
+
+               VkXlibSurfaceCreateInfoKHR surface_create_info = { };
+               surface_create_info.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR;
+               surface_create_info.dpy = display.get_private().display;
+               surface_create_info.window = window.get_private().window;
+
+               result = f.vkCreateXlibSurface(priv->instance, &surface_create_info, 0, &priv->surface);
+               if(result!=VK_SUCCESS)
+                       throw vulkan_error(result, "vkCreateXlibSurface");
+
+               unsigned n_phys_devices = 0;
+               result = f.vkEnumeratePhysicalDevices(priv->instance, &n_phys_devices, 0);
+               if(result!=VK_SUCCESS)
+                       throw vulkan_error(result, "vkEnumeratePhysicalDevices");
+               else if(!n_phys_devices)
+                       throw runtime_error("no physical device found");
+               vector<VkPhysicalDevice> phys_devices(n_phys_devices);
+               result = f.vkEnumeratePhysicalDevices(priv->instance, &n_phys_devices, &phys_devices[0]);
+               if(result!=VK_SUCCESS)
+                       throw vulkan_error(result, "vkEnumeratePhysicalDevices");
+
+               priv->physical_device = 0;
+               unsigned gfx_queue_index = 0;
+               for(unsigned i=0; i<n_phys_devices; ++i)
+               {
+                       VkPhysicalDevice phys_device = phys_devices[i];
+
+                       unsigned n_queue_families = 0;
+                       f.vkGetPhysicalDeviceQueueFamilyProperties(phys_device, &n_queue_families, 0);
+                       vector<VkQueueFamilyProperties> queue_family_props(n_queue_families);
+                       f.vkGetPhysicalDeviceQueueFamilyProperties(phys_device, &n_queue_families, queue_family_props.data());
+
+                       for(; gfx_queue_index<n_queue_families; ++gfx_queue_index)
+                               if((queue_family_props[gfx_queue_index].queueFlags&VK_QUEUE_GRAPHICS_BIT))
+                                       break;
+
+                       if(gfx_queue_index>=n_queue_families)
+                               continue;
+
+                       VkBool32 supported = VK_FALSE;
+                       result = f.vkGetPhysicalDeviceSurfaceSupport(phys_device, gfx_queue_index, priv->surface, &supported);
+                       if(result!=VK_SUCCESS)
+                               continue;
+
+                       if(supported)
+                               priv->physical_device = phys_devices[0];
+               }
+
+               if(!priv->physical_device)
+                       throw runtime_error("no usable physical devices found");
+
+               priv->graphics_queue_family = gfx_queue_index;
+
+               float queue_priority = 1.0f;
+               VkDeviceQueueCreateInfo queue_create_info = { };
+               queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+               queue_create_info.queueFamilyIndex = gfx_queue_index;
+               queue_create_info.queueCount = 1;
+               queue_create_info.pQueuePriorities = &queue_priority;
+
+               extensions.clear();
+               extensions.push_back("VK_KHR_swapchain");
+
+               VkDeviceCreateInfo device_create_info = { };
+               device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
+               device_create_info.queueCreateInfoCount = 1;
+               device_create_info.pQueueCreateInfos = &queue_create_info;
+               device_create_info.enabledExtensionCount = extensions.size();
+               device_create_info.ppEnabledExtensionNames = extensions.data();
+
+               result = f.vkCreateDevice(priv->physical_device, &device_create_info, 0, &priv->device);
+               if(result!=VK_SUCCESS)
+                       throw vulkan_error(result, "vkCreateDevice");
+
+               f.vkGetDeviceQueue(priv->device, gfx_queue_index, 0, &priv->graphics_queue);
+       }
+       catch(...)
+       {
+               if(priv->device)
+                       f.vkDestroyDevice(priv->device, 0);
+               if(priv->surface)
+                       f.vkDestroySurface(priv->instance, priv->surface, 0);
+               if(priv->debug_report_callback)
+                       f.vkDestroyDebugReportCallback(priv->instance, priv->debug_report_callback, 0);
+               if(priv->instance)
+                       f.vkDestroyInstance(priv->instance, 0);
+               delete priv;
+               throw;
+       }
+}
+
+VulkanContext::~VulkanContext()
+{
+       const VulkanFunctions &f = priv->functions;
+       f.vkDestroyDevice(priv->device, 0);
+       f.vkDestroySurface(priv->instance, priv->surface, 0);
+       if(priv->debug_report_callback)
+               f.vkDestroyDebugReportCallback(priv->instance, priv->debug_report_callback, 0);
+       f.vkDestroyInstance(priv->instance, 0);
+       delete priv;
+}
+
+void (*VulkanContext::_get_function(const std::string &name) const)()
+{
+       return vkGetInstanceProcAddr(priv->instance, name.c_str());
+}
+
+
+VulkanContext::Private::Private():
+       instance(0),
+       physical_device(0),
+       device(0),
+       graphics_queue(0),
+       debug_report_callback(0)
+{ }
+
+VkBool32 VulkanContext::Private::debug_report_func(VkDebugReportFlagsEXT, VkDebugReportObjectTypeEXT obj_type, uint64_t obj_id, size_t, int32_t, const char *layer_prefix, const char *message, void *)
+{
+       IO::print(IO::cerr, "Vulkan debug report from %s: Object %d of type %d: %s\n", layer_prefix, obj_type, obj_id, message);
+       return VK_FALSE;
+}
+
+} // namespace Graphics
+} // namespace Msp
+
diff --git a/source/graphics/vkxlib/vulkancontext_platform.h b/source/graphics/vkxlib/vulkancontext_platform.h
new file mode 100644 (file)
index 0000000..9c08c96
--- /dev/null
@@ -0,0 +1,46 @@
+#ifndef MSP_GRAPHICS_VULKANCONTEXT_PLATFORM_H_
+#define MSP_GRAPHICS_VULKANCONTEXT_PLATFORM_H_
+
+#define VK_USE_PLATFORM_XLIB_KHR
+#include <vulkan/vulkan.h>
+#include "vulkancontext.h"
+
+namespace Msp {
+namespace Graphics {
+
+struct VulkanFunctions
+{
+       PFN_vkCreateInstance vkCreateInstance;
+       PFN_vkDestroyInstance vkDestroyInstance;
+       PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices;
+       PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties;
+       PFN_vkGetPhysicalDeviceSurfaceSupportKHR vkGetPhysicalDeviceSurfaceSupport;
+       PFN_vkCreateDevice vkCreateDevice;
+       PFN_vkDestroyDevice vkDestroyDevice;
+       PFN_vkGetDeviceQueue vkGetDeviceQueue;
+       PFN_vkCreateXlibSurfaceKHR vkCreateXlibSurface;
+       PFN_vkDestroySurfaceKHR vkDestroySurface;
+       PFN_vkCreateDebugReportCallbackEXT vkCreateDebugReportCallback;
+       PFN_vkDestroyDebugReportCallbackEXT vkDestroyDebugReportCallback;
+};
+
+struct VulkanContext::Private
+{
+       VulkanFunctions functions;
+       VkInstance instance;
+       VkPhysicalDevice physical_device;
+       VkDevice device;
+       unsigned graphics_queue_family;
+       VkQueue graphics_queue;
+       VkSurfaceKHR surface;
+       VkDebugReportCallbackEXT debug_report_callback;
+
+       Private();
+
+       static VkBool32 debug_report_func(VkDebugReportFlagsEXT, VkDebugReportObjectTypeEXT, uint64_t, size_t, int32_t, const char *, const char *, void *);
+};
+
+} // namespace Graphics
+} // namespace Msp
+
+#endif
diff --git a/source/graphics/vulkancontext.cpp b/source/graphics/vulkancontext.cpp
new file mode 100644 (file)
index 0000000..bbf03e3
--- /dev/null
@@ -0,0 +1,36 @@
+#include <msp/strings/format.h>
+#include "vulkancontext.h"
+#include "window.h"
+
+namespace Msp {
+namespace Graphics {
+
+vulkan_error::vulkan_error(unsigned code, const char *function):
+       runtime_error(format("%s failed: %s", function, get_error_message(code)))
+{ }
+
+
+VulkanContext::VulkanContext(Window &w, const VulkanOptions &opts):
+       display(w.get_display()),
+       window(w)
+{
+       platform_init(opts);
+}
+
+#ifndef WITH_VULKAN
+void VulkanContext::platform_init(const VulkanOptions &)
+{
+       throw runtime_error("no Vulkan support");
+}
+
+VulkanContext::~VulkanContext()
+{ }
+
+void (*VulkanContext::_get_function(const std::string &) const)()
+{
+       return 0;
+}
+#endif
+
+} // namespace Graphics
+} // namespace Msp
diff --git a/source/graphics/vulkancontext.h b/source/graphics/vulkancontext.h
new file mode 100644 (file)
index 0000000..44ac4a5
--- /dev/null
@@ -0,0 +1,61 @@
+#ifndef MSP_GRAPHICS_VULKANCONTEXT_H_
+#define MSP_GRAPHICS_VULKANCONTEXT_H_
+
+#include <stdexcept>
+#include <string>
+
+namespace Msp {
+namespace Graphics {
+
+class Display;
+class Window;
+
+class vulkan_error: public std::runtime_error
+{
+public:
+       vulkan_error(unsigned, const char *);
+       virtual ~vulkan_error() throw() { }
+
+private:
+       static std::string get_error_message(unsigned);
+};
+
+struct VulkanOptions
+{
+       bool enable_validation;
+       bool enable_debug_report;
+
+       VulkanOptions();
+};
+
+class VulkanContext
+{
+private:
+       struct Private;
+
+       Display &display;
+       Window &window;
+       Private *priv;
+
+public:
+       VulkanContext(Window &, const VulkanOptions & = VulkanOptions());
+private:
+       void platform_init(const VulkanOptions &);
+public:
+       ~VulkanContext();
+
+       template<typename T>
+       T get_function(const std::string &name) const
+       { return reinterpret_cast<T>(_get_function(name)); }
+
+private:
+       void (*_get_function(const std::string &) const)();
+
+public:
+       const Private &get_private() const { return *priv; }
+};
+
+} // namespace Graphics
+} // namespace Msp
+
+#endif