QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GstD3DVideoBufferCommon.h
Go to the documentation of this file.
1#pragma once
2
3#include <QtCore/qglobal.h>
4
5#if defined(Q_OS_WIN) && (defined(QGC_HAS_GST_D3D11_GPU_PATH) || defined(QGC_HAS_GST_D3D12_GPU_PATH))
6
7#include <QtCore/QLoggingCategory>
8#include <QtCore/QSize>
9#include <QtMultimedia/QVideoFrame>
10#include <QtMultimedia/QVideoFrameFormat>
11#include <array>
12#include <atomic>
13#include <cstddef>
14#include <functional>
15#include <gst/gst.h>
16#include <memory>
17#include <private/qhwvideobuffer_p.h>
18#include <private/qvideotexturehelper_p.h>
19#include <rhi/qrhi.h>
20#include <tuple>
21
24#include "GstHwPathTelemetry.h"
25#include "GstHwVideoBuffer.h"
26
28namespace GstD3DVideoBufferCommon {
29
31
32inline bool canAliasSingleResourceAcrossPlanes(QVideoFrameFormat::PixelFormat pixelFormat) noexcept
33{
34 switch (pixelFormat) {
35 case QVideoFrameFormat::Format_NV12:
36 case QVideoFrameFormat::Format_NV21:
37 case QVideoFrameFormat::Format_P010:
38 case QVideoFrameFormat::Format_P016:
39 return true;
40 default:
41 return false;
42 }
43}
44
47struct MapDiagnostics : GstHw::MapDiagnostics
48{
49 std::atomic<bool> loggedNonD3DMemory{false};
50 std::atomic<bool> loggedNullResource{false};
51 // Tripped when GstD3DXMemory carries a device that doesn't match the bridge's shared device — a NEED_CONTEXT race
52 // the bridge lost.
53 std::atomic<bool> loggedDeviceMismatch{false};
54
56 void reset() noexcept
57 {
58 loggedFirstSuccess.store(false, std::memory_order_release);
59 loggedNullSample.store(false, std::memory_order_release);
60 loggedBadBackend.store(false, std::memory_order_release);
61 loggedNullBuffer.store(false, std::memory_order_release);
62 loggedTextureCreateFail.store(false, std::memory_order_release);
63 loggedNonD3DMemory.store(false, std::memory_order_release);
64 loggedNullResource.store(false, std::memory_order_release);
65 loggedDeviceMismatch.store(false, std::memory_order_release);
66 }
67};
68
71struct StagingKey
72{
73 quint64 width = 0;
74 quint32 height = 0;
75 quint32 format = 0; // DXGI_FORMAT
76 int plane = 0;
77
78 bool operator<(const StagingKey& o) const
79 {
80 return std::tie(width, height, format, plane) < std::tie(o.width, o.height, o.format, o.plane);
81 }
82
83 bool operator==(const StagingKey& o) const noexcept = default;
84};
85
86struct StagingKeyHash
87{
88 std::size_t operator()(const StagingKey& key) const noexcept
89 {
90 std::size_t seed = std::hash<quint64>{}(key.width);
91 seed ^= std::hash<quint32>{}(key.height) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
92 seed ^= std::hash<quint32>{}(key.format) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
93 seed ^= std::hash<int>{}(key.plane) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
94 return seed;
95 }
96};
97
100template <class HandleT>
101struct PlaneResourceGuard
102{
103 std::array<HandleT*, kMaxPlanes> handles{};
104 int owned = 0;
105
106 ~PlaneResourceGuard()
107 {
108 for (int j = 0; j < owned; ++j) {
109 if (handles[j])
110 handles[j]->Release();
111 }
112 }
113
116 void commit() noexcept { owned = 0; }
117};
118
121template <class HandleT>
122class FrameTextures final : public GstHwFrameTexturesBase
123{
124public:
125 FrameTextures(HwVideoBufferPath path, QRhi* rhi, QSize size, QVideoFrameFormat::PixelFormat pixelFormat,
126 std::array<HandleT*, kMaxPlanes> handles, int count)
127 : _handles(handles), _path(path), _rhi(rhi), _size(size), _pixelFormat(pixelFormat)
128 {
129 _count = count;
130 const auto* desc = QVideoTextureHelper::textureDescription(pixelFormat);
131 if (!desc)
132 return;
133 for (int i = 0; i < _count; ++i) {
134 const QSize planeSize = desc->rhiPlaneSize(size, i, rhi);
135 _textures[i].reset(rhi->newTexture(desc->rhiTextureFormat(i, rhi), planeSize, 1, {}));
136 if (_textures[i] && !_textures[i]->createFrom({reinterpret_cast<quint64>(handles[i]), 0})) {
137 _textures[i].reset();
138 }
139 }
140 }
141
142 ~FrameTextures() override
143 {
144 for (int i = 0; i < _count; ++i) {
145 if (_handles[i])
146 _handles[i]->Release();
147 }
148 }
149
150 HwVideoBufferPath sourcePath() const override { return _path; }
151
154 bool matches(QRhi* rhi, QSize size, QVideoFrameFormat::PixelFormat pixelFormat,
155 const std::array<HandleT*, kMaxPlanes>& handles, int count) const
156 {
157 if (_rhi != rhi || _size != size || _pixelFormat != pixelFormat || _count != count) {
158 return false;
159 }
160 for (int i = 0; i < _count; ++i) {
161 if (!_handles[i] || _handles[i] != handles[i] || !_textures[i]) {
162 return false;
163 }
164 }
165 return true;
166 }
167
168private:
169 std::array<HandleT*, kMaxPlanes> _handles;
171 QRhi* _rhi = nullptr;
172 QSize _size;
173 QVideoFrameFormat::PixelFormat _pixelFormat = QVideoFrameFormat::Format_Invalid;
174};
175
181template <class HandleT, class CatFn>
182QVideoFrameTexturesUPtr mapResolvedTextures(GstHwVideoBuffer& self, QRhi& rhi, QVideoFrameTexturesUPtr& old,
184 const std::array<HandleT*, kMaxPlanes>& resolvedHandles, int resolvedCount,
185 QSize frameSize, QVideoFrameFormat::PixelFormat pixelFormat,
186 std::atomic<bool>& loggedFirstSuccess,
187 std::atomic<bool>& loggedTextureCreateFail, CatFn catFn, const char* tag)
188{
189 using BundleT = FrameTextures<HandleT>;
190
191 const auto* desc = QVideoTextureHelper::textureDescription(pixelFormat);
192 if (!desc || desc->nplanes <= 0) {
193 return GstHwPathTelemetry::fail(path);
194 }
195
196 const int textureCount = (std::min)(desc->nplanes, kMaxPlanes);
197 if (resolvedCount <= 0 || resolvedCount > kMaxPlanes) {
198 return GstHwPathTelemetry::fail(path);
199 }
200
201 std::array<HandleT*, kMaxPlanes> handles{};
202 for (int i = 0; i < (std::min)(resolvedCount, textureCount); ++i) {
203 handles[i] = resolvedHandles[i];
204 }
205
206 if (resolvedCount < textureCount) {
207 if (resolvedCount != 1 || !handles[0] || !canAliasSingleResourceAcrossPlanes(pixelFormat)) {
208 return GstHwPathTelemetry::fail(path);
209 }
210 // D3D NV12/P010 output can expose one texture with multiple shader-resource planes.
211 for (int i = resolvedCount; i < textureCount; ++i) {
212 handles[i] = handles[0];
213 }
214 }
215
216 if (auto* prev = GstHwFrameTexturesBase::reusableBundle<BundleT>(old, path)) {
217 if (prev->matches(&rhi, frameSize, pixelFormat, handles, textureCount)) {
219 prev->setSourceSample(self.takeSample());
220 QVideoFrameTexturesUPtr reused = std::move(old);
221 return reused;
222 }
223 }
224
225 // Pre-flight RHI format/size support before createFrom() so an unsupported import demotes to CPU on a query.
226 if (!GstHwImportPreflight::preflightOrRecord(&rhi, path, pixelFormat, frameSize)) {
227 return GstHwPathTelemetry::fail(path);
228 }
229
230 // FrameTextures takes ownership of AddRef'd refs; the buffer dtor releases the originals independently.
231 for (int i = 0; i < textureCount; ++i) {
232 if (handles[i])
233 handles[i]->AddRef();
234 }
235
236 auto textures = std::make_unique<BundleT>(path, &rhi, frameSize, pixelFormat, handles, textureCount);
237 // Check all planes: NV12 chroma can fail while luma succeeds, and a partial bundle renders with missing planes.
238 for (int i = 0; i < textureCount; ++i) {
239 if (!textures->texture(static_cast<uint>(i))) {
240 QGC_HW_WARN_ONCE(catFn, loggedTextureCreateFail,
241 "mapTextures: QRhiTexture::createFrom failed plane="
242 << i << " (size=" << frameSize << " format=" << int(pixelFormat)
243 << " planes=" << textureCount << ")");
244 return GstHwPathTelemetry::fail(path);
245 }
246 }
247
248 GstHwVideoBuffer::logFirstSuccess(loggedFirstSuccess, catFn(), tag, frameSize, pixelFormat, textureCount);
249 textures->setSourceSample(self.takeSample());
250 return textures;
251}
252
253} // namespace GstD3DVideoBufferCommon
254
255#endif // Q_OS_WIN && (QGC_HAS_GST_D3D11_GPU_PATH || QGC_HAS_GST_D3D12_GPU_PATH)
HwVideoBufferPath
Identifies which GPU path was chosen; used by the adapter to increment the right counter.
#define QGC_HW_WARN_ONCE(LOGCAT, FLAG,...)
Logs once via qCWarning(LOGCAT) the first time FLAG flips true; subsequent trips are silent.
Common base for per-platform FrameTextures : QVideoFrameTextures from *VideoBuffer::mapTextures().
Common base for GStreamer-backed QHwVideoBuffer subclasses.
GstSample * takeSample() noexcept
Transfers GstSample ownership out for early pool-slot release in mapTextures.
static void logFirstSuccess(std::atomic< bool > &flag, const QLoggingCategory &cat, const char *tag, QSize frameSize, QVideoFrameFormat::PixelFormat pixelFormat, int planes)
One-shot "first zero-copy success" info line; silent after flag first flips.
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...