QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GstHwVideoBufferFactory.cc
Go to the documentation of this file.
2
3#include <QtCore/QMutex>
4#include <QtCore/QMutexLocker>
5#include <QtCore/QSet>
6#include <QtCore/QString>
7#include <private/qhwvideobuffer_p.h>
8
10#include "QGCLoggingCategory.h"
11
12#if defined(QGC_HAS_GST_DMABUF_GPU_PATH)
13#include <gst/allocators/gstdmabuf.h>
14
16#endif
17#if defined(QGC_HAS_GST_CUDA_GPU_PATH)
18#include <gst/cuda/gstcuda.h>
19
20#include "GstCudaVideoBuffer.h"
21#endif
22#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH)
23#include <gst/gl/gstglmemory.h>
24#include <rhi/qrhi.h>
25
26#include "GstGlVideoBuffer.h"
27#include "QGCRhiCapture.h"
28#endif
29#if defined(QGC_HAS_GST_D3D11_GPU_PATH)
30#include <gst/d3d11/gstd3d11.h>
31
32#include "GstD3D11VideoBuffer.h"
33#endif
34#if defined(QGC_HAS_GST_D3D12_GPU_PATH)
35#include <gst/d3d12/gstd3d12.h>
36
37#include "GstD3D12VideoBuffer.h"
38#endif
39#if defined(QGC_HAS_GST_IOSURFACE_GPU_PATH)
41#endif
42#if defined(QGC_HAS_GST_VULKAN_GPU_PATH)
43#include <vulkan/vulkan.h>
44// `slots` member in gst-vulkan video headers vs Qt's `slots` keyword macro — see GstVulkanVideoBuffer.cc.
45#pragma push_macro("slots")
46#undef slots
47#include <gst/vulkan/gstvkimagememory.h>
48#pragma pop_macro("slots")
49
51#endif
52#if defined(QGC_HAS_GST_AHARDWAREBUFFER_GPU_PATH)
53#include <gst/android/gstandroid.h>
54
56#endif
57
58QGC_LOGGING_CATEGORY(GstHwBufFactoryLog, "Video.GStreamer.HwBuffers.Factory")
59
60
61namespace {
62
63const char* pathName(HwVideoBufferPath path) noexcept
64{
65 switch (path) {
66 case HwVideoBufferPath::DmaBuf: return "DMABuf";
67 case HwVideoBufferPath::GlMemory: return "GL";
68 case HwVideoBufferPath::D3D11: return "D3D11";
69 case HwVideoBufferPath::D3D12: return "D3D12";
70 case HwVideoBufferPath::IOSurface: return "IOSurface";
71 case HwVideoBufferPath::AHardwareBuffer: return "AHWBuf";
72 case HwVideoBufferPath::Vulkan: return "Vulkan";
73 case HwVideoBufferPath::None: break;
74 }
75 return "None";
76}
77
78#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH)
79// GstGlVideoBuffer::mapTextures binds GLMemory only under Qt's OpenGL(ES2) QRhi; the glupload bin still emits GLMemory
80// under a non-GL RHI (forced Vulkan), so gate selection here or mapTextures fails on the render thread → black frame.
81bool glMemoryConsumable() noexcept
82{
83 const QRhi* rhi = QGCRhiCapture::cachedRhi();
84 return !rhi || rhi->backend() == QRhi::OpenGLES2;
85}
86#endif
87
88// Construct the QHwVideoBuffer for an already-resolved path (fast path: skips the gst_is_*_memory ladder). Returns
89// nullptr for paths that need extra context the cache can't carry, forcing a full re-resolve.
90std::unique_ptr<GstHwVideoBuffer> constructForPath(HwVideoBufferPath path, GstSample* sample,
91 [[maybe_unused]] const GstVideoInfo& info,
92 [[maybe_unused]] QVideoFrameFormat format,
93 [[maybe_unused]] const HwVideoBufferContext& context)
94{
95 switch (path) {
97#if defined(QGC_HAS_GST_DMABUF_GPU_PATH)
98 if (context.dmaBufEglDisplay == EGL_NO_DISPLAY) {
99 return nullptr;
100 }
101 return std::make_unique<GstDmaBufVideoBuffer>(sample, info, format, context.dmaBufEglDisplay);
102#else
103 break;
104#endif
106#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH)
107 return std::make_unique<GstGlVideoBuffer>(sample, info, format);
108#else
109 break;
110#endif
112#if defined(QGC_HAS_GST_D3D11_GPU_PATH)
113 return std::make_unique<GstD3D11VideoBuffer>(sample, info, format);
114#else
115 break;
116#endif
118#if defined(QGC_HAS_GST_D3D12_GPU_PATH)
119 return std::make_unique<GstD3D12VideoBuffer>(sample, info, format);
120#else
121 break;
122#endif
124#if defined(QGC_HAS_GST_VULKAN_GPU_PATH)
125 return std::make_unique<GstVulkanVideoBuffer>(sample, info, format);
126#else
127 break;
128#endif
130#if defined(QGC_HAS_GST_IOSURFACE_GPU_PATH)
131 return std::make_unique<GstIOSurfaceVideoBuffer>(sample, info, format);
132#else
133 break;
134#endif
136#if defined(QGC_HAS_GST_AHARDWAREBUFFER_GPU_PATH)
137 if (context.ahbEglDisplay == EGL_NO_DISPLAY) {
138 return nullptr;
139 }
140 format.setPixelFormat(QVideoFrameFormat::Format_SamplerExternalOES);
141 return std::make_unique<GstAHardwareBufferVideoBuffer>(sample, info, format, context.ahbEglDisplay);
142#else
143 break;
144#endif
146 break;
147 }
148
149 return nullptr;
150}
151
152bool memoryMatchesPath(HwVideoBufferPath path, GstMemory* mem, [[maybe_unused]] const HwVideoBufferContext& context)
153{
154 if (!mem) {
155 return false;
156 }
157
158 switch (path) {
160#if defined(QGC_HAS_GST_DMABUF_GPU_PATH)
161 return context.dmaBufEglDisplay != EGL_NO_DISPLAY && gst_is_dmabuf_memory(mem);
162#else
163 break;
164#endif
166#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH)
167 return gst_is_gl_memory(mem) && glMemoryConsumable();
168#else
169 break;
170#endif
172#if defined(QGC_HAS_GST_D3D11_GPU_PATH)
173 return gst_is_d3d11_memory(mem);
174#else
175 break;
176#endif
178#if defined(QGC_HAS_GST_D3D12_GPU_PATH)
179 return gst_is_d3d12_memory(mem);
180#else
181 break;
182#endif
184#if defined(QGC_HAS_GST_VULKAN_GPU_PATH)
185 return gst_is_vulkan_image_memory(mem);
186#else
187 break;
188#endif
190#if defined(QGC_HAS_GST_IOSURFACE_GPU_PATH)
191 return mem->allocator && g_strcmp0(mem->allocator->mem_type, "AppleCoreVideoMemory") == 0;
192#else
193 break;
194#endif
196#if defined(QGC_HAS_GST_AHARDWAREBUFFER_GPU_PATH)
197 return context.ahbEglDisplay != EGL_NO_DISPLAY && gst_is_ahardware_buffer_memory(mem);
198#else
199 break;
200#endif
202 break;
203 }
204
205 return false;
206}
207
208} // namespace
209
210std::unique_ptr<QHwVideoBuffer> makeHwVideoBuffer(GstSample* sample, [[maybe_unused]] const GstVideoInfo& info,
211 [[maybe_unused]] QVideoFrameFormat format,
212 const HwVideoBufferContext& context, HwVideoBufferPath& matchedPath,
213 HwResolvedPathCache* cache)
214{
215 matchedPath = HwVideoBufferPath::None;
216 if (!context.gpuEnabled || !sample) {
217 return nullptr;
218 }
219
220 GstBuffer* buffer = gst_sample_get_buffer(sample);
221 if (!buffer) {
222 return nullptr;
223 }
224
225 // Plane 0's allocator is the dispatch key; gst-video buffers from a single decoder use one allocator type across
226 // all planes.
227 GstMemory* mem0 = gst_buffer_peek_memory(buffer, 0);
228 if (!mem0) {
229 return nullptr;
230 }
231
232 // Validate before commit — failure resets matchedPath so per-path counters don't double-count.
233 auto buildOrFallback = [&matchedPath, &info, cache](auto&& buf) -> std::unique_ptr<QHwVideoBuffer> {
234 if (!buf || !buf->validatePlaneHandles()) {
236 static std::atomic<quint64> s_validateFails{0};
237 const quint64 c = s_validateFails.fetch_add(1, std::memory_order_relaxed) + 1;
238 if ((c & 0x3F) == 1) {
239 qCWarning(GstHwBufFactoryLog)
240 << "validatePlaneHandles failed — CPU memcpy fallback (total:" << c << ")";
241 }
242 if (cache) {
243 cache->validated = false;
244 }
245 matchedPath = HwVideoBufferPath::None;
246 return nullptr;
247 }
248 if (cache && !cache->validated) {
249 qCDebug(GstHwBufFactoryLog).noquote()
250 << "zero-copy path selected:" << pathName(matchedPath)
251 << "format=" << gst_video_format_to_string(GST_VIDEO_INFO_FORMAT(&info))
252 << GST_VIDEO_INFO_WIDTH(&info) << "x" << GST_VIDEO_INFO_HEIGHT(&info);
253 }
254 if (cache) {
255 cache->path = matchedPath;
256 cache->validated = true;
257 }
258 return buf;
259 };
260
261 // Fast path: a path resolved by a validated sample earlier this epoch skips the predicate ladder.
262 // Allocator and handle checks stay per-buffer: decoders can swap memory or recycle bad handles under stable caps.
263 if (cache && cache->validated && cache->path != HwVideoBufferPath::None) {
264 if (memoryMatchesPath(cache->path, mem0, context)) {
265 matchedPath = cache->path;
266 if (auto buf = constructForPath(cache->path, sample, info, format, context)) {
267 return buildOrFallback(std::move(buf));
268 }
269 matchedPath = HwVideoBufferPath::None;
270 }
271 cache->validated = false;
272 }
273
274#if defined(QGC_HAS_GST_CUDA_GPU_PATH)
275 // NVMM/CUDA memory exports to a DMABuf fd (gst_cuda_memory_export / Jetson NvBufSurfaceMapEglImage) and then reuses
276 // the DMABuf EGLImage path, avoiding a separate CUDA-GL interop. Scaffold: requires NVIDIA hardware to validate.
277 if (gst_is_cuda_memory(mem0)) {
278 // One-shot capability latch: desktop dGPU drivers can reject the export every frame, so probe once and skip
279 // the per-frame retry thereafter (CPU fallback).
280 static std::atomic<bool> s_cudaExportUnsupported{false};
281 if (s_cudaExportUnsupported.load(std::memory_order_relaxed)) {
282 return nullptr;
283 }
284 matchedPath = HwVideoBufferPath::DmaBuf;
285 if (auto buf = buildOrFallback(GstCudaVideoBuffer::exportToDmaBuf(sample, info, format, context))) {
286 return buf;
287 }
288 if (!s_cudaExportUnsupported.exchange(true, std::memory_order_relaxed)) {
291 qCWarning(GstHwBufFactoryLog) << "CUDA->DMABuf export unsupported on this driver — CPU fallback for the"
292 << "remainder of the process";
293 }
294 matchedPath = HwVideoBufferPath::None;
295 return nullptr;
296 }
297#endif
298
299#if defined(QGC_HAS_GST_DMABUF_GPU_PATH)
300 if (context.dmaBufEglDisplay != EGL_NO_DISPLAY && gst_is_dmabuf_memory(mem0)) {
301 matchedPath = HwVideoBufferPath::DmaBuf;
302 return buildOrFallback(std::make_unique<GstDmaBufVideoBuffer>(sample, info, format, context.dmaBufEglDisplay));
303 }
304#endif
305
306#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH)
307 if (gst_is_gl_memory(mem0)) {
308 if (!glMemoryConsumable()) {
309 static std::atomic<bool> s_warned{false};
310 if (!s_warned.exchange(true, std::memory_order_relaxed)) {
311 qCWarning(GstHwBufFactoryLog) << "GLMemory frame but QRhi backend is not OpenGL — routing to CPU pool";
312 }
315 return nullptr;
316 }
317 matchedPath = HwVideoBufferPath::GlMemory;
318 return buildOrFallback(std::make_unique<GstGlVideoBuffer>(sample, info, format));
319 }
320#endif
321
322#if defined(QGC_HAS_GST_D3D11_GPU_PATH)
323 if (gst_is_d3d11_memory(mem0)) {
324 matchedPath = HwVideoBufferPath::D3D11;
325 return buildOrFallback(std::make_unique<GstD3D11VideoBuffer>(sample, info, format));
326 }
327#endif
328
329#if defined(QGC_HAS_GST_D3D12_GPU_PATH)
330 if (gst_is_d3d12_memory(mem0)) {
331 matchedPath = HwVideoBufferPath::D3D12;
332 return buildOrFallback(std::make_unique<GstD3D12VideoBuffer>(sample, info, format));
333 }
334#endif
335
336#if defined(QGC_HAS_GST_VULKAN_GPU_PATH)
337 if (gst_is_vulkan_image_memory(mem0)) {
338 matchedPath = HwVideoBufferPath::Vulkan;
339 return buildOrFallback(std::make_unique<GstVulkanVideoBuffer>(sample, info, format));
340 }
341#endif
342
343#if defined(QGC_HAS_GST_IOSURFACE_GPU_PATH)
344 // String-compare the allocator name; gst-applemedia exports no public gst_is_apple_core_video_memory() predicate.
345 if (mem0->allocator && g_strcmp0(mem0->allocator->mem_type, "AppleCoreVideoMemory") == 0) {
346 matchedPath = HwVideoBufferPath::IOSurface;
347 return buildOrFallback(std::make_unique<GstIOSurfaceVideoBuffer>(sample, info, format));
348 }
349#endif
350
351#if defined(QGC_HAS_GST_AHARDWAREBUFFER_GPU_PATH)
352 if (context.ahbEglDisplay != EGL_NO_DISPLAY && gst_is_ahardware_buffer_memory(mem0)) {
354 // GL_TEXTURE_EXTERNAL_OES requires the SamplerExternalOES pixel format for Qt's shader path.
355 format.setPixelFormat(QVideoFrameFormat::Format_SamplerExternalOES);
356 return buildOrFallback(
357 std::make_unique<GstAHardwareBufferVideoBuffer>(sample, info, format, context.ahbEglDisplay));
358 }
359#endif
360
361 {
362 static QSet<QString> s_seen;
363 static QMutex s_mtx;
364 const QString memType =
365 mem0->allocator ? QString::fromUtf8(mem0->allocator->mem_type) : QStringLiteral("<no-allocator>");
366 QMutexLocker lock(&s_mtx);
367 if (!s_seen.contains(memType)) {
368 s_seen.insert(memType);
369 qCDebug(GstHwBufFactoryLog) << "no zero-copy path for memory type" << memType
370 << "— falling back to CPU memcpy";
371 }
372 }
375 return nullptr;
376}
std::unique_ptr< QHwVideoBuffer > makeHwVideoBuffer(GstSample *sample, const GstVideoInfo &info, QVideoFrameFormat format, const HwVideoBufferContext &context, HwVideoBufferPath &matchedPath, HwResolvedPathCache *cache)
HwVideoBufferPath
Identifies which GPU path was chosen; used by the adapter to increment the right counter.
#define QGC_LOGGING_CATEGORY(name, categoryStr)
void recordFallbackReason(HwVideoBufferPath attemptedPath, HwFallbackReason reason) noexcept
Per-(path,reason) fallback accounting; lets a bug report show why a path demoted to CPU.
QByteArray format(const QList< LogEntry > &entries, int fmt)
QRhi * cachedRhi() noexcept
Cached QRhi* maintained by sceneGraph signals; safe from any thread via acquire ordering.
Platform context for the factory; encapsulates EGL handles so callers don't need path-specific ifdefs...