QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GstGlContextBridge.cc
Go to the documentation of this file.
3
4#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH)
5
7
8#include <QtCore/QLoggingCategory>
9#include <QtCore/QMutex>
10#include <QtCore/QMutexLocker>
11#include <QtGui/QGuiApplication>
12#include <QtGui/QOpenGLContext>
13#include <qpa/qplatformnativeinterface.h>
14
15#include <gst/gl/gl.h>
16#if defined(__linux__)
17# include <gst/gl/egl/gstgldisplay_egl.h>
18# include <EGL/egl.h>
19// Qt 6 on xcb uses GLX by default — fall back to GLX context wrapping when EGL isn't available.
20# if __has_include(<gst/gl/x11/gstgldisplay_x11.h>) && __has_include(<QtGui/qopenglcontext_platform.h>)
21# include <gst/gl/x11/gstgldisplay_x11.h>
22# include <QtGui/qopenglcontext_platform.h>
23# include <X11/Xlib.h>
24# define QGC_GST_BRIDGE_HAS_GLX 1
25# endif
26// Wayland: downstream elements that probe GST_IS_GL_DISPLAY_WAYLAND need the wl_display tagged on the GstGLDisplay; without it pool/queue negotiation falls back to a generic EGL display and silently misses Wayland-specific zero-copy paths.
27# if __has_include(<gst/gl/wayland/gstgldisplay_wayland.h>)
28# include <gst/gl/wayland/gstgldisplay_wayland.h>
29# define QGC_GST_BRIDGE_HAS_WAYLAND 1
30# endif
31#endif
32
33QGC_LOGGING_CATEGORY(GstGlBridgeLog, "Video.GStreamer.HwBuffers.GstGlBridge")
34
35namespace GstGlContextBridge {
36namespace {
37
38QMutex s_mutex;
39GstGLDisplay *s_display = nullptr;
40GstGLContext *s_context = nullptr;
41bool s_primed = false;
42bool s_primeAttempted = false;
43// Bounds globalShareContext retry spam when Qt GL is never initialized.
44int s_primeNullShareCount = 0;
45constexpr int kMaxPrimeNullShareRetries = 16;
46
47#if defined(__linux__)
48EGLDisplay qtEglDisplay()
49{
50 if (auto *ni = QGuiApplication::platformNativeInterface()) {
51 if (auto *d = ni->nativeResourceForIntegration("egldisplay")) {
52 return static_cast<EGLDisplay>(d);
53 }
54 }
55 return eglGetDisplay(EGL_DEFAULT_DISPLAY);
56}
57
58EGLContext qtEglContext(QOpenGLContext *qtCtx)
59{
60 if (!qtCtx) return EGL_NO_CONTEXT;
61 if (auto *egl = qtCtx->nativeInterface<QNativeInterface::QEGLContext>()) {
62 return egl->nativeContext();
63 }
64 return EGL_NO_CONTEXT;
65}
66#endif
67
68bool primeLocked()
69{
70 if (s_primed) return true;
71 if (s_primeAttempted) return false;
72 s_primeAttempted = true;
73
74 QOpenGLContext *qtCtx = QOpenGLContext::globalShareContext();
75 if (!qtCtx) {
76 ++s_primeNullShareCount;
77 if (s_primeNullShareCount <= kMaxPrimeNullShareRetries) {
78 qCInfo(GstGlBridgeLog) << "globalShareContext() is null — Qt GL not initialized yet"
79 << "(attempt" << s_primeNullShareCount << "/" << kMaxPrimeNullShareRetries << ")";
80 s_primeAttempted = false; // allow retry until limit is reached
81 } else {
82 if (s_primeNullShareCount == kMaxPrimeNullShareRetries + 1) {
83 qCWarning(GstGlBridgeLog) << "globalShareContext() still null after"
84 << kMaxPrimeNullShareRetries
85 << "retries; GL bridge giving up";
86 }
87 // s_primeAttempted stays true — no more retries until reset()
88 }
89 return false;
90 }
91
92#if defined(__linux__)
93 // Try EGL first (Wayland, eglfs, xcb_egl); fall back to GLX (xcb default on Qt 6 desktop).
94 EGLContext eglCtx = qtEglContext(qtCtx);
95 EGLDisplay eglDisp = (eglCtx != EGL_NO_CONTEXT) ? qtEglDisplay() : EGL_NO_DISPLAY;
96 // Allow retry on every "real" failure below — these are likely permanent (misconfigured
97 // gst build, missing native interface) but if a window-recreate fixes things the next
98 // NEED_CONTEXT should get another chance. reset() also re-enables retry by clearing
99 // s_primeAttempted directly.
100 auto bail = [](const char *) -> bool { s_primeAttempted = false; return false; };
101 if (eglCtx != EGL_NO_CONTEXT && eglDisp != EGL_NO_DISPLAY) {
102# if defined(QGC_GST_BRIDGE_HAS_WAYLAND)
103 // On Wayland, primary display must be GstGLDisplayWayland (carries wl_display); the EGL
104 // display is then derived and marked foreign so unref doesn't tear down the EGL handle Qt owns.
105 const QString platformName = QGuiApplication::platformName();
106 if (platformName == QLatin1String("wayland") || platformName == QLatin1String("wayland-egl")) {
107 struct wl_display *wlDisp = nullptr;
108 if (auto *ni = QGuiApplication::platformNativeInterface()) {
109 wlDisp = static_cast<struct wl_display *>(ni->nativeResourceForIntegration("wl_display"));
110 }
111 if (wlDisp) {
112 GstGLDisplayWayland *displayWl = gst_gl_display_wayland_new_with_display(wlDisp);
113 if (displayWl) {
114 s_display = GST_GL_DISPLAY(displayWl);
115# if GST_CHECK_VERSION(1, 26, 0)
116 // Pre-derive the EGL view so downstream NEED_CONTEXT for gst.gl.display.egl is satisfied
117 // by a display that maps back to the same wl_display. set_foreign(TRUE) is mandatory:
118 // Qt owns the EGLDisplay; without it gst would call eglTerminate on Qt's display.
119 if (GstGLDisplayEGL *derived = gst_gl_display_egl_from_gl_display(s_display)) {
120 gst_gl_display_egl_set_foreign(derived, TRUE);
121 gst_object_unref(derived);
122 }
123# endif
124 }
125 }
126 }
127# endif
128 if (!s_display) {
129 GstGLDisplayEGL *displayEgl = gst_gl_display_egl_new_with_egl_display(eglDisp);
130 if (!displayEgl) {
131 qCWarning(GstGlBridgeLog) << "gst_gl_display_egl_new_with_egl_display failed";
132 return bail("displayEgl");
133 }
134 s_display = GST_GL_DISPLAY(displayEgl);
135 }
136
137 s_context = gst_gl_context_new_wrapped(s_display,
138 reinterpret_cast<guintptr>(eglCtx),
139 GST_GL_PLATFORM_EGL,
140 static_cast<GstGLAPI>(GST_GL_API_GLES2 | GST_GL_API_OPENGL));
141 if (!s_context) {
142 qCWarning(GstGlBridgeLog) << "gst_gl_context_new_wrapped (EGL) failed";
143 gst_clear_object(&s_display);
144 return bail("ctxEgl");
145 }
146# if defined(QGC_GST_BRIDGE_HAS_WAYLAND)
147 const bool isWayland = GST_IS_GL_DISPLAY_WAYLAND(s_display);
148 qCInfo(GstGlBridgeLog) << (isWayland ? "GL bridge primed (Wayland+EGL)" : "GL bridge primed (EGL)");
149# else
150 qCInfo(GstGlBridgeLog) << "GL bridge primed (EGL)";
151# endif
152 } else {
153# if defined(QGC_GST_BRIDGE_HAS_GLX)
154 // Qt's QGLXContext exposes the X11 Display* + GLXContext we need to wrap.
155 auto *glx = qtCtx->nativeInterface<QNativeInterface::QGLXContext>();
156 if (!glx) {
157 qCWarning(GstGlBridgeLog) << "Qt GL context exposes neither EGL nor GLX; GL bridge disabled";
158 return bail("noGlx");
159 }
160 Display *xdisp = nullptr;
161 if (auto *ni = QGuiApplication::platformNativeInterface()) {
162 xdisp = static_cast<Display *>(ni->nativeResourceForIntegration("display"));
163 }
164 if (!xdisp) {
165 qCWarning(GstGlBridgeLog) << "X11 Display unresolvable; GL bridge disabled";
166 return bail("xdisp");
167 }
168 GstGLDisplayX11 *displayX11 = gst_gl_display_x11_new_with_display(xdisp);
169 if (!displayX11) {
170 qCWarning(GstGlBridgeLog) << "gst_gl_display_x11_new_with_display failed";
171 return bail("displayX11");
172 }
173 s_display = GST_GL_DISPLAY(displayX11);
174 s_context = gst_gl_context_new_wrapped(s_display,
175 reinterpret_cast<guintptr>(glx->nativeContext()),
176 GST_GL_PLATFORM_GLX,
177 static_cast<GstGLAPI>(GST_GL_API_OPENGL));
178 if (!s_context) {
179 qCWarning(GstGlBridgeLog) << "gst_gl_context_new_wrapped (GLX) failed";
180 gst_clear_object(&s_display);
181 return bail("ctxGlx");
182 }
183 qCInfo(GstGlBridgeLog) << "GL bridge primed (GLX)";
184# else
185 qCWarning(GstGlBridgeLog) << "Qt EGLContext unresolvable and GLX bridge not built; GL bridge disabled";
186 return bail("noEglNoGlx");
187# endif
188 }
189
190 s_primed = true;
191 // gst_gl_context_fill_info() must run on the context's own thread, but a freshly wrapped context has no active thread until first activation — calling thread_add now trips an assertion. GStreamer fills info lazily on first use, so don't pre-fill.
192 qCDebug(GstGlBridgeLog) << "GL bridge primed: display=" << s_display
193 << "context=" << s_context;
194 return true;
195#else
196 // EGL-only by design. Windows uses GstD3D11ContextBridge; macOS/iOS use the
197 // CVPixelBuffer path. WGL/CGL wrappers add no value over those.
198 qCInfo(GstGlBridgeLog) << "GL bridge inactive on this platform (non-EGL)";
199 return false;
200#endif
201}
202
203} // namespace
204
205bool prime()
206{
207 QMutexLocker lock(&s_mutex);
208 return primeLocked();
209}
210
211GstBusSyncReply handleSyncMessage(GstMessage *message)
212{
213 if (GST_MESSAGE_TYPE(message) != GST_MESSAGE_NEED_CONTEXT) {
214 return GST_BUS_PASS;
215 }
216
217 const gchar *contextType = nullptr;
218 if (!gst_message_parse_context_type(message, &contextType) || !contextType) {
219 return GST_BUS_PASS;
220 }
221 const bool isGlDisplay = (g_strcmp0(contextType, GST_GL_DISPLAY_CONTEXT_TYPE) == 0);
222 const bool isGlApp = (g_strcmp0(contextType, "gst.gl.app_context") == 0);
223 if (!isGlDisplay && !isGlApp) {
224 return GST_BUS_PASS;
225 }
226
227 QMutexLocker lock(&s_mutex);
228 if (!primeLocked()) {
229 return GST_BUS_PASS;
230 }
231
232 GstElement *element = GST_ELEMENT(GST_MESSAGE_SRC(message));
233 if (!element) {
234 return GST_BUS_PASS;
235 }
236
237 if (isGlDisplay && s_display) {
238 GstContext *gctx = gst_context_new(GST_GL_DISPLAY_CONTEXT_TYPE, TRUE);
239 gst_context_set_gl_display(gctx, s_display);
240 gst_element_set_context(element, gctx);
241 gst_context_unref(gctx);
242 qCDebug(GstGlBridgeLog) << "Provided GL display context to" << GST_ELEMENT_NAME(element);
243 } else if (isGlApp && s_context) {
244 GstContext *gctx = gst_context_new("gst.gl.app_context", TRUE);
245 GstStructure *s = gst_context_writable_structure(gctx);
246 gst_structure_set(s, "context", GST_TYPE_GL_CONTEXT, s_context, NULL);
247 gst_element_set_context(element, gctx);
248 gst_context_unref(gctx);
249 qCDebug(GstGlBridgeLog) << "Provided GL app context to" << GST_ELEMENT_NAME(element);
250 } else {
251 return GST_BUS_PASS;
252 }
253
254 gst_message_unref(message);
255 return GST_BUS_DROP;
256}
257
258bool answerContextQuery(GstQuery *query)
259{
260 if (!query || GST_QUERY_TYPE(query) != GST_QUERY_CONTEXT) {
261 return false;
262 }
263 const gchar *contextType = nullptr;
264 if (!gst_query_parse_context_type(query, &contextType) || !contextType) {
265 return false;
266 }
267 const bool isGlDisplay = (g_strcmp0(contextType, GST_GL_DISPLAY_CONTEXT_TYPE) == 0);
268 const bool isGlApp = (g_strcmp0(contextType, "gst.gl.app_context") == 0);
269 if (!isGlDisplay && !isGlApp) {
270 return false;
271 }
272
273 QMutexLocker lock(&s_mutex);
274 if (!primeLocked()) {
275 return false;
276 }
277
278 if (isGlDisplay && s_display) {
279 GstContext *gctx = gst_context_new(GST_GL_DISPLAY_CONTEXT_TYPE, TRUE);
280 gst_context_set_gl_display(gctx, s_display);
281 gst_query_set_context(query, gctx);
282 gst_context_unref(gctx);
283 return true;
284 }
285 if (isGlApp && s_context) {
286 GstContext *gctx = gst_context_new("gst.gl.app_context", TRUE);
287 GstStructure *s = gst_context_writable_structure(gctx);
288 gst_structure_set(s, "context", GST_TYPE_GL_CONTEXT, s_context, NULL);
289 gst_query_set_context(query, gctx);
290 gst_context_unref(gctx);
291 return true;
292 }
293 return false;
294}
295
296void reset()
297{
298 QMutexLocker lock(&s_mutex);
299 gst_clear_object(&s_context);
300 gst_clear_object(&s_display);
301 s_primed = false;
302 s_primeAttempted = false;
303 s_primeNullShareCount = 0;
304 qCDebug(GstGlBridgeLog) << "GL bridge reset";
305}
306
307void rearm()
308{
309 QMutexLocker lock(&s_mutex);
310 if (s_primed) return;
311 if (s_primeAttempted && s_primeNullShareCount > kMaxPrimeNullShareRetries) {
312 s_primeAttempted = false;
313 s_primeNullShareCount = 0;
314 qCInfo(GstGlBridgeLog) << "GL bridge rearm: clearing exhausted retry latch";
315 }
316}
317
318
319namespace {
320struct GlBridgeRegistrar {
321 GlBridgeRegistrar() {
322 GstContextBridgeRegistry::registerBridgeHandler(&GstGlContextBridge::handleSyncMessage);
323 GstContextBridgeRegistry::registerResetCallback(&GstGlContextBridge::reset);
324 }
325};
326static GlBridgeRegistrar s_glBridgeRegistrar;
327} // anonymous namespace
328
329} // namespace GstGlContextBridge
330
331#endif // QGC_HAS_GST_GLMEMORY_GPU_PATH
struct _GstElement GstElement
#define QGC_LOGGING_CATEGORY(name, categoryStr)