QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GstVulkanContextBridge.cc
Go to the documentation of this file.
2
3#include <QtGui/qtguiglobal.h>
4
7
8#if defined(QGC_HAS_GST_VULKAN_GPU_PATH) && QT_CONFIG(vulkan)
9
10#include <QtCore/QLoggingCategory>
11#include <QtCore/QMutex>
12#include <QtCore/QMutexLocker>
13#include <rhi/qrhi.h>
14#include <vulkan/vulkan.h>
15
16// gstvkdecoder.h's `slots` member collides with Qt's `slots` keyword macro — see GstVulkanVideoBuffer.cc.
17#pragma push_macro("slots")
18#undef slots
19// gstvkqueue.h (gst-vulkan 1.24.x) omits G_BEGIN_DECLS, so gst_context_set_vulkan_queue would get C++
20// linkage and fail to link against the C symbol; the explicit extern "C" restores it (harmless for the
21// sibling headers that already self-guard).
22extern "C" {
23#include <gst/vulkan/gstvkdevice.h>
24#include <gst/vulkan/gstvkimagememory.h>
25#include <gst/vulkan/gstvkinstance.h>
26#include <gst/vulkan/gstvkphysicaldevice.h>
27#include <gst/vulkan/gstvkqueue.h>
28}
29#pragma pop_macro("slots")
30
31#include "QGCLoggingCategory.h"
32#include "QGCRhiCapture.h"
33
34QGC_LOGGING_CATEGORY(GstVulkanBridgeLog, "Video.GStreamer.HwBuffers.GstVulkanBridge")
35
36namespace GstVulkanContextBridge {
37namespace {
38
39QMutex s_mutex;
40GstVulkanInstance* s_instance = nullptr;
41GstVulkanDevice* s_device = nullptr;
42GstVulkanQueue* s_queue = nullptr;
43GstBridgePrimeRetry::PrimeRetryState s_retry;
44
45// Vulkan handles snapshotted render-side by QGCRhiCapture::populateSnapshot(); primeLocked() runs on the bus-sync
46// streaming thread, so it must never dereference the cached QRhi* (races render-thread QRhi teardown). Desktop Linux
47// pins GL in Platform::initialize (Vulkan import is dormant), so on Linux this is the expected no-op path.
48struct SnapshotHandles
49{
50 VkPhysicalDevice physDev = VK_NULL_HANDLE;
51 quint32 queueFamilyIdx = 0;
52 quint32 queueIdx = 0;
53};
54
55bool readSnapshotHandles(SnapshotHandles& out)
56{
57 auto& snap = QGCRhiCapture::deviceSnapshot();
58 // backend is published last (release); acquire-loading it first guarantees the handle fields are visible.
59 if (snap.backend.load(std::memory_order_acquire) != static_cast<int>(QRhi::Vulkan)) {
60 return false;
61 }
62 out.physDev = static_cast<VkPhysicalDevice>(snap.vkPhysicalDevice.load(std::memory_order_acquire));
63 out.queueFamilyIdx = snap.vkQueueFamilyIdx.load(std::memory_order_acquire);
64 out.queueIdx = snap.vkQueueIdx.load(std::memory_order_acquire);
65 return out.physDev != VK_NULL_HANDLE;
66}
67
68// gst-vulkan 1.24 exposes no wrapped-instance/device constructor, so we cannot hand vulkanh26xdec QRhi's *own*
69// VkInstance/VkDevice. We build a gst instance, then bind a GstVulkanDevice to the GstVulkanPhysicalDevice matching
70// QRhi's physDev. That yields same-physical-device but a distinct VkDevice; the importer's device-match guard then
71// routes those (foreign-VkDevice) frames to CPU. True same-VkDevice zero-copy needs a wrap API not present here.
72GstVulkanPhysicalDevice* matchPhysicalDevice(GstVulkanInstance* instance, VkPhysicalDevice want)
73{
74 const guint n = instance->n_physical_devices;
75 for (guint i = 0; i < n; ++i) {
76 GstVulkanPhysicalDevice* phys = gst_vulkan_physical_device_new(instance, i);
77 if (!phys) {
78 continue;
79 }
80 if (phys->device == want) {
81 return phys;
82 }
83 gst_object_unref(phys);
84 }
85 return nullptr;
86}
87
88bool primeLocked()
89{
90 switch (GstBridgePrimeRetry::primeRetryGuard(s_retry)) {
91 case GstBridgePrimeRetry::Decision::AlreadyPrimed:
92 return true;
93 case GstBridgePrimeRetry::Decision::GiveUp:
94 return false;
95 case GstBridgePrimeRetry::Decision::ShouldRetry:
96 break;
97 }
98
99 SnapshotHandles nh;
100 if (!readSnapshotHandles(nh)) {
101 // Expected on the default GL RHI build; retry in case the Vulkan RHI initializes later.
102 if (!GstBridgePrimeRetry::rearmRetry(s_retry) && GstBridgePrimeRetry::justGaveUp(s_retry)) {
103 qCInfo(GstVulkanBridgeLog) << "active RHI is not Vulkan after" << s_retry.maxRetries
104 << "retries; Vulkan bridge inactive";
105 }
106 return false;
107 }
108
109 auto bail = [](const char* what) -> bool {
110 qCWarning(GstVulkanBridgeLog) << "Vulkan bridge prime failed:" << what;
111 gst_clear_object(&s_queue);
112 gst_clear_object(&s_device);
113 gst_clear_object(&s_instance);
114 s_retry.primeAttempted = false;
115 return false;
116 };
117
118 s_instance = gst_vulkan_instance_new();
119 if (!s_instance || !gst_vulkan_instance_open(s_instance, nullptr)) {
120 return bail("gst_vulkan_instance_open");
121 }
122
123 GstVulkanPhysicalDevice* phys = matchPhysicalDevice(s_instance, nh.physDev);
124 if (!phys) {
125 return bail("no GstVulkanPhysicalDevice matches QRhi physDev");
126 }
127 s_device = gst_vulkan_device_new(phys);
128 gst_object_unref(phys);
129 if (!s_device || !gst_vulkan_device_open(s_device, nullptr)) {
130 return bail("gst_vulkan_device_open");
131 }
132 s_queue = gst_vulkan_device_get_queue(s_device, nh.queueFamilyIdx, nh.queueIdx);
133 if (!s_queue) {
134 return bail("gst_vulkan_device_get_queue");
135 }
136
137 s_retry.primed = true;
138 // Distinct-VkDevice limitation is by design until a wrap API lands; flag it once so the CPU fallback isn't mistaken
139 // for a bug.
140 qCInfo(GstVulkanBridgeLog) << "Vulkan bridge primed on QRhi physical device — note: gst VkDevice differs from "
141 "QRhi VkDevice, so import falls back to CPU until same-device wrapping is available";
142 return true;
143}
144
145GstVulkanInstance* refInstance()
146{
147 return s_instance ? GST_VULKAN_INSTANCE(gst_object_ref(s_instance)) : nullptr;
148}
149
150GstVulkanDevice* refDevice()
151{
152 return s_device ? GST_VULKAN_DEVICE(gst_object_ref(s_device)) : nullptr;
153}
154
155GstVulkanQueue* refQueue()
156{
157 return s_queue ? GST_VULKAN_QUEUE(gst_object_ref(s_queue)) : nullptr;
158}
159
160} // namespace
161
162namespace {
163
164const char* const kContextTypes[] = {
165 GST_VULKAN_INSTANCE_CONTEXT_TYPE_STR,
166 GST_VULKAN_DEVICE_CONTEXT_TYPE_STR,
167 GST_VULKAN_QUEUE_CONTEXT_TYPE_STR,
168};
169
170const QLoggingCategory& vtCat(void*)
171{
172 return GstVulkanBridgeLog();
173}
174
175QMutex& vtMutex(void*)
176{
177 return s_mutex;
178}
179
180bool vtPrime(void*)
181{
182 return primeLocked();
183}
184
185GstObject* vtRefObject(void*, const char* contextType)
186{
187 if (g_strcmp0(contextType, GST_VULKAN_INSTANCE_CONTEXT_TYPE_STR) == 0) {
188 return GST_OBJECT(refInstance());
189 }
190 if (g_strcmp0(contextType, GST_VULKAN_DEVICE_CONTEXT_TYPE_STR) == 0) {
191 return GST_OBJECT(refDevice());
192 }
193 return GST_OBJECT(refQueue());
194}
195
196GstContext* vtBuildContext(void*, const char* contextType, GstObject* object)
197{
198 if (g_strcmp0(contextType, GST_VULKAN_INSTANCE_CONTEXT_TYPE_STR) == 0) {
199 GstContext* ctx = gst_context_new(GST_VULKAN_INSTANCE_CONTEXT_TYPE_STR, TRUE);
200 gst_context_set_vulkan_instance(ctx, GST_VULKAN_INSTANCE(object));
201 return ctx;
202 }
203 if (g_strcmp0(contextType, GST_VULKAN_DEVICE_CONTEXT_TYPE_STR) == 0) {
204 GstContext* ctx = gst_context_new(GST_VULKAN_DEVICE_CONTEXT_TYPE_STR, TRUE);
205 gst_context_set_vulkan_device(ctx, GST_VULKAN_DEVICE(object));
206 return ctx;
207 }
208 GstContext* ctx = gst_context_new(GST_VULKAN_QUEUE_CONTEXT_TYPE_STR, TRUE);
209 gst_context_set_vulkan_queue(ctx, GST_VULKAN_QUEUE(object));
210 return ctx;
211}
212
213const GstContextBridge::BridgeVTable s_vtable = {
214 "Vulkan", kContextTypes, 3, &vtCat, &vtMutex, &vtPrime, &vtRefObject, &vtBuildContext, nullptr,
215};
216
217} // namespace
218
219bool prime()
220{
221 QMutexLocker lock(&s_mutex);
222 return primeLocked();
223}
224
225GstBusSyncReply handleSyncMessage(GstMessage* message)
226{
227 return GstContextBridge::handleSyncMessage(s_vtable, nullptr, message);
228}
229
230bool answerContextQuery(GstQuery* query)
231{
232 return GstContextBridge::answerContextQuery(s_vtable, nullptr, query);
233}
234
235void reset()
236{
237 QMutexLocker lock(&s_mutex);
238 gst_clear_object(&s_queue);
239 gst_clear_object(&s_device);
240 gst_clear_object(&s_instance);
241 GstBridgePrimeRetry::resetRetry(s_retry);
242 qCDebug(GstVulkanBridgeLog) << "Vulkan bridge reset";
243}
244
245void rearm()
246{
247 QMutexLocker lock(&s_mutex);
248 GstBridgePrimeRetry::rearmAfterExhaustion(s_retry);
249}
250
251namespace {
252struct VulkanBridgeRegistrar
253{
254 VulkanBridgeRegistrar()
255 {
256 GstContextBridge::registerBridge(GstVulkanBridgeLog(), "Vulkan", &GstVulkanContextBridge::handleSyncMessage,
257 &GstVulkanContextBridge::reset);
258 }
259};
260
261static VulkanBridgeRegistrar s_vulkanBridgeRegistrar;
262} // namespace
263
264} // namespace GstVulkanContextBridge
265
266#endif // QGC_HAS_GST_VULKAN_GPU_PATH
static std::atomic< LogManager * > s_instance
Definition LogManager.cc:26
#define QGC_LOGGING_CATEGORY(name, categoryStr)
DeviceSnapshot & deviceSnapshot() noexcept
Returns the global snapshot. Atomic fields make individual reads thread-safe.