Skip to content

Commit 588562e

Browse files
Add Vulkan device sharing example (maplibre#3649)
Co-authored-by: Birk Skyum <74932975+birkskyum@users.noreply.github.com>
1 parent aeaadc0 commit 588562e

4 files changed

Lines changed: 178 additions & 36 deletions

File tree

include/mbgl/vulkan/renderer_backend.hpp

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,16 @@ class RendererBackend : public gfx::RendererBackend {
8484
virtual std::vector<const char*> getDeviceExtensions();
8585
std::vector<const char*> getDebugExtensions();
8686

87-
void initInstance();
88-
void initDebug();
89-
void initSurface();
90-
void initDevice();
91-
void initAllocator();
92-
void initSwapchain();
93-
void initCommandPool();
94-
void initFrameCapture();
87+
virtual void initInstance();
88+
virtual void initDebug();
89+
virtual void initSurface();
90+
virtual void initDevice();
91+
virtual void initAllocator();
92+
virtual void initSwapchain();
93+
virtual void initCommandPool();
94+
virtual void initFrameCapture();
9595

96-
void destroyResources();
96+
virtual void destroyResources();
9797

9898
protected:
9999
vk::DynamicLoader dynamicLoader;
@@ -120,6 +120,7 @@ class RendererBackend : public gfx::RendererBackend {
120120
VmaAllocator allocator;
121121

122122
bool debugUtilsEnabled{false};
123+
bool usingSharedContext{false};
123124
};
124125

125126
} // namespace vulkan

platform/glfw/glfw_vulkan_backend.cpp

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,113 @@
1111
#define GLFW_INCLUDE_VULKAN
1212
#include <GLFW/glfw3.h>
1313

14+
#ifdef USE_SHARED_VK_CONTEXT
15+
16+
// An example of an external vkInstance/vkDevice provider.
17+
// It uses the first available device and queue
18+
class VkContext {
19+
public:
20+
VkContext() = default;
21+
~VkContext() {
22+
device.reset();
23+
instance.reset();
24+
}
25+
26+
static VkContext& shared() {
27+
static std::unique_ptr<VkContext> context;
28+
if (!context) {
29+
context = std::make_unique<VkContext>();
30+
}
31+
32+
return *context;
33+
}
34+
35+
vk::Instance getInstance() {
36+
if (instance) {
37+
return instance.get();
38+
}
39+
40+
dispatcher.init(dynamicLoader);
41+
42+
#ifdef __APPLE__
43+
vk::InstanceCreateFlags instanceFlags = vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR;
44+
#else
45+
vk::InstanceCreateFlags instanceFlags = {};
46+
#endif
47+
48+
const std::vector<const char*> layers = {"VK_LAYER_KHRONOS_validation"};
49+
std::vector<const char*> extensions = {
50+
VK_EXT_DEBUG_UTILS_EXTENSION_NAME,
51+
#ifdef __APPLE__
52+
"VK_KHR_portability_enumeration",
53+
#endif
54+
};
55+
56+
uint32_t glfwExtensionCount = 0;
57+
const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
58+
std::copy(glfwExtensions, glfwExtensions + glfwExtensionCount, std::back_inserter(extensions));
59+
60+
vk::ApplicationInfo appInfo("maplibre-native", 1, "maplibre-native", 1, VK_API_VERSION_1_0);
61+
const auto createInfo = vk::InstanceCreateInfo(instanceFlags)
62+
.setPApplicationInfo(&appInfo)
63+
.setPEnabledExtensionNames(extensions)
64+
.setPEnabledLayerNames(layers);
65+
66+
instance = vk::createInstanceUnique(createInfo, nullptr, dispatcher);
67+
dispatcher.init(instance.get());
68+
69+
return instance.get();
70+
}
71+
72+
vk::PhysicalDevice getPhysicalDevice() {
73+
if (!physicalDevice) {
74+
physicalDevice = instance->enumeratePhysicalDevices(dispatcher)[0];
75+
}
76+
77+
return physicalDevice;
78+
}
79+
80+
vk::Device getDevice() {
81+
if (device) {
82+
return device.get();
83+
}
84+
85+
const std::vector<const char*> layers = {"VK_LAYER_KHRONOS_validation"};
86+
const std::vector<const char*> extensions = {
87+
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
88+
#ifdef __APPLE__
89+
"VK_KHR_portability_subset",
90+
#endif
91+
};
92+
93+
float queuePriority = 1.0f;
94+
vk::DeviceQueueCreateInfo queueCreateInfo(vk::DeviceQueueCreateFlags(), getQueueIndex(), 1, &queuePriority);
95+
96+
auto createInfo = vk::DeviceCreateInfo()
97+
.setQueueCreateInfos(queueCreateInfo)
98+
.setPEnabledExtensionNames(extensions)
99+
.setPEnabledLayerNames(layers);
100+
101+
device = physicalDevice.createDeviceUnique(createInfo, nullptr, dispatcher);
102+
dispatcher.init(device.get());
103+
104+
return device.get();
105+
}
106+
107+
uint32_t getQueueIndex() { return graphicsQueueIndex; }
108+
109+
private:
110+
vk::DynamicLoader dynamicLoader;
111+
vk::DispatchLoaderDynamic dispatcher;
112+
113+
vk::UniqueInstance instance;
114+
vk::PhysicalDevice physicalDevice;
115+
vk::UniqueDevice device;
116+
uint32_t graphicsQueueIndex = 0;
117+
};
118+
119+
#endif
120+
14121
class GLFWVulkanRenderableResource final : public mbgl::vulkan::SurfaceRenderableResource {
15122
public:
16123
explicit GLFWVulkanRenderableResource(GLFWVulkanBackend& backend_, bool capFrameRate)
@@ -75,6 +182,39 @@ std::vector<const char*> GLFWVulkanBackend::getInstanceExtensions() {
75182
return extensions;
76183
}
77184

185+
#ifdef USE_SHARED_VK_CONTEXT
186+
187+
void GLFWVulkanBackend::initInstance() {
188+
// tell the backend to keep the objects alive
189+
usingSharedContext = true;
190+
191+
// this method is required to set `instance` to a valid vkInstance
192+
instance = vk::UniqueInstance(VkContext::shared().getInstance(),
193+
vk::ObjectDestroy<vk::NoParent, vk::DispatchLoaderDynamic>(nullptr, dispatcher));
194+
195+
// debug builds also require:
196+
// - enabling either `VK_EXT_debug_utils` (and updating `debugUtilsEnabled`) or `VK_EXT_debug_report` extension
197+
// - or passing the values returned by `getDebugExtensions()` as enabled extensions during instance creation
198+
debugUtilsEnabled = true;
199+
}
200+
201+
void GLFWVulkanBackend::initDevice() {
202+
// this method is required to populate:
203+
// `physicalDevice`
204+
// `device`
205+
// `graphicsQueueIndex`/`presentQueueIndex`
206+
// `physicalDeviceFeatures` - enabled features (optional)
207+
208+
physicalDevice = VkContext::shared().getPhysicalDevice();
209+
device = vk::UniqueDevice(VkContext::shared().getDevice(),
210+
vk::ObjectDestroy<vk::NoParent, vk::DispatchLoaderDynamic>(nullptr, dispatcher));
211+
212+
graphicsQueueIndex = presentQueueIndex = VkContext::shared().getQueueIndex();
213+
physicalDeviceFeatures = vk::PhysicalDeviceFeatures();
214+
}
215+
216+
#endif
217+
78218
namespace mbgl {
79219
namespace gfx {
80220

platform/glfw/glfw_vulkan_backend.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
#include <mbgl/vulkan/renderable_resource.hpp>
66
#include <mbgl/vulkan/renderer_backend.hpp>
77

8+
// Example of using an application side VkInstance/VkDevice
9+
// that's shared with MapLibre's renderer backend
10+
// #define USE_SHARED_VK_CONTEXT
11+
812
struct GLFWwindow;
913

1014
class GLFWVulkanBackend final : public GLFWBackend,
@@ -29,6 +33,11 @@ class GLFWVulkanBackend final : public GLFWBackend,
2933
protected:
3034
std::vector<const char*> getInstanceExtensions() override;
3135

36+
#ifdef USE_SHARED_VK_CONTEXT
37+
void initInstance() override;
38+
void initDevice() override;
39+
#endif
40+
3241
void activate() override {}
3342
void deactivate() override {}
3443

src/mbgl/vulkan/renderer_backend.cpp

Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -360,23 +360,31 @@ void RendererBackend::initDebug() {
360360
}
361361

362362
void RendererBackend::init() {
363+
// initialize minimal set of function pointers
364+
dispatcher.init(dynamicLoader);
365+
363366
initFrameCapture();
364367
initInstance();
368+
369+
// initialize function pointers for instance
370+
dispatcher.init(instance.get());
371+
372+
initDebug();
365373
initSurface();
366374
initDevice();
375+
376+
// optional function pointer specialization for device
377+
dispatcher.init(device.get());
378+
physicalDeviceProperties = physicalDevice.getProperties(dispatcher);
379+
if (graphicsQueueIndex != -1) graphicsQueue = device->getQueue(graphicsQueueIndex, 0, dispatcher);
380+
if (presentQueueIndex != -1) presentQueue = device->getQueue(presentQueueIndex, 0, dispatcher);
381+
367382
initAllocator();
368383
initSwapchain();
369384
initCommandPool();
370385
}
371386

372387
void RendererBackend::initInstance() {
373-
// VULKAN_HPP_DEFAULT_DISPATCHER is global and can lead to race conditions in multi-map environments
374-
static std::mutex vkDefaultDispatchLoaderMutex;
375-
std::lock_guard<std::mutex> lock(vkDefaultDispatchLoaderMutex);
376-
377-
// initialize minimal set of function pointers
378-
dispatcher.init(dynamicLoader);
379-
380388
// Vulkan 1.1 on Android is supported on 71% of devices (compared to 1.3 with 6%) as of April 23 2024
381389
// https://vulkan.gpuinfo.org/
382390
#ifdef __APPLE__
@@ -430,14 +438,6 @@ void RendererBackend::initInstance() {
430438
}
431439

432440
instance = vk::createInstanceUnique(createInfo, nullptr, dispatcher);
433-
434-
// initialize function pointers for instance
435-
dispatcher.init(instance.get());
436-
437-
#ifdef ENABLE_VULKAN_VALIDATION
438-
// enable validation layer callback
439-
initDebug();
440-
#endif
441441
}
442442

443443
void RendererBackend::initSurface() {
@@ -541,8 +541,6 @@ void RendererBackend::initDevice() {
541541
}
542542

543543
if (!physicalDevice) throw std::runtime_error("No suitable GPU found");
544-
545-
physicalDeviceProperties = physicalDevice.getProperties(dispatcher);
546544
};
547545

548546
pickPhysicalDevice();
@@ -582,20 +580,13 @@ void RendererBackend::initDevice() {
582580

583581
auto createInfo = vk::DeviceCreateInfo()
584582
.setQueueCreateInfos(queueCreateInfos)
585-
.setEnabledExtensionCount(static_cast<uint32_t>(extensions.size()))
586-
.setPpEnabledExtensionNames(extensions.data())
583+
.setPEnabledExtensionNames(extensions)
587584
.setPEnabledFeatures(&physicalDeviceFeatures);
588585

589586
// this is not needed for newer implementations
590587
createInfo.setPEnabledLayerNames(layers);
591588

592589
device = physicalDevice.createDeviceUnique(createInfo, nullptr, dispatcher);
593-
594-
// optional function pointer specialization for device
595-
dispatcher.init(device.get());
596-
597-
graphicsQueue = device->getQueue(graphicsQueueIndex, 0, dispatcher);
598-
if (presentQueueIndex != -1) presentQueue = device->getQueue(presentQueueIndex, 0, dispatcher);
599590
}
600591

601592
void RendererBackend::initSwapchain() {
@@ -630,12 +621,13 @@ void RendererBackend::destroyResources() {
630621

631622
vmaDestroyAllocator(allocator);
632623

633-
device.reset();
624+
usingSharedContext ? void(device.release()) : device.reset();
634625

635626
// destroy this last so we have cleanup validation
636627
debugUtilsCallback.reset();
637628
debugReportCallback.reset();
638-
instance.reset();
629+
630+
usingSharedContext ? void(instance.release()) : instance.reset();
639631
}
640632

641633
/// @brief Register a list of types with a shader registry instance

0 commit comments

Comments
 (0)