diff --git a/include/mbgl/vulkan/renderer_backend.hpp b/include/mbgl/vulkan/renderer_backend.hpp index b3283088d..f1c643eaf 100644 --- a/include/mbgl/vulkan/renderer_backend.hpp +++ b/include/mbgl/vulkan/renderer_backend.hpp @@ -84,16 +84,16 @@ protected: virtual std::vector getDeviceExtensions(); std::vector getDebugExtensions(); - void initInstance(); - void initDebug(); - void initSurface(); - void initDevice(); - void initAllocator(); - void initSwapchain(); - void initCommandPool(); - void initFrameCapture(); + virtual void initInstance(); + virtual void initDebug(); + virtual void initSurface(); + virtual void initDevice(); + virtual void initAllocator(); + virtual void initSwapchain(); + virtual void initCommandPool(); + virtual void initFrameCapture(); - void destroyResources(); + virtual void destroyResources(); protected: vk::DynamicLoader dynamicLoader; @@ -120,6 +120,7 @@ protected: VmaAllocator allocator; bool debugUtilsEnabled{false}; + bool usingSharedContext{false}; }; } // namespace vulkan diff --git a/platform/glfw/glfw_vulkan_backend.cpp b/platform/glfw/glfw_vulkan_backend.cpp index 25e736c4c..ce87933c1 100644 --- a/platform/glfw/glfw_vulkan_backend.cpp +++ b/platform/glfw/glfw_vulkan_backend.cpp @@ -11,6 +11,113 @@ #define GLFW_INCLUDE_VULKAN #include +#ifdef USE_SHARED_VK_CONTEXT + +// An example of an external vkInstance/vkDevice provider. +// It uses the first available device and queue +class VkContext { +public: + VkContext() = default; + ~VkContext() { + device.reset(); + instance.reset(); + } + + static VkContext& shared() { + static std::unique_ptr context; + if (!context) { + context = std::make_unique(); + } + + return *context; + } + + vk::Instance getInstance() { + if (instance) { + return instance.get(); + } + + dispatcher.init(dynamicLoader); + +#ifdef __APPLE__ + vk::InstanceCreateFlags instanceFlags = vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR; +#else + vk::InstanceCreateFlags instanceFlags = {}; +#endif + + const std::vector layers = {"VK_LAYER_KHRONOS_validation"}; + std::vector extensions = { + VK_EXT_DEBUG_UTILS_EXTENSION_NAME, +#ifdef __APPLE__ + "VK_KHR_portability_enumeration", +#endif + }; + + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + std::copy(glfwExtensions, glfwExtensions + glfwExtensionCount, std::back_inserter(extensions)); + + vk::ApplicationInfo appInfo("maplibre-native", 1, "maplibre-native", 1, VK_API_VERSION_1_0); + const auto createInfo = vk::InstanceCreateInfo(instanceFlags) + .setPApplicationInfo(&appInfo) + .setPEnabledExtensionNames(extensions) + .setPEnabledLayerNames(layers); + + instance = vk::createInstanceUnique(createInfo, nullptr, dispatcher); + dispatcher.init(instance.get()); + + return instance.get(); + } + + vk::PhysicalDevice getPhysicalDevice() { + if (!physicalDevice) { + physicalDevice = instance->enumeratePhysicalDevices(dispatcher)[0]; + } + + return physicalDevice; + } + + vk::Device getDevice() { + if (device) { + return device.get(); + } + + const std::vector layers = {"VK_LAYER_KHRONOS_validation"}; + const std::vector extensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, +#ifdef __APPLE__ + "VK_KHR_portability_subset", +#endif + }; + + float queuePriority = 1.0f; + vk::DeviceQueueCreateInfo queueCreateInfo(vk::DeviceQueueCreateFlags(), getQueueIndex(), 1, &queuePriority); + + auto createInfo = vk::DeviceCreateInfo() + .setQueueCreateInfos(queueCreateInfo) + .setPEnabledExtensionNames(extensions) + .setPEnabledLayerNames(layers); + + device = physicalDevice.createDeviceUnique(createInfo, nullptr, dispatcher); + dispatcher.init(device.get()); + + return device.get(); + } + + uint32_t getQueueIndex() { return graphicsQueueIndex; } + +private: + vk::DynamicLoader dynamicLoader; + vk::DispatchLoaderDynamic dispatcher; + + vk::UniqueInstance instance; + vk::PhysicalDevice physicalDevice; + vk::UniqueDevice device; + uint32_t graphicsQueueIndex = 0; +}; + +#endif + class GLFWVulkanRenderableResource final : public mbgl::vulkan::SurfaceRenderableResource { public: explicit GLFWVulkanRenderableResource(GLFWVulkanBackend& backend_, bool capFrameRate) @@ -75,6 +182,39 @@ std::vector GLFWVulkanBackend::getInstanceExtensions() { return extensions; } +#ifdef USE_SHARED_VK_CONTEXT + +void GLFWVulkanBackend::initInstance() { + // tell the backend to keep the objects alive + usingSharedContext = true; + + // this method is required to set `instance` to a valid vkInstance + instance = vk::UniqueInstance(VkContext::shared().getInstance(), + vk::ObjectDestroy(nullptr, dispatcher)); + + // debug builds also require: + // - enabling either `VK_EXT_debug_utils` (and updating `debugUtilsEnabled`) or `VK_EXT_debug_report` extension + // - or passing the values returned by `getDebugExtensions()` as enabled extensions during instance creation + debugUtilsEnabled = true; +} + +void GLFWVulkanBackend::initDevice() { + // this method is required to populate: + // `physicalDevice` + // `device` + // `graphicsQueueIndex`/`presentQueueIndex` + // `physicalDeviceFeatures` - enabled features (optional) + + physicalDevice = VkContext::shared().getPhysicalDevice(); + device = vk::UniqueDevice(VkContext::shared().getDevice(), + vk::ObjectDestroy(nullptr, dispatcher)); + + graphicsQueueIndex = presentQueueIndex = VkContext::shared().getQueueIndex(); + physicalDeviceFeatures = vk::PhysicalDeviceFeatures(); +} + +#endif + namespace mbgl { namespace gfx { diff --git a/platform/glfw/glfw_vulkan_backend.hpp b/platform/glfw/glfw_vulkan_backend.hpp index 6eab1dfea..36ef1c05a 100644 --- a/platform/glfw/glfw_vulkan_backend.hpp +++ b/platform/glfw/glfw_vulkan_backend.hpp @@ -5,6 +5,10 @@ #include #include +// Example of using an application side VkInstance/VkDevice +// that's shared with MapLibre's renderer backend +// #define USE_SHARED_VK_CONTEXT + struct GLFWwindow; class GLFWVulkanBackend final : public GLFWBackend, @@ -29,6 +33,11 @@ public: protected: std::vector getInstanceExtensions() override; +#ifdef USE_SHARED_VK_CONTEXT + void initInstance() override; + void initDevice() override; +#endif + void activate() override {} void deactivate() override {} diff --git a/src/mbgl/vulkan/renderer_backend.cpp b/src/mbgl/vulkan/renderer_backend.cpp index ff9a28d06..a17444273 100644 --- a/src/mbgl/vulkan/renderer_backend.cpp +++ b/src/mbgl/vulkan/renderer_backend.cpp @@ -360,23 +360,31 @@ void RendererBackend::initDebug() { } void RendererBackend::init() { + // initialize minimal set of function pointers + dispatcher.init(dynamicLoader); + initFrameCapture(); initInstance(); + + // initialize function pointers for instance + dispatcher.init(instance.get()); + + initDebug(); initSurface(); initDevice(); + + // optional function pointer specialization for device + dispatcher.init(device.get()); + physicalDeviceProperties = physicalDevice.getProperties(dispatcher); + if (graphicsQueueIndex != -1) graphicsQueue = device->getQueue(graphicsQueueIndex, 0, dispatcher); + if (presentQueueIndex != -1) presentQueue = device->getQueue(presentQueueIndex, 0, dispatcher); + initAllocator(); initSwapchain(); initCommandPool(); } void RendererBackend::initInstance() { - // VULKAN_HPP_DEFAULT_DISPATCHER is global and can lead to race conditions in multi-map environments - static std::mutex vkDefaultDispatchLoaderMutex; - std::lock_guard lock(vkDefaultDispatchLoaderMutex); - - // initialize minimal set of function pointers - dispatcher.init(dynamicLoader); - // Vulkan 1.1 on Android is supported on 71% of devices (compared to 1.3 with 6%) as of April 23 2024 // https://vulkan.gpuinfo.org/ #ifdef __APPLE__ @@ -430,14 +438,6 @@ void RendererBackend::initInstance() { } instance = vk::createInstanceUnique(createInfo, nullptr, dispatcher); - - // initialize function pointers for instance - dispatcher.init(instance.get()); - -#ifdef ENABLE_VULKAN_VALIDATION - // enable validation layer callback - initDebug(); -#endif } void RendererBackend::initSurface() { @@ -541,8 +541,6 @@ void RendererBackend::initDevice() { } if (!physicalDevice) throw std::runtime_error("No suitable GPU found"); - - physicalDeviceProperties = physicalDevice.getProperties(dispatcher); }; pickPhysicalDevice(); @@ -582,20 +580,13 @@ void RendererBackend::initDevice() { auto createInfo = vk::DeviceCreateInfo() .setQueueCreateInfos(queueCreateInfos) - .setEnabledExtensionCount(static_cast(extensions.size())) - .setPpEnabledExtensionNames(extensions.data()) + .setPEnabledExtensionNames(extensions) .setPEnabledFeatures(&physicalDeviceFeatures); // this is not needed for newer implementations createInfo.setPEnabledLayerNames(layers); device = physicalDevice.createDeviceUnique(createInfo, nullptr, dispatcher); - - // optional function pointer specialization for device - dispatcher.init(device.get()); - - graphicsQueue = device->getQueue(graphicsQueueIndex, 0, dispatcher); - if (presentQueueIndex != -1) presentQueue = device->getQueue(presentQueueIndex, 0, dispatcher); } void RendererBackend::initSwapchain() { @@ -630,12 +621,13 @@ void RendererBackend::destroyResources() { vmaDestroyAllocator(allocator); - device.reset(); + usingSharedContext ? void(device.release()) : device.reset(); // destroy this last so we have cleanup validation debugUtilsCallback.reset(); debugReportCallback.reset(); - instance.reset(); + + usingSharedContext ? void(instance.release()) : instance.reset(); } /// @brief Register a list of types with a shader registry instance