QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GstGlVideoBuffer.cc
Go to the documentation of this file.
1#include "GstGlVideoBuffer.h"
2
4#include "GstHwImportCache.h"
7#include "GstHwVideoBuffer.h"
8
9#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH)
10
11#include <QtCore/QLoggingCategory>
12#include <QtCore/QSize>
13#include <gst/gl/gl.h>
14#include <gst/gl/gstglbasememory.h>
15#include <gst/gl/gstglmemory.h>
16#include <gst/gl/gstglsyncmeta.h>
17#include <gst/video/video.h>
18#include <private/qvideotexturehelper_p.h>
19#include <rhi/qrhi.h>
20
21#include "QGCLoggingCategory.h"
22
23// Qt's portable GL types (GLuint et al.) — works on Linux/macOS/Android without GLES2 SDK headers.
24#include <QtGui/qopengl.h>
25#include <algorithm>
26#include <array>
27
28QGC_LOGGING_CATEGORY(GstGlBufLog, "Video.GStreamer.HwBuffers.GstGlBuf")
29
30namespace {
31
33
34class FrameTextures final : public GstGlFrameTextures
35{
36public:
38
40
41 // Reuse-eligible when ids match: gst-gl rotates ids within a fixed pool, so the QRhiTexture view transparently
42 // samples new data.
43 bool matches(QRhi* rhi, QSize size, QVideoFrameFormat::PixelFormat pixelFormat,
44 const std::array<GLuint, kMaxPlanes>& names, int count) const
45 {
46 if (_rhi != rhi || _size != size || _pixelFormat != pixelFormat || _count != count) {
47 return false;
48 }
49 for (int i = 0; i < _count; ++i) {
50 if (_names[i] == 0 || _names[i] != names[i] || !_textures[i]) {
51 return false;
52 }
53 }
54 return true;
55 }
56};
57
59
60// Pool-ring recycle key: the gst-gl texture id tuple for a frame. gst-gl rotates a fixed set of ids, so re-seeing a
61// tuple means a pool slot recycled (the immediate `old`-bundle reuse below only covers the last frame, not the ring).
62struct GlTexKey
63{
64 std::array<GLuint, kMaxPlanes> names{};
65 int count = 0;
66
67 bool operator==(const GlTexKey& o) const noexcept { return count == o.count && names == o.names; }
68};
69
70struct GlTexKeyHash
71{
72 std::size_t operator()(const GlTexKey& k) const noexcept
73 {
74 std::size_t h = std::hash<int>{}(k.count);
75 for (int i = 0; i < k.count; ++i) {
76 h ^= std::hash<GLuint>{}(k.names[i]) + 0x9e3779b97f4a7c15ULL + (h << 6) + (h >> 2);
77 }
78 return h;
79 }
80};
81
82// Render-thread-confined (mapTextures runs on the QRhi thread): no locking needed. Values are non-owning markers — the
83// GL textures belong to gst-gl, so the deleter is a no-op; the cache only tracks ring membership for reuse telemetry.
84constexpr std::size_t kGlRingCapacity = 8;
85GstHw::GstHwImportCache<GlTexKey, char, GlTexKeyHash> s_glRingCache{kGlRingCapacity, [](const GlTexKey&, char&) {}};
86
87} // namespace
88
89GstGlVideoBuffer::GstGlVideoBuffer(GstSample* sample, const GstVideoInfo& videoInfo, const QVideoFrameFormat& format)
90 : GstHwVideoBuffer(QVideoFrame::RhiTextureHandle, sample, videoInfo, format)
91{}
92
93bool GstGlVideoBuffer::validatePlaneHandles() const
94{
95 // Allocator-only — GLuint check needs GST_MAP_GL, too expensive on streaming thread.
96 return validatePlanes([](GstMemory* mem) { return mem && gst_is_gl_memory(mem); });
97}
98
99QVideoFrameTexturesUPtr GstGlVideoBuffer::mapTextures(QRhi& rhi, QVideoFrameTexturesUPtr& old)
100{
102 // QRhi::OpenGLES2 covers both desktop GL and GLES — Qt collapses both; there is no separate QRhi::OpenGL.
103 GstBuffer* buffer = nullptr;
104 if (!checkMapPreconditions(rhi, static_cast<int>(QRhi::OpenGLES2), GstGlBufLog(), s_diag, buffer)) {
105 return GstHwPathTelemetry::fail(HwVideoBufferPath::GlMemory);
106 }
107
108 // Defensive: custom RHI integrations may not have Qt's GL context current on QSGRenderThread entry.
109 rhi.makeThreadLocalNativeContextCurrent();
110
111 GstMemory* mem0 = gst_buffer_peek_memory(buffer, 0);
112 if (!mem0 || !gst_is_gl_memory(mem0)) {
113 return GstHwPathTelemetry::fail(HwVideoBufferPath::GlMemory);
114 }
115
116 GstVideoFrame frame = {};
117 // GST_MAP_GL (a gst-gl extension flag) OR'd with GST_MAP_READ triggers GstGLMemory upload.
118 if (!gst_video_frame_map(&frame, &_videoInfo, buffer, static_cast<GstMapFlags>(GST_MAP_READ | GST_MAP_GL))) {
119 qCWarning(GstGlBufLog) << "gst_video_frame_map(GST_MAP_READ | GST_MAP_GL) failed";
122 return GstHwPathTelemetry::fail(HwVideoBufferPath::GlMemory);
123 }
124
125 // Mirror Qt's mapFromGlTexture: set_sync_point + GPU-side wait; synthesize meta when upstream omits one.
126 GstGLContext* glCtx = GST_GL_BASE_MEMORY_CAST(mem0)->context;
127 if (glCtx) {
128 GstGLSyncMeta* syncMeta = gst_buffer_get_gl_sync_meta(buffer);
129 GstBuffer* throwaway = nullptr;
130 if (!syncMeta) {
131 throwaway = gst_buffer_new();
132 if (!throwaway) {
133 qCWarning(GstGlBufLog) << "gst_buffer_new() failed while creating GL sync meta holder";
136 gst_video_frame_unmap(&frame);
137 return GstHwPathTelemetry::fail(HwVideoBufferPath::GlMemory);
138 }
139 syncMeta = gst_buffer_add_gl_sync_meta(glCtx, throwaway);
140 }
141 if (syncMeta) {
142 gst_gl_sync_meta_set_sync_point(syncMeta, glCtx);
143 gst_gl_sync_meta_wait(syncMeta, glCtx);
145 }
146 if (throwaway) {
147 gst_buffer_unref(throwaway);
148 }
149 }
150
151 const int planeCount = std::clamp(int(GST_VIDEO_FRAME_N_PLANES(&frame)), 1, kMaxPlanes);
152 std::array<GLuint, kMaxPlanes> names{};
153 for (int i = 0; i < planeCount; ++i) {
154 // GST_MAP_GL maps return a *pointer to* the GLuint texture id, not the id itself.
155 const GLuint* texIdPtr = static_cast<const GLuint*>(GST_VIDEO_FRAME_PLANE_DATA(&frame, i));
156 names[i] = texIdPtr ? *texIdPtr : 0;
157 }
158
159 gst_video_frame_unmap(&frame);
160
161 if (auto* prev = GstHwFrameTexturesBase::reusableBundle<FrameTextures>(old, HwVideoBufferPath::GlMemory)) {
162 if (prev->matches(&rhi, _format.frameSize(), _format.pixelFormat(), names, planeCount)) {
164 prev->setSourceSample(takeSample());
165 QVideoFrameTexturesUPtr reused = std::move(old);
166 return reused;
167 }
168 }
169
170 // Ring recycle: the `old` bundle missed (different frame) but this id tuple was validated within the last
171 // kGlRingCapacity frames, so the pool reused a slot. Record the reuse; the createFrom below is a cheap non-owning
172 // view over the existing gst-gl texture (no real re-import).
173 const GlTexKey ringKey{names, planeCount};
174 const bool ringHit = (s_glRingCache.find(ringKey) != nullptr);
175
176 // Pre-flight the per-plane RHI format/size before createFrom() so an unsupported import demotes to CPU on a cheap
177 // capability query instead of a driver error.
178 if (!GstHwImportPreflight::preflightOrRecord(&rhi, HwVideoBufferPath::GlMemory, _format.pixelFormat(),
179 _format.frameSize())) {
180 return GstHwPathTelemetry::fail(HwVideoBufferPath::GlMemory);
181 }
182
183 auto textures =
184 std::make_unique<FrameTextures>(&rhi, _format.frameSize(), _format.pixelFormat(), names, planeCount);
185 // For NV12/I420 chroma can fail while luma succeeds; must verify all planes, not just plane 0.
186 for (int i = 0; i < planeCount; ++i) {
187 if (!textures->texture(static_cast<uint>(i))) {
188 qCWarning(GstGlBufLog) << "createFrom failed for plane" << i << "format=" << _format.pixelFormat();
191 return GstHwPathTelemetry::fail(HwVideoBufferPath::GlMemory);
192 }
193 }
194 if (ringHit) {
196 }
197 s_glRingCache.insert(ringKey, char{});
198 textures->setSourceSample(takeSample());
199 return textures;
200}
201
202#endif // QGC_HAS_GST_GLMEMORY_GPU_PATH
HwVideoBufferPath
Identifies which GPU path was chosen; used by the adapter to increment the right counter.
#define QGC_LOGGING_CATEGORY(name, categoryStr)
Shared base for GL-texture-backed QVideoFrameTextures wrappers (GLMemory and DMABuf-via-EGLImage).
GstGlFrameTextures(QRhi *rhi, QSize size, QVideoFrameFormat::PixelFormat pixelFormat, std::array< GLuint, GstHw::kMaxPlanes > names, int count, FallbackPolicy fallback=FallbackPolicy::Enable)
virtual HwVideoBufferPath sourcePath() const
GPU path that produced this bundle; used after a type-safe downcast to decide path-local reuse.
RAII timer: records mapTextures() wall time into the path's EWMA on scope exit.
Common base for GStreamer-backed QHwVideoBuffer subclasses.
void recordSyncWait(HwVideoBufferPath path, bool gpuSide) noexcept
GL fence sync wait; split CPU-blocking vs GPU-side.
void recordFallbackReason(HwVideoBufferPath attemptedPath, HwFallbackReason reason) noexcept
Per-(path,reason) fallback accounting; lets a bug report show why a path demoted to CPU.
void recordTextureReuse(HwVideoBufferPath path) noexcept
Prior frame's QRhiTexture wrappers reused (decoder pool returned same native handle).
constexpr int kMaxPlanes
Matches GST_VIDEO_MAX_PLANES (gst-video pins it at 4); single source of truth for every per-platform ...
QByteArray format(const QList< LogEntry > &entries, int fmt)
One-shot warning flags per failure cause; paths with extra causes (D3D, IOSurface) derive and add mem...