QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GstD3D12VideoBuffer.cc
Go to the documentation of this file.
2
3#if defined(Q_OS_WIN) && defined(QGC_HAS_GST_D3D12_GPU_PATH)
4
9
10#include <QtCore/QVarLengthArray>
11
12#include <gst/d3d12/gstd3d12.h>
13
14#include <d3d12.h>
15
16QGC_LOGGING_CATEGORY(GstD3D12Log, "Video.GStreamer.HwBuffers.GstD3D12Buf")
17
18namespace {
19
20using GstD3DVideoBufferCommon::kMaxPlanes;
21using GstD3DVideoBufferCommon::MapDiagnostics;
22using GstD3DVideoBufferCommon::fail;
23using D3D12FrameTextures = GstD3DVideoBufferCommon::FrameTextures<ID3D12Resource>;
24
25MapDiagnostics s_diag;
26
29ID3D12Resource *copySliceToStaging(ID3D12Resource *resource, guint subIdx, int planeIdx,
30 const D3D12_RESOURCE_DESC &srcDesc,
31 GstD3D12Memory *d3dmem)
32{
33 ID3D12Device *d3dDev = gst_d3d12_device_get_device_handle(d3dmem->device);
34 D3D12_COMMAND_LIST_TYPE queueType = D3D12_COMMAND_LIST_TYPE_COPY;
35 GstD3D12CmdQueue *cmdQueue = gst_d3d12_device_get_cmd_queue(d3dmem->device, queueType);
36 if (!cmdQueue) {
37 queueType = D3D12_COMMAND_LIST_TYPE_DIRECT;
38 cmdQueue = gst_d3d12_device_get_cmd_queue(d3dmem->device, queueType);
39 }
40 ID3D12CommandQueue *rawQueue = cmdQueue ? gst_d3d12_cmd_queue_get_handle(cmdQueue) : nullptr;
41 if (!d3dDev || !rawQueue) {
42 QGC_D3D_WARN_ONCE(GstD3D12Log, s_diag.loggedTextureCreateFail,
43 "mapTextures: could not obtain D3D12 device/queue for slice copy (plane="
44 << planeIdx << ")");
45 return nullptr;
46 }
47
48 D3D12_RESOURCE_DESC dstDesc = srcDesc;
49 dstDesc.DepthOrArraySize = 1;
50 dstDesc.MipLevels = 1;
51
52 D3D12_HEAP_PROPERTIES heapProps{};
53 heapProps.Type = D3D12_HEAP_TYPE_DEFAULT;
54
55 ID3D12Resource *stagingResource = nullptr;
56 if (FAILED(d3dDev->CreateCommittedResource(
57 &heapProps, D3D12_HEAP_FLAG_NONE, &dstDesc,
58 D3D12_RESOURCE_STATE_COMMON, nullptr, IID_PPV_ARGS(&stagingResource)))) {
59 QGC_D3D_WARN_ONCE(GstD3D12Log, s_diag.loggedTextureCreateFail,
60 "mapTextures: CreateCommittedResource for slice copy failed (plane=" << planeIdx
61 << " subresource=" << subIdx << ")");
62 return nullptr;
63 }
64
65 // COPY queue accepts COMMON↔COPY_SOURCE↔COPY_DEST barriers — transitions below stay legal.
66 ID3D12CommandAllocator *cmdAlloc = nullptr;
67 ID3D12GraphicsCommandList *cmdList = nullptr;
68 bool copyOk = false;
69 if (SUCCEEDED(d3dDev->CreateCommandAllocator(queueType, IID_PPV_ARGS(&cmdAlloc))) &&
70 SUCCEEDED(d3dDev->CreateCommandList(0, queueType,
71 cmdAlloc, nullptr, IID_PPV_ARGS(&cmdList)))) {
72 D3D12_TEXTURE_COPY_LOCATION srcLoc{};
73 srcLoc.pResource = resource;
74 srcLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
75 srcLoc.SubresourceIndex = subIdx;
76
77 D3D12_TEXTURE_COPY_LOCATION dstLoc{};
78 dstLoc.pResource = stagingResource;
79 dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
80 dstLoc.SubresourceIndex = 0;
81
82 // Decoder leaves the resource in a non-COPY_SOURCE state (typically COMMON
83 // after gst_d3d12_memory_sync, or VIDEO_DECODE_WRITE pre-sync). Issue an
84 // explicit transition; without it the debug layer fires and some drivers TDR.
85 D3D12_RESOURCE_BARRIER toCopySrc{};
86 toCopySrc.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
87 toCopySrc.Transition.pResource = resource;
88 toCopySrc.Transition.Subresource = subIdx;
89 toCopySrc.Transition.StateBefore = D3D12_RESOURCE_STATE_COMMON;
90 toCopySrc.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE;
91 cmdList->ResourceBarrier(1, &toCopySrc);
92
93 cmdList->CopyTextureRegion(&dstLoc, 0, 0, 0, &srcLoc, nullptr);
94
95 D3D12_RESOURCE_BARRIER toCommon = toCopySrc;
96 toCommon.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_SOURCE;
97 toCommon.Transition.StateAfter = D3D12_RESOURCE_STATE_COMMON;
98 cmdList->ResourceBarrier(1, &toCommon);
99
100 cmdList->Close();
101
102 ID3D12CommandList *lists[] = { cmdList };
103 rawQueue->ExecuteCommandLists(1, lists);
104
105 ID3D12Fence *fence = nullptr;
106 if (SUCCEEDED(d3dDev->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)))) {
107 rawQueue->Signal(fence, 1);
108 HANDLE event = CreateEvent(nullptr, FALSE, FALSE, nullptr);
109 if (event) {
110 fence->SetEventOnCompletion(1, event);
111 WaitForSingleObject(event, INFINITE);
112 CloseHandle(event);
113 }
114 fence->Release();
115 }
116 copyOk = true;
117 }
118 if (cmdList) cmdList->Release();
119 if (cmdAlloc) cmdAlloc->Release();
120
121 if (!copyOk) {
122 stagingResource->Release();
123 return nullptr;
124 }
125 return stagingResource;
126}
127
128} // namespace
129
130GstD3D12VideoBuffer::GstD3D12VideoBuffer(GstSample *sample,
131 const GstVideoInfo &videoInfo,
132 const QVideoFrameFormat &format)
133 : GstHwVideoBuffer(QVideoFrame::RhiTextureHandle, sample, videoInfo, format)
134{
135}
136
137GstD3D12VideoBuffer::~GstD3D12VideoBuffer() = default;
138
139QAbstractVideoBuffer::MapData GstD3D12VideoBuffer::map(QVideoFrame::MapMode /*mode*/)
140{
141 return {};
142}
143
144bool GstD3D12VideoBuffer::validatePlaneHandles() const
145{
146 if (!_sample) return false;
147 GstBuffer *buffer = gst_sample_get_buffer(_sample);
148 if (!buffer) return false;
149 const int memCount = qMin(int(gst_buffer_n_memory(buffer)), kMaxPlanes);
150 if (memCount <= 0) return false;
151 for (int i = 0; i < memCount; ++i) {
152 GstMemory *mem = gst_buffer_peek_memory(buffer, i);
153 if (!mem || !gst_is_d3d12_memory(mem)) return false;
154 if (!gst_d3d12_memory_get_resource_handle(GST_D3D12_MEMORY_CAST(mem))) {
155 return false;
156 }
157 }
158 return true;
159}
160
161QVideoFrameTexturesUPtr GstD3D12VideoBuffer::mapTextures(QRhi &rhi, QVideoFrameTexturesUPtr & /*old*/)
162{
163 Q_ASSERT(rhi.thread()->isCurrentThread()); // Qt's contract: mapTextures runs on the QRhi (render) thread.
164 // Shared-device wiring is provided by GstD3D12ContextBridge — when primed, gst-d3d12
165 // decoders allocate resources on QRhi's adapter, so the handles below are
166 // directly QRhi-importable. Without the bridge, resources are on an isolated device
167 // and createFrom() will succeed but rendering will produce garbage / crashes.
168 if (!_sample) {
169 QGC_D3D_WARN_ONCE(GstD3D12Log, s_diag.loggedNullSample, "mapTextures: GstSample is null");
170 return fail(s_diag);
171 }
172 if (rhi.backend() != QRhi::D3D12) {
173 QGC_D3D_WARN_ONCE(GstD3D12Log, s_diag.loggedBadBackend,
174 "mapTextures: QRhi backend is" << rhi.backendName() << "(D3D12 required)");
175 return fail(s_diag);
176 }
177
178 GstBuffer *buffer = gst_sample_get_buffer(_sample);
179 if (!buffer) {
180 QGC_D3D_WARN_ONCE(GstD3D12Log, s_diag.loggedNullBuffer, "mapTextures: GstSample has no buffer");
181 return fail(s_diag);
182 }
183
184 const int memCount = qMin(int(gst_buffer_n_memory(buffer)), kMaxPlanes);
185 std::array<ID3D12Resource *, kMaxPlanes> resources{};
186 QVarLengthArray<ID3D12Resource *, kMaxPlanes> refdResources;
187 for (int i = 0; i < memCount; ++i) {
188 GstMemory *mem = gst_buffer_peek_memory(buffer, i);
189 if (!mem || !gst_is_d3d12_memory(mem)) {
190 QGC_D3D_WARN_ONCE(GstD3D12Log, s_diag.loggedNonD3DMemory,
191 "mapTextures: plane" << i << "memory is not GstD3D12Memory (allocator="
192 << (mem && mem->allocator ? mem->allocator->mem_type : "null")
193 << ")");
194 for (auto *r : refdResources) r->Release();
195 return fail(s_diag);
196 }
197 GstD3D12Memory *d3dmem = GST_D3D12_MEMORY_CAST(mem);
198 // Per-buffer device guard: gst-d3d12 may run on an isolated device when our
199 // NEED_CONTEXT response was preempted. Importing a foreign-device ID3D12Resource
200 // into QRhi either fails or silently corrupts. Check once on the first plane.
201 if (i == 0) {
202 // currentDevice() is transfer-full — unref both branches to avoid UAF after reset().
203 GstD3D12Device *bridgeDev = GstD3D12ContextBridge::currentDevice();
204 if (bridgeDev && d3dmem->device != bridgeDev) {
205 const gint64 bridgeLuid = GstD3DContextBridgeCommon::readAdapterLuid(bridgeDev);
206 const gint64 bufLuid = GstD3DContextBridgeCommon::readAdapterLuid(d3dmem->device);
207 gst_object_unref(bridgeDev);
208 QGC_D3D_WARN_ONCE(GstD3D12Log, s_diag.loggedDeviceMismatch,
209 "mapTextures: GstD3D12Memory on foreign device (bridge LUID="
210 << bridgeLuid << "buffer LUID=" << bufLuid
211 << "); bridge missed NEED_CONTEXT race — rejecting frame");
212 return fail(s_diag);
213 }
214 if (bridgeDev) gst_object_unref(bridgeDev);
215 }
216 // Block on the decoder's fence before reading; otherwise QRhi may sample mid-write
217 // (decoder still has a CL writing this resource). gst-d3d12 stores the fence on the
218 // memory when the producing element calls set_fence; sync() flushes it.
219 if (!gst_d3d12_memory_sync(d3dmem)) {
220 QGC_D3D_WARN_ONCE(GstD3D12Log, s_diag.loggedNullResource,
221 "mapTextures: gst_d3d12_memory_sync failed for plane" << i);
222 for (auto *r : refdResources) r->Release();
223 return fail(s_diag);
224 }
225 ID3D12Resource *resource = gst_d3d12_memory_get_resource_handle(d3dmem);
226 if (!resource) {
227 QGC_D3D_WARN_ONCE(GstD3D12Log, s_diag.loggedNullResource,
228 "mapTextures: gst_d3d12_memory_get_resource_handle returned null for plane" << i);
229 for (auto *r : refdResources) r->Release();
230 return fail(s_diag);
231 }
232 // QRhi::createFrom has no subresource parameter — copy the slice when needed.
233 guint subIdx = 0;
234 gst_d3d12_memory_get_subresource_index(d3dmem, guint(i), &subIdx);
235 D3D12_RESOURCE_DESC srcDesc = resource->GetDesc();
236 if (subIdx > 0 || srcDesc.DepthOrArraySize > 1) {
237 ID3D12Resource *stagingResource = copySliceToStaging(resource, subIdx, i, srcDesc, d3dmem);
238 if (!stagingResource) {
239 for (auto *r : refdResources) r->Release();
240 return fail(s_diag);
241 }
242 refdResources.append(stagingResource);
243 resources[i] = stagingResource;
244 } else {
245 resource->AddRef();
246 refdResources.append(resource);
247 resources[i] = resource;
248 }
249 }
250
251 auto textures = std::make_unique<D3D12FrameTextures>(&rhi, _format.frameSize(),
252 _format.pixelFormat(), resources, memCount);
253 // Per-plane: NV12 chroma can fail while luma succeeds. Returning a partial bundle
254 // would render with missing planes and no failure-counter increment.
255 for (int i = 0; i < memCount; ++i) {
256 if (!textures->texture(static_cast<uint>(i))) {
257 QGC_D3D_WARN_ONCE(GstD3D12Log, s_diag.loggedTextureCreateFail,
258 "mapTextures: QRhiTexture::createFrom failed plane=" << i
259 << " (size=" << _format.frameSize()
260 << " format=" << int(_format.pixelFormat()) << " planes=" << memCount << ")");
261 return fail(s_diag);
262 }
263 }
264
265 if (!s_diag.loggedFirstSuccess.exchange(true, std::memory_order_relaxed)) {
266 qCInfo(GstD3D12Log) << "First D3D12 zero-copy mapTextures success: size=" << _format.frameSize()
267 << "format=" << int(_format.pixelFormat()) << "planes=" << memCount;
268 }
269 return textures;
270}
271
272quint64 GstD3D12VideoBuffer::takeMapFailureCount()
273{
274 return GstD3DVideoBufferCommon::takeMapFailureCount(s_diag);
275}
276
277quint64 GstD3D12VideoBuffer::peekMapFailureCount()
278{
279 return GstD3DVideoBufferCommon::peekMapFailureCount(s_diag);
280}
281
282#endif // Q_OS_WIN && QGC_HAS_GST_D3D12_GPU_PATH
#define QGC_LOGGING_CATEGORY(name, categoryStr)
QByteArray format(const QList< LogEntry > &entries, int fmt)