QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
HwBuffers.cc
Go to the documentation of this file.
1#include "HwBuffers.h"
2
3#if defined(QGC_HAS_ANY_GPU_PATH)
5#endif
6#if defined(QGC_HAS_GST_AHARDWAREBUFFER_GPU_PATH)
8#endif
9#if defined(QGC_HAS_GST_DMABUF_GPU_PATH)
11#endif
12#if defined(QGC_HAS_GST_D3D11_GPU_PATH)
14#include "GstD3D11VideoBuffer.h"
15#endif
16#if defined(QGC_HAS_GST_D3D12_GPU_PATH)
18#include "GstD3D12VideoBuffer.h"
19#endif
20#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH)
21#include "GstGlContextBridge.h"
22#endif
23#if defined(QGC_HAS_GST_VULKAN_GPU_PATH)
25#endif
26#if defined(QGC_HAS_GST_IOSURFACE_GPU_PATH)
28#endif
29#if defined(QGC_HAS_ANY_GPU_PATH)
30#include "QGCRhiCapture.h"
31#endif
32
33#if defined(QGC_HAS_ANY_GPU_PATH)
34#include "QGCLoggingCategory.h"
35QGC_LOGGING_CATEGORY(HwBuffersFacadeLog, "Video.GStreamer.HwBuffers.Facade")
36#endif
37#if defined(QGC_HAS_GST_DMABUF_GPU_PATH) || defined(QGC_HAS_GST_AHARDWAREBUFFER_GPU_PATH)
38#include <EGL/egl.h>
39#include <QtGui/QGuiApplication>
40#include <qpa/qplatformnativeinterface.h>
41
42#include "GstEglHelpers.h"
43#endif
44
45#include <QtCore/QByteArray>
46#include <array>
47#include <utility>
48
49#include "QGCLoggingCategory.h"
50
51namespace HwBuffers {
52
53QGC_LOGGING_CATEGORY(HwBuffersConfigLog, "Video.GStreamer.HwBuffers.Config")
54
55#if defined(QGC_HAS_ANY_GPU_PATH)
56namespace {
57// Compile-time table of enabled GPU paths; iterated by resetCachedGpuResources/formatPathStats to avoid repeating the
58// ifdef ladder.
59struct GpuPathEntry
60{
62 const char* label;
63};
64
65constexpr std::array<GpuPathEntry, 0
66#if defined(QGC_HAS_GST_DMABUF_GPU_PATH)
67 + 1
68#endif
69#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH)
70 + 1
71#endif
72#if defined(QGC_HAS_GST_D3D11_GPU_PATH)
73 + 1
74#endif
75#if defined(QGC_HAS_GST_D3D12_GPU_PATH)
76 + 1
77#endif
78#if defined(QGC_HAS_GST_IOSURFACE_GPU_PATH)
79 + 1
80#endif
81#if defined(QGC_HAS_GST_AHARDWAREBUFFER_GPU_PATH)
82 + 1
83#endif
84#if defined(QGC_HAS_GST_VULKAN_GPU_PATH)
85 + 1
86#endif
87 >
88 kEnabledPaths = {{
89#if defined(QGC_HAS_GST_DMABUF_GPU_PATH)
90 {HwVideoBufferPath::DmaBuf, "DMABuf"},
91#endif
92#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH)
94#endif
95#if defined(QGC_HAS_GST_D3D11_GPU_PATH)
96 {HwVideoBufferPath::D3D11, "D3D11"},
97#endif
98#if defined(QGC_HAS_GST_D3D12_GPU_PATH)
99 {HwVideoBufferPath::D3D12, "D3D12"},
100#endif
101#if defined(QGC_HAS_GST_IOSURFACE_GPU_PATH)
102 {HwVideoBufferPath::IOSurface, "IOSurface"},
103#endif
104#if defined(QGC_HAS_GST_AHARDWAREBUFFER_GPU_PATH)
106#endif
107#if defined(QGC_HAS_GST_VULKAN_GPU_PATH)
108 {HwVideoBufferPath::Vulkan, "Vulkan"},
109#endif
110 }};
111} // namespace
112#endif
113
114void dispatchBusMessage(GstMessage* msg) noexcept
115{
116 if (!msg || GST_MESSAGE_TYPE(msg) != GST_MESSAGE_ERROR)
117 return;
118
119 // Only a real device loss should drop every GPU cache; ordinary stream errors must not. There is no structured
120 // device-lost event, so classify by the D3D/DXGI device-removed/reset text surfaced in the error/debug strings.
121 GError* err = nullptr;
122 gchar* debug = nullptr;
123 gst_message_parse_error(msg, &err, &debug);
124
125 const auto mentionsDeviceLoss = [](const gchar* text) {
126 return text && (g_strstr_len(text, -1, "DEVICE_REMOVED") || g_strstr_len(text, -1, "DEVICE_RESET") ||
127 g_strstr_len(text, -1, "device removed") || g_strstr_len(text, -1, "device reset"));
128 };
129 const bool deviceLost = mentionsDeviceLoss(err ? err->message : nullptr) || mentionsDeviceLoss(debug);
130
131 if (err)
132 g_error_free(err);
133 g_free(debug);
134
135 if (deviceLost)
137}
138
139void initializeOnce() noexcept
140{
141 // Bridges self-register at static-init; function kept as a stable call site for future lazy init.
142}
143
145{
146 static const HwBufferEnvConfig cfg = []() noexcept {
147 const auto truthy = [](const char* name, bool dflt) noexcept {
148 const QByteArray v = qgetenv(name).trimmed().toLower();
149 if (v.isEmpty()) {
150 return dflt;
151 }
152 return v != "0" && v != "false" && v != "off" && v != "no";
153 };
155 c.dmaBufCache = truthy("QGC_GST_DMABUF_CACHE", false);
156 c.dmaBufSingleEglImage = truthy("QGC_GST_DMABUF_SINGLE_EGLIMAGE", true);
157 c.dmaBufNoMmapFence = truthy("QGC_GST_DMABUF_NO_MMAP_FENCE", false);
158 c.offerDmaDrmLinear = truthy("QGC_GST_OFFER_DMA_DRM_LINEAR", false);
159 qCInfo(HwBuffersConfigLog).nospace()
160 << "HwBuffer env config: QGC_GST_DMABUF_CACHE=" << c.dmaBufCache
161 << " QGC_GST_DMABUF_SINGLE_EGLIMAGE=" << c.dmaBufSingleEglImage
162 << " QGC_GST_DMABUF_NO_MMAP_FENCE=" << c.dmaBufNoMmapFence
163 << " QGC_GST_OFFER_DMA_DRM_LINEAR=" << c.offerDmaDrmLinear;
164 return c;
165 }();
166 return cfg;
167}
168
169GstBusSyncReply onBusSyncMessage(GstBus* /*bus*/, GstMessage* msg, gpointer /*userData*/) noexcept
170{
171#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH) || defined(QGC_HAS_GST_D3D11_GPU_PATH) || \
172 defined(QGC_HAS_GST_D3D12_GPU_PATH) || defined(QGC_HAS_GST_VULKAN_GPU_PATH)
173 return GstContextBridgeRegistry::dispatchBridges(msg);
174#else
175 Q_UNUSED(msg);
176 return GST_BUS_PASS;
177#endif
178}
179
180void onPipelineRestart() noexcept
181{
183 // GL is the only path needing a pipeline-restart rearm (re-prime the shared GstGLContext).
184#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH)
185 GstGlContextBridge::rearm();
186#endif
187#if defined(QGC_HAS_GST_VULKAN_GPU_PATH)
188 GstVulkanContextBridge::rearm();
189#endif
190}
191
193{
194#if defined(QGC_HAS_ANY_GPU_PATH)
195 GstContextBridgeRegistry::resetAllBridges();
196 GstContextBridgeRegistry::resetAllCaches();
197#endif
198#if defined(QGC_HAS_GST_DMABUF_GPU_PATH) || defined(QGC_HAS_GST_AHARDWAREBUFFER_GPU_PATH)
199 GstEglHelpers::resetExtensionCache();
200#endif
201}
202
203void connectMainWindow(QQuickWindow* window) noexcept
204{
205#if defined(QGC_HAS_ANY_GPU_PATH)
207#else
208 Q_UNUSED(window);
209#endif
210}
211
212#if defined(QGC_HAS_ANY_GPU_PATH)
213HwVideoBufferContext makeAdapterContext(bool gpuEnabled) noexcept
214{
216 ctx.gpuEnabled = gpuEnabled;
217 if (!gpuEnabled) {
218 return ctx;
219 }
220
221#if defined(QGC_HAS_GST_DMABUF_GPU_PATH)
222 // Construction-time hint only; render-time mapTextures() prefers eglGetCurrentDisplay() to avoid xcb_egl EGLDisplay
223 // mismatches.
224 EGLDisplay dpy = EGL_NO_DISPLAY;
225 const QString platform = QGuiApplication::platformName();
226 if (platform == QLatin1String("wayland") || platform == QLatin1String("wayland-egl")) {
227 if (auto* ni = QGuiApplication::platformNativeInterface()) {
228 dpy = static_cast<EGLDisplay>(ni->nativeResourceForIntegration("egldisplay"));
229 }
230 }
231 if (dpy == EGL_NO_DISPLAY) {
232 dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
233 }
234 if (dpy == EGL_NO_DISPLAY) {
235 qCWarning(HwBuffersFacadeLog) << "GPU zero-copy requested but EGLDisplay unavailable on platform" << platform
236 << "— DMABuf path disabled";
237 } else {
238 qCInfo(HwBuffersFacadeLog) << "DMABuf zero-copy path available on" << platform
239 << "— actual path chosen at caps negotiation";
240 }
241 ctx.dmaBufEglDisplay = dpy;
242#endif
243
244#if defined(QGC_HAS_GST_AHARDWAREBUFFER_GPU_PATH)
245 ctx.ahbEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
246 if (ctx.ahbEglDisplay == EGL_NO_DISPLAY) {
247 qCWarning(HwBuffersFacadeLog) << "AHardwareBuffer path: EGLDisplay unavailable";
248 } else {
249 qCInfo(HwBuffersFacadeLog) << "AHardwareBuffer zero-copy path available"
250 << "— actual path chosen at caps negotiation";
251 }
252#endif
253
254#if defined(QGC_HAS_GST_D3D11_GPU_PATH)
255 qCInfo(HwBuffersFacadeLog) << "D3D11 zero-copy path available — actual path chosen at caps negotiation";
256#endif
257#if defined(QGC_HAS_GST_D3D12_GPU_PATH)
258 qCInfo(HwBuffersFacadeLog) << "D3D12 zero-copy path available — actual path chosen at caps negotiation";
259#endif
260#if defined(QGC_HAS_GST_IOSURFACE_GPU_PATH)
261 qCInfo(HwBuffersFacadeLog) << "IOSurface zero-copy path available — actual path chosen at caps negotiation";
262#endif
263#if defined(QGC_HAS_GST_VULKAN_GPU_PATH)
264 qCInfo(HwBuffersFacadeLog) << "Vulkan zero-copy path available (active only when QRhi uses the Vulkan backend)";
265#endif
266
267 return ctx;
268}
269#endif // QGC_HAS_ANY_GPU_PATH
270
271bool answerSinkBinContextQuery(GstQuery* query) noexcept
272{
273 bool handled = false;
274#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH)
275 handled = GstGlContextBridge::answerContextQuery(query);
276#endif
277#if defined(QGC_HAS_GST_VULKAN_GPU_PATH)
278 if (!handled) {
279 handled = GstVulkanContextBridge::answerContextQuery(query);
280 }
281#endif
282#if defined(QGC_HAS_GST_D3D11_GPU_PATH)
283 if (!handled) {
284 handled = GstD3D11ContextBridge::answerContextQuery(query);
285 }
286#endif
287#if defined(QGC_HAS_GST_D3D12_GPU_PATH)
288 if (!handled) {
289 handled = GstD3D12ContextBridge::answerContextQuery(query);
290 }
291#endif
292 Q_UNUSED(query);
293 return handled;
294}
295
296PathStats formatPathStats(bool reset) noexcept
297{
298 PathStats out;
299#if defined(QGC_HAS_ANY_GPU_PATH)
300 for (const auto& entry : kEnabledPaths) {
301 const quint64 delivered = reset ? GstHwPathTelemetry::takeDeliveredCount(entry.path)
303 const quint64 failures = reset ? GstHwPathTelemetry::takeMapFailureCount(entry.path)
305 // Teardown (reset) uses expanded label so operators can copy the failures field into bug reports.
306 if (reset) {
307 out.line +=
308 QStringLiteral(" %1:%2 %1-failures:%3").arg(QLatin1String(entry.label)).arg(delivered).arg(failures);
309 } else {
310 out.line += QStringLiteral(" %1:%2/%3").arg(QLatin1String(entry.label)).arg(delivered).arg(failures);
311 }
313 }
314#else
315 Q_UNUSED(reset);
316#endif
317 return out;
318}
319
320QString takeExtraPathStats() noexcept
321{
322 QString out;
323#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH)
325 quint64 glGpuWaits = 0;
326 const quint64 glCpuWaits = GstHwPathTelemetry::takeSyncWaitCounts(HwVideoBufferPath::GlMemory, glGpuWaits);
327 out += QStringLiteral(" GL-reuse:%1 GL-wait[gpu/cpu]:%2/%3").arg(glReuse).arg(glGpuWaits).arg(glCpuWaits);
328#endif
329#if defined(QGC_HAS_GST_VULKAN_GPU_PATH)
330 quint64 vkGpuWaits = 0;
331 const quint64 vkCpuWaits = GstHwPathTelemetry::takeSyncWaitCounts(HwVideoBufferPath::Vulkan, vkGpuWaits);
332 out += QStringLiteral(" Vulkan-wait[gpu/cpu]:%1/%2").arg(vkGpuWaits).arg(vkCpuWaits);
333#endif
334#if defined(QGC_HAS_GST_DMABUF_GPU_PATH)
335 out += QStringLiteral(" DMABuf-fence-timeouts:%1 DMABuf-mmap-barriers:%2 DMABuf-explicit-fence-waits:%3")
339#endif
340#if defined(QGC_HAS_ANY_GPU_PATH)
341 static constexpr std::pair<GstHwPathTelemetry::HwFallbackReason, const char*> kReasons[] = {
352 };
353 for (const auto& entry : kEnabledPaths) {
354 const quint64 ewmaUs = GstHwPathTelemetry::peekMapDurationUsEwma(entry.path);
355 if (ewmaUs > 0) {
356 out += QStringLiteral(" %1-map-us:%2").arg(QLatin1String(entry.label)).arg(ewmaUs);
357 }
358 const quint64 demotions = GstHwPathTelemetry::takeStreamDemotions(entry.path);
359 if (demotions > 0) {
360 out += QStringLiteral(" %1-demotions:%2").arg(QLatin1String(entry.label)).arg(demotions);
361 }
362 QString reasonBreakdown;
363 for (const auto& [reason, label] : kReasons) {
364 const quint64 n = GstHwPathTelemetry::takeFallbackReason(entry.path, reason);
365 if (n > 0) {
366 reasonBreakdown += QStringLiteral("%1=%2,").arg(QLatin1String(label)).arg(n);
367 }
368 }
369 if (!reasonBreakdown.isEmpty()) {
370 reasonBreakdown.chop(1);
371 out += QStringLiteral(" %1-fallback[%2]").arg(QLatin1String(entry.label)).arg(reasonBreakdown);
372 }
373 }
374 // None-path accumulates UnknownMemType (no allocator matched any compiled path).
375 {
376 const quint64 unknownMem =
379 if (unknownMem > 0) {
380 out += QStringLiteral(" None-fallback[unknown-mem=%1]").arg(unknownMem);
381 }
383 if (cpuDemotions > 0) {
384 out += QStringLiteral(" None-demotions:%1").arg(cpuDemotions);
385 }
386 }
387#endif
388 return out;
389}
390
391} // namespace HwBuffers
std::atomic< quint64 > delivered
HwVideoBufferPath
Identifies which GPU path was chosen; used by the adapter to increment the right counter.
#define QGC_LOGGING_CATEGORY(name, categoryStr)
quint64 peekMapFailureCount(HwVideoBufferPath path) noexcept
quint64 takeExplicitFenceWaits(HwVideoBufferPath path) noexcept
quint64 takeTextureReuseHits(HwVideoBufferPath path) noexcept
quint64 takeSyncWaitCounts(HwVideoBufferPath path, quint64 &gpuWaits) noexcept
Reads-and-resets CPU waits; writes GPU waits into gpuWaits.
quint64 peekMapDurationUsEwma(HwVideoBufferPath path) noexcept
quint64 takeFenceTimeouts(HwVideoBufferPath path) noexcept
quint64 takeStreamDemotions(HwVideoBufferPath negotiated) noexcept
quint64 peekDeliveredCount(HwVideoBufferPath path) noexcept
quint64 takeMapFailureCount(HwVideoBufferPath path) noexcept
quint64 takeDeliveredCount(HwVideoBufferPath path) noexcept
quint64 takeFallbackReason(HwVideoBufferPath attemptedPath, HwFallbackReason reason) noexcept
quint64 takeMmapBarrierHits(HwVideoBufferPath path) noexcept
void initializeOnce() noexcept
One-time process init; single call site for future lazy bridge registration.
Definition HwBuffers.cc:139
bool answerSinkBinContextQuery(GstQuery *query) noexcept
Synchronously answer GST_QUERY_CONTEXT (gst.gl.GLDisplay/app_context); false -> let bus NEED_CONTEXT ...
Definition HwBuffers.cc:271
QString takeExtraPathStats() noexcept
Path-specific extras after formatPathStats (GL reuse/sync waits); reads-and-clears,...
Definition HwBuffers.cc:320
void onPipelineRestart() noexcept
Pipeline-restart hook; re-arms one-shot priming latches so a restart can prime on the next NEED_CONTE...
Definition HwBuffers.cc:180
void resetCachedGpuResources() noexcept
Drop process-wide native GPU handles tied to the current Qt scene-graph device/context.
Definition HwBuffers.cc:192
void connectMainWindow(QQuickWindow *window) noexcept
Wire the main QQuickWindow into the RHI-capture path so snapshots follow its QRhi; no-op without GPU.
Definition HwBuffers.cc:203
GstBusSyncReply onBusSyncMessage(GstBus *, GstMessage *msg, gpointer) noexcept
Bus sync handler (GstBusSyncHandler) installed on every pipeline; no-op when no GPU path compiled.
Definition HwBuffers.cc:169
void dispatchBusMessage(GstMessage *msg) noexcept
Receiver-side bus hook; drops cached GPU devices on GST_MESSAGE_ERROR. No-op when no GPU paths compil...
Definition HwBuffers.cc:114
const HwBufferEnvConfig & hwBufferEnvConfig() noexcept
Lazily parses + logs the toggle config on first call; thread-safe via static-init guarantees.
Definition HwBuffers.cc:144
PathStats formatPathStats(bool reset) noexcept
Definition HwBuffers.cc:296
void connectWindow(QQuickWindow *window)
Formatted per-path counters + delivered total; reset=true reads-and-clears (teardown),...
Definition HwBuffers.h:62
Platform context for the factory; encapsulates EGL handles so callers don't need path-specific ifdefs...