QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GstD3D11VideoBuffer.cc
Go to the documentation of this file.
2
3#if defined(Q_OS_WIN) && defined(QGC_HAS_GST_D3D11_GPU_PATH)
4
9
10#include <QtCore/QVarLengthArray>
11
12#include <gst/d3d11/gstd3d11.h>
13
14#include <d3d11.h>
15
16QGC_LOGGING_CATEGORY(GstD3D11Log, "Video.GStreamer.HwBuffers.GstD3D11Buf")
17
18namespace {
19
20using GstD3DVideoBufferCommon::kMaxPlanes;
21using GstD3DVideoBufferCommon::MapDiagnostics;
22using GstD3DVideoBufferCommon::fail;
23using D3D11FrameTextures = GstD3DVideoBufferCommon::FrameTextures<ID3D11Texture2D>;
24
25MapDiagnostics s_diag;
26
30ID3D11Texture2D *copySliceToStaging(ID3D11Texture2D *tex, guint subIdx, int planeIdx,
31 const D3D11_TEXTURE2D_DESC &srcDesc,
32 GstD3D11Memory *d3dmem)
33{
34 ID3D11Device *d3dDev = gst_d3d11_device_get_device_handle(d3dmem->device);
35 ID3D11DeviceContext *d3dCtx = gst_d3d11_device_get_device_context_handle(d3dmem->device);
36 D3D11_TEXTURE2D_DESC dstDesc = srcDesc;
37 dstDesc.ArraySize = 1;
38 dstDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
39 dstDesc.MiscFlags = 0;
40 dstDesc.MipLevels = 1;
41 ID3D11Texture2D *stagingTex = nullptr;
42 if (FAILED(d3dDev->CreateTexture2D(&dstDesc, nullptr, &stagingTex))) {
43 QGC_D3D_WARN_ONCE(GstD3D11Log, s_diag.loggedTextureCreateFail,
44 "mapTextures: CreateTexture2D for slice copy failed (plane=" << planeIdx
45 << "subresource=" << subIdx << ")");
46 return nullptr;
47 }
48 d3dCtx->CopySubresourceRegion(stagingTex, 0, 0, 0, 0, tex, subIdx, nullptr);
49 // Flush so QRhi sees the staged copy in the immediate context queue before binding it.
50 d3dCtx->Flush();
51 return stagingTex;
52}
53
54} // namespace
55
56GstD3D11VideoBuffer::GstD3D11VideoBuffer(GstSample *sample,
57 const GstVideoInfo &videoInfo,
58 const QVideoFrameFormat &format)
59 : GstHwVideoBuffer(QVideoFrame::RhiTextureHandle, sample, videoInfo, format)
60{
61}
62
63GstD3D11VideoBuffer::~GstD3D11VideoBuffer() = default;
64
65QAbstractVideoBuffer::MapData GstD3D11VideoBuffer::map(QVideoFrame::MapMode /*mode*/)
66{
67 return {};
68}
69
70bool GstD3D11VideoBuffer::validatePlaneHandles() const
71{
72 if (!_sample) return false;
73 GstBuffer *buffer = gst_sample_get_buffer(_sample);
74 if (!buffer) return false;
75 const int memCount = qMin(int(gst_buffer_n_memory(buffer)), kMaxPlanes);
76 if (memCount <= 0) return false;
77 for (int i = 0; i < memCount; ++i) {
78 GstMemory *mem = gst_buffer_peek_memory(buffer, i);
79 if (!mem || !gst_is_d3d11_memory(mem)) return false;
80 // Cheap field read; confirms the wrapper actually backs an ID3D11Texture2D.
81 if (!gst_d3d11_memory_get_resource_handle(GST_D3D11_MEMORY_CAST(mem))) {
82 return false;
83 }
84 }
85 return true;
86}
87
88QVideoFrameTexturesUPtr GstD3D11VideoBuffer::mapTextures(QRhi &rhi, QVideoFrameTexturesUPtr & /*old*/)
89{
90 Q_ASSERT(rhi.thread()->isCurrentThread()); // Qt's contract: mapTextures runs on the QRhi (render) thread.
91 // *** UNTESTED on Windows hardware. CI compiles this path; runtime validation TBD. ***
92 // Shared-device wiring is provided by GstD3D11ContextBridge — when primed, gst-d3d11
93 // decoders allocate textures on QRhi's ID3D11Device, so the handles below are
94 // directly QRhi-sampleable. Without the bridge, textures are on an isolated device
95 // and createFrom() will succeed but rendering will produce garbage / crashes.
96 if (!_sample) {
97 QGC_D3D_WARN_ONCE(GstD3D11Log, s_diag.loggedNullSample, "mapTextures: GstSample is null");
98 return fail(s_diag);
99 }
100 if (rhi.backend() != QRhi::D3D11) {
101 QGC_D3D_WARN_ONCE(GstD3D11Log, s_diag.loggedBadBackend,
102 "mapTextures: QRhi backend is" << rhi.backendName() << "(D3D11 required)");
103 return fail(s_diag);
104 }
105
106 GstBuffer *buffer = gst_sample_get_buffer(_sample);
107 if (!buffer) {
108 QGC_D3D_WARN_ONCE(GstD3D11Log, s_diag.loggedNullBuffer, "mapTextures: GstSample has no buffer");
109 return fail(s_diag);
110 }
111
112 const int memCount = qMin(int(gst_buffer_n_memory(buffer)), kMaxPlanes);
113 std::array<ID3D11Texture2D *, kMaxPlanes> texs{};
114 QVarLengthArray<ID3D11Texture2D *, kMaxPlanes> refdTexs;
115 for (int i = 0; i < memCount; ++i) {
116 GstMemory *mem = gst_buffer_peek_memory(buffer, i);
117 if (!mem || !gst_is_d3d11_memory(mem)) {
118 QGC_D3D_WARN_ONCE(GstD3D11Log, s_diag.loggedNonD3DMemory,
119 "mapTextures: plane" << i << "memory is not GstD3D11Memory (allocator="
120 << (mem && mem->allocator ? mem->allocator->mem_type : "null")
121 << ")");
122 for (auto *t : refdTexs) t->Release();
123 return fail(s_diag);
124 }
125 // Per-buffer device guard: gst-d3d11 elements may run on an isolated device when our
126 // NEED_CONTEXT response was preempted by another bridge. Sampling a foreign-device
127 // ID3D11Texture2D from QRhi corrupts silently (texture handle is valid on the wrong
128 // device). Check once per first plane; same buffer ⇒ same device for all planes.
129 if (i == 0) {
130 // currentDevice() is transfer-full — unref both branches to avoid UAF after reset().
131 GstD3D11Device *bridgeDev = GstD3D11ContextBridge::currentDevice();
132 GstD3D11Device *bufDev = GST_D3D11_MEMORY_CAST(mem)->device;
133 if (bridgeDev && bufDev != bridgeDev) {
134 const gint64 bridgeLuid = GstD3DContextBridgeCommon::readAdapterLuid(bridgeDev);
135 const gint64 bufLuid = GstD3DContextBridgeCommon::readAdapterLuid(bufDev);
136 gst_object_unref(bridgeDev);
137 QGC_D3D_WARN_ONCE(GstD3D11Log, s_diag.loggedDeviceMismatch,
138 "mapTextures: GstD3D11Memory on foreign device (bridge LUID="
139 << bridgeLuid << "buffer LUID=" << bufLuid
140 << "); bridge missed NEED_CONTEXT race — rejecting frame");
141 return fail(s_diag);
142 }
143 if (bridgeDev) gst_object_unref(bridgeDev);
144 }
145 ID3D11Texture2D *tex = reinterpret_cast<ID3D11Texture2D *>(
146 gst_d3d11_memory_get_resource_handle(GST_D3D11_MEMORY_CAST(mem)));
147 if (!tex) {
148 QGC_D3D_WARN_ONCE(GstD3D11Log, s_diag.loggedNullResource,
149 "mapTextures: gst_d3d11_memory_get_resource_handle returned null for plane" << i);
150 for (auto *t : refdTexs) t->Release();
151 return fail(s_diag);
152 }
153 // QRhi::createFrom has no subresource parameter — copy the slice when needed.
154 const guint subIdx = gst_d3d11_memory_get_subresource_index(GST_D3D11_MEMORY_CAST(mem));
155 D3D11_TEXTURE2D_DESC srcDesc{};
156 tex->GetDesc(&srcDesc);
157 if (subIdx > 0 || srcDesc.ArraySize > 1) {
158 ID3D11Texture2D *stagingTex = copySliceToStaging(tex, subIdx, i, srcDesc,
159 GST_D3D11_MEMORY_CAST(mem));
160 if (!stagingTex) {
161 for (auto *t : refdTexs) t->Release();
162 return fail(s_diag);
163 }
164 refdTexs.append(stagingTex);
165 texs[i] = stagingTex;
166 } else {
167 tex->AddRef();
168 refdTexs.append(tex);
169 texs[i] = tex;
170 }
171 }
172
173 auto textures = std::make_unique<D3D11FrameTextures>(&rhi, _format.frameSize(),
174 _format.pixelFormat(), texs, memCount);
175 // Per-plane: NV12 chroma can fail while luma succeeds. Returning a partial bundle
176 // would render with missing planes and no failure-counter increment.
177 for (int i = 0; i < memCount; ++i) {
178 if (!textures->texture(static_cast<uint>(i))) {
179 QGC_D3D_WARN_ONCE(GstD3D11Log, s_diag.loggedTextureCreateFail,
180 "mapTextures: QRhiTexture::createFrom failed plane=" << i
181 << " (size=" << _format.frameSize()
182 << "format=" << int(_format.pixelFormat()) << "planes=" << memCount << ")");
183 return fail(s_diag);
184 }
185 }
186
187 if (!s_diag.loggedFirstSuccess.exchange(true, std::memory_order_relaxed)) {
188 qCInfo(GstD3D11Log) << "First D3D11 zero-copy mapTextures success: size=" << _format.frameSize()
189 << "format=" << int(_format.pixelFormat()) << "planes=" << memCount;
190 }
191 return textures;
192}
193
194quint64 GstD3D11VideoBuffer::takeMapFailureCount()
195{
196 return GstD3DVideoBufferCommon::takeMapFailureCount(s_diag);
197}
198
199quint64 GstD3D11VideoBuffer::peekMapFailureCount()
200{
201 return GstD3DVideoBufferCommon::peekMapFailureCount(s_diag);
202}
203
204#endif // Q_OS_WIN && QGC_HAS_GST_D3D11_GPU_PATH
#define QGC_LOGGING_CATEGORY(name, categoryStr)
QByteArray format(const QList< LogEntry > &entries, int fmt)