Add Vulkan device sharing example (#3649)

Co-authored-by: Birk Skyum <74932975+birkskyum@users.noreply.github.com>
This commit is contained in:
Adrian Cojocaru
2025-07-26 01:46:44 +03:00
committed by GitHub
parent aeaadc06b4
commit 588562ef0d
4 changed files with 178 additions and 36 deletions

View File

@@ -84,16 +84,16 @@ protected:
virtual std::vector<const char*> getDeviceExtensions();
std::vector<const char*> 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

View File

@@ -11,6 +11,113 @@
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#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<VkContext> context;
if (!context) {
context = std::make_unique<VkContext>();
}
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<const char*> layers = {"VK_LAYER_KHRONOS_validation"};
std::vector<const char*> 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<const char*> layers = {"VK_LAYER_KHRONOS_validation"};
const std::vector<const char*> 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<const char*> 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<vk::NoParent, vk::DispatchLoaderDynamic>(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<vk::NoParent, vk::DispatchLoaderDynamic>(nullptr, dispatcher));
graphicsQueueIndex = presentQueueIndex = VkContext::shared().getQueueIndex();
physicalDeviceFeatures = vk::PhysicalDeviceFeatures();
}
#endif
namespace mbgl {
namespace gfx {

View File

@@ -5,6 +5,10 @@
#include <mbgl/vulkan/renderable_resource.hpp>
#include <mbgl/vulkan/renderer_backend.hpp>
// 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<const char*> getInstanceExtensions() override;
#ifdef USE_SHARED_VK_CONTEXT
void initInstance() override;
void initDevice() override;
#endif
void activate() override {}
void deactivate() override {}

View File

@@ -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<std::mutex> 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<uint32_t>(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