QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GstGlContextBridge.cc
Go to the documentation of this file.
2
5
6#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH)
7
8#include <QtCore/QLoggingCategory>
9#include <QtCore/QMutex>
10#include <QtCore/QMutexLocker>
11#include <QtGui/QGuiApplication>
12#include <QtGui/QOpenGLContext>
13#include <QtGui/qguiapplication_platform.h>
14#include <gst/gl/gl.h>
15
16#include "QGCLoggingCategory.h"
17// Shared by Linux and Windows-ANGLE (ANGLE ships EGL/egl.h); on Windows it's a fallback behind D3D11/D3D12.
18// GLX/Wayland/X11 wrapping stays Linux-only below.
19#if defined(__linux__) || (defined(_WIN32) && __has_include(<EGL/egl.h>))
20#include <EGL/egl.h>
21#include <gst/gl/egl/gstgldisplay_egl.h>
22#define QGC_GST_BRIDGE_HAS_EGL 1
23#endif
24#if defined(__linux__)
25// Qt 6 on xcb uses GLX by default — fall back to GLX context wrapping when EGL isn't available.
26#if __has_include(<gst/gl/x11/gstgldisplay_x11.h>) && __has_include(<QtGui/qopenglcontext_platform.h>)
27#include <QtGui/qopenglcontext_platform.h>
28#include <X11/Xlib.h>
29#include <gst/gl/x11/gstgldisplay_x11.h>
30#define QGC_GST_BRIDGE_HAS_GLX 1
31#endif
32// Wayland: downstream elements probe GST_IS_GL_DISPLAY_WAYLAND; wl_display must be tagged on GstGLDisplay or zero-copy
33// paths are silently missed.
34#if __has_include(<gst/gl/wayland/gstgldisplay_wayland.h>)
35#include <gst/gl/wayland/gstgldisplay_wayland.h>
36#define QGC_GST_BRIDGE_HAS_WAYLAND 1
37#endif
38#endif
39
40QGC_LOGGING_CATEGORY(GstGlBridgeLog, "Video.GStreamer.HwBuffers.GstGlBridge")
41
42namespace GstGlContextBridge {
43namespace {
44
45QMutex s_mutex;
46GstGLDisplay* s_display = nullptr;
47GstGLContext* s_context = nullptr;
48// Bounds globalShareContext retry spam when Qt GL is never initialized.
49GstBridgePrimeRetry::PrimeRetryState s_retry;
50
51#if defined(QGC_GST_BRIDGE_HAS_EGL)
52EGLDisplay qtEglDisplay(QOpenGLContext* qtCtx)
53{
54 // QEGLContext::display() is the only handle that guarantees EGLImage import/sample compatibility; fall back to
55 // EGL_DEFAULT_DISPLAY when unavailable.
56 if (qtCtx) {
57 if (auto* egl = qtCtx->nativeInterface<QNativeInterface::QEGLContext>()) {
58 EGLDisplay d = egl->display();
59 if (d != EGL_NO_DISPLAY)
60 return d;
61 }
62 }
63 return eglGetDisplay(EGL_DEFAULT_DISPLAY);
64}
65
66EGLContext qtEglContext(QOpenGLContext* qtCtx)
67{
68 if (!qtCtx)
69 return EGL_NO_CONTEXT;
70 if (auto* egl = qtCtx->nativeInterface<QNativeInterface::QEGLContext>()) {
71 return egl->nativeContext();
72 }
73 return EGL_NO_CONTEXT;
74}
75#endif
76
77bool primeLocked()
78{
79 switch (GstBridgePrimeRetry::primeRetryGuard(s_retry)) {
80 case GstBridgePrimeRetry::Decision::AlreadyPrimed:
81 return true;
82 case GstBridgePrimeRetry::Decision::GiveUp:
83 return false;
84 case GstBridgePrimeRetry::Decision::ShouldRetry:
85 break;
86 }
87
88 QOpenGLContext* qtCtx = QOpenGLContext::globalShareContext();
89 if (!qtCtx) {
90 if (GstBridgePrimeRetry::rearmRetry(s_retry)) {
91 qCInfo(GstGlBridgeLog) << "globalShareContext() is null — Qt GL not initialized yet"
92 << "(attempt" << s_retry.nullCount << "/" << s_retry.maxRetries << ")";
93 } else if (GstBridgePrimeRetry::justGaveUp(s_retry)) {
94 qCWarning(GstGlBridgeLog) << "globalShareContext() still null after" << s_retry.maxRetries
95 << "retries; GL bridge giving up";
96 }
97 return false;
98 }
99
100#if defined(QGC_GST_BRIDGE_HAS_EGL)
101 // Try EGL first (Linux: Wayland/eglfs/xcb_egl; Windows: ANGLE-EGL); fall back to GLX (Linux xcb default on Qt 6).
102 EGLContext eglCtx = qtEglContext(qtCtx);
103 EGLDisplay eglDisp = (eglCtx != EGL_NO_CONTEXT) ? qtEglDisplay(qtCtx) : EGL_NO_DISPLAY;
104 // Reset s_primeAttempted on each bail so a window-recreate or reset() gets a retry.
105 auto bail = [](const char*) -> bool {
106 s_retry.primeAttempted = false;
107 return false;
108 };
109 if (eglCtx != EGL_NO_CONTEXT && eglDisp != EGL_NO_DISPLAY) {
110#if defined(QGC_GST_BRIDGE_HAS_WAYLAND)
111 // On Wayland, primary display must be GstGLDisplayWayland; derived EGL display is marked foreign so unref
112 // doesn't tear down Qt's EGLDisplay.
113 const QString platformName = QGuiApplication::platformName();
114 if (platformName == QLatin1String("wayland") || platformName == QLatin1String("wayland-egl")) {
115 struct wl_display* wlDisp = nullptr;
116 if (auto* wl = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>()) {
117 wlDisp = wl->display();
118 }
119 if (wlDisp) {
120 GstGLDisplayWayland* displayWl = gst_gl_display_wayland_new_with_display(wlDisp);
121 if (displayWl) {
122 s_display = GST_GL_DISPLAY(displayWl);
123#if GST_CHECK_VERSION(1, 26, 0)
124 // set_foreign(TRUE) is mandatory: Qt owns the EGLDisplay; without it gst calls eglTerminate on Qt's
125 // display.
126 if (GstGLDisplayEGL* derived = gst_gl_display_egl_from_gl_display(s_display)) {
127 gst_gl_display_egl_set_foreign(derived, TRUE);
128 gst_object_unref(derived);
129 }
130#endif
131 }
132 }
133 }
134#endif
135 if (!s_display) {
136 GstGLDisplayEGL* displayEgl = gst_gl_display_egl_new_with_egl_display(eglDisp);
137 if (!displayEgl) {
138 qCWarning(GstGlBridgeLog) << "gst_gl_display_egl_new_with_egl_display failed";
139 return bail("displayEgl");
140 }
141 s_display = GST_GL_DISPLAY(displayEgl);
142 }
143
144 s_context = gst_gl_context_new_wrapped(s_display, reinterpret_cast<guintptr>(eglCtx), GST_GL_PLATFORM_EGL,
145 static_cast<GstGLAPI>(GST_GL_API_GLES2 | GST_GL_API_OPENGL));
146 if (!s_context) {
147 qCWarning(GstGlBridgeLog) << "gst_gl_context_new_wrapped (EGL) failed";
148 gst_clear_object(&s_display);
149 return bail("ctxEgl");
150 }
151#if defined(QGC_GST_BRIDGE_HAS_WAYLAND)
152 const bool isWayland = GST_IS_GL_DISPLAY_WAYLAND(s_display);
153 qCInfo(GstGlBridgeLog) << (isWayland ? "GL bridge primed (Wayland+EGL)" : "GL bridge primed (EGL)");
154#else
155 qCInfo(GstGlBridgeLog) << "GL bridge primed (EGL)";
156#endif
157 } else {
158#if defined(QGC_GST_BRIDGE_HAS_GLX)
159 // Qt's QGLXContext exposes the X11 Display* + GLXContext we need to wrap.
160 auto* glx = qtCtx->nativeInterface<QNativeInterface::QGLXContext>();
161 if (!glx) {
162 qCWarning(GstGlBridgeLog) << "Qt GL context exposes neither EGL nor GLX; GL bridge disabled";
163 return bail("noGlx");
164 }
165 Display* xdisp = nullptr;
166 if (auto* x11 = qGuiApp->nativeInterface<QNativeInterface::QX11Application>()) {
167 xdisp = x11->display();
168 }
169 if (!xdisp) {
170 qCWarning(GstGlBridgeLog) << "X11 Display unresolvable; GL bridge disabled";
171 return bail("xdisp");
172 }
173 GstGLDisplayX11* displayX11 = gst_gl_display_x11_new_with_display(xdisp);
174 if (!displayX11) {
175 qCWarning(GstGlBridgeLog) << "gst_gl_display_x11_new_with_display failed";
176 return bail("displayX11");
177 }
178 s_display = GST_GL_DISPLAY(displayX11);
179 s_context = gst_gl_context_new_wrapped(s_display, reinterpret_cast<guintptr>(glx->nativeContext()),
180 GST_GL_PLATFORM_GLX, static_cast<GstGLAPI>(GST_GL_API_OPENGL));
181 if (!s_context) {
182 qCWarning(GstGlBridgeLog) << "gst_gl_context_new_wrapped (GLX) failed";
183 gst_clear_object(&s_display);
184 return bail("ctxGlx");
185 }
186 qCInfo(GstGlBridgeLog) << "GL bridge primed (GLX)";
187#else
188 qCWarning(GstGlBridgeLog) << "Qt EGLContext unresolvable and GLX bridge not built; GL bridge disabled";
189 return bail("noEglNoGlx");
190#endif
191 }
192
193 s_retry.primed = true;
194 // Don't call gst_gl_context_fill_info(): a freshly wrapped context has no active thread yet, and GStreamer fills
195 // info lazily on first activation.
196 qCDebug(GstGlBridgeLog) << "GL bridge primed: display=" << s_display << "context=" << s_context;
197 return true;
198#else
199 // EGL-only by design; Windows uses the D3D11/D3D12 bridge, macOS/iOS use CVPixelBuffer. No WGL/CGL wrap: forcing
200 // QSG_RHI_BACKEND=opengl on Windows (rare; RHI defaults to D3D11) just falls back to the CPU path here.
201 qCInfo(GstGlBridgeLog) << "GL bridge inactive on this platform (non-EGL)";
202 return false;
203#endif
204}
205
206} // namespace
207
208namespace {
209
210constexpr char kGlAppContextType[] = "gst.gl.app_context";
211const char* const kContextTypes[] = {GST_GL_DISPLAY_CONTEXT_TYPE, kGlAppContextType};
212
213const QLoggingCategory& vtCat(void*)
214{
215 return GstGlBridgeLog();
216}
217
218QMutex& vtMutex(void*)
219{
220 return s_mutex;
221}
222
223bool vtPrime(void*)
224{
225 return primeLocked();
226}
227
228GstObject* vtRefObject(void*, const char* contextType)
229{
230 if (g_strcmp0(contextType, GST_GL_DISPLAY_CONTEXT_TYPE) == 0) {
231 return s_display ? GST_OBJECT(gst_object_ref(s_display)) : nullptr;
232 }
233 return s_context ? GST_OBJECT(gst_object_ref(s_context)) : nullptr;
234}
235
236GstContext* vtBuildContext(void*, const char* contextType, GstObject* object)
237{
238 if (g_strcmp0(contextType, GST_GL_DISPLAY_CONTEXT_TYPE) == 0) {
239 GstContext* ctx = gst_context_new(GST_GL_DISPLAY_CONTEXT_TYPE, TRUE);
240 gst_context_set_gl_display(ctx, GST_GL_DISPLAY(object));
241 return ctx;
242 }
243 GstContext* ctx = gst_context_new(kGlAppContextType, TRUE);
244 GstStructure* s = gst_context_writable_structure(ctx);
245 gst_structure_set(s, "context", GST_TYPE_GL_CONTEXT, GST_GL_CONTEXT(object), NULL);
246 return ctx;
247}
248
249const GstContextBridge::BridgeVTable s_vtable = {
250 "GL", kContextTypes, 2, &vtCat, &vtMutex, &vtPrime, &vtRefObject, &vtBuildContext, nullptr,
251};
252
253} // namespace
254
255bool prime()
256{
257 QMutexLocker lock(&s_mutex);
258 return primeLocked();
259}
260
261GstBusSyncReply handleSyncMessage(GstMessage* message)
262{
263 return GstContextBridge::handleSyncMessage(s_vtable, nullptr, message);
264}
265
266bool answerContextQuery(GstQuery* query)
267{
268 return GstContextBridge::answerContextQuery(s_vtable, nullptr, query);
269}
270
271void reset()
272{
273 QMutexLocker lock(&s_mutex);
274 gst_clear_object(&s_context);
275 gst_clear_object(&s_display);
276 GstBridgePrimeRetry::resetRetry(s_retry);
277 qCDebug(GstGlBridgeLog) << "GL bridge reset";
278}
279
280void rearm()
281{
282 QMutexLocker lock(&s_mutex);
283 if (GstBridgePrimeRetry::rearmAfterExhaustion(s_retry)) {
284 qCInfo(GstGlBridgeLog) << "GL bridge rearm: clearing exhausted retry latch";
285 }
286}
287
288namespace {
289struct GlBridgeRegistrar
290{
291 GlBridgeRegistrar()
292 {
293 GstContextBridge::registerBridge(GstGlBridgeLog(), "GL", &GstGlContextBridge::handleSyncMessage,
294 &GstGlContextBridge::reset);
295 }
296};
297
298static GlBridgeRegistrar s_glBridgeRegistrar;
299} // anonymous namespace
300
301} // namespace GstGlContextBridge
302
303#endif // QGC_HAS_GST_GLMEMORY_GPU_PATH
#define qGuiApp
#define QGC_LOGGING_CATEGORY(name, categoryStr)