Loading...
Searching...
No Matches
DvpCaptureD3D11.hpp
1#pragma once
3#include <nv_dvp_bridge.h>
4
5#include <QtGui/private/qrhid3d11_p.h>
6
7#include <QDebug>
8#include <QtCore/QtGlobal>
9
10#include <d3d11.h>
11
12#include <array>
13#include <cstdint>
14#include <cstring>
15
16namespace score::gfx::interop
17{
18
25template <class Lock>
27{
28 DvpCaptureD3D11(Lock lock, NvDvpFormat dvpFormat, const char* name) noexcept
29 : m_lock{lock}, m_dvpFormat{dvpFormat}, m_name{name} {}
30
31 Lock m_lock;
32 NvDvpFormat m_dvpFormat{};
33 const char* m_name{"DVP-D3D11"};
34
36
37 static constexpr int kMaxFailures = 8;
38 int m_failures{};
39 bool m_dead{};
40 ID3D11Device* m_dev{};
41
42 NvDvpContextHandle m_dvpCtx{};
43 NvDvpResourceHandle m_dvpTex{};
44 bool m_threadStarted{};
45
46 static constexpr std::size_t kSlotCount = 3;
47 struct Slot
48 {
49 void* sysmem{};
50 NvDvpResourceHandle dvpBuf{};
51 bool dmaLocked{};
52 };
53 std::array<Slot, kSlotCount> m_slots{};
54
55 uint32_t m_sysmemBytes{};
56 uint32_t m_sysmemStrideBytes{};
57 int m_texW{};
58 int m_texH{};
59
60 const char* name() const noexcept override { return m_name; }
61
62 bool init(const score::gfx::interop::VideoCaptureStrategyConfig& c) override
63 {
64 cfg = c;
65 if(!cfg.rhi || !m_lock.valid() || !cfg.outputTexture)
66 return false;
67
68 auto* native
69 = static_cast<const QRhiD3D11NativeHandles*>(cfg.rhi->nativeHandles());
70 if(!native || !native->dev)
71 return false;
72 m_dev = static_cast<ID3D11Device*>(native->dev);
73
74 qDebug() << "DVP-IN(D3D11): loading dvp.dll...";
75 if(nv_dvp_init_d3d11(&m_dvpCtx, m_dev) != NV_DVP_SUCCESS || !m_dvpCtx)
76 {
77 qWarning() << "DVP-IN(D3D11): init failed:"
78 << nv_dvp_get_error_string(m_dvpCtx);
79 return false;
80 }
81 if(nv_dvp_thread_begin(m_dvpCtx) != NV_DVP_SUCCESS)
82 {
83 qWarning() << "DVP-IN(D3D11): thread_begin failed:"
84 << nv_dvp_get_error_string(m_dvpCtx);
85 release();
86 return false;
87 }
88 m_threadStarted = true;
89 qDebug() << "DVP-IN(D3D11): dvp.dll loaded + D3D11 device bound";
90
91 // Geometry from caller's QRhi texture; sysmem stride/size derived
92 // and validated against the card's frame byte size.
93 const QSize texSize = cfg.outputTexture->pixelSize();
94 m_texW = texSize.width();
95 m_texH = texSize.height();
96 m_sysmemStrideBytes = static_cast<uint32_t>(m_texW) * 4u;
97 m_sysmemBytes = m_sysmemStrideBytes * static_cast<uint32_t>(m_texH);
98
99 if(m_sysmemBytes != cfg.frameByteSize)
100 {
101 qWarning() << "DVP-IN(D3D11): texture byte size" << m_sysmemBytes
102 << "(" << m_texW << "x" << m_texH << " RGBA8/BGRA8) !="
103 << "frame size" << cfg.frameByteSize
104 << "- texture geometry doesn't match captured layout";
105 release();
106 return false;
107 }
108
109 auto nt = cfg.outputTexture->nativeTexture();
110 if(!nt.object)
111 {
112 qWarning() << "DVP-IN(D3D11): output texture has no native handle";
113 release();
114 return false;
115 }
116 auto* d3d11Tex = reinterpret_cast<ID3D11Texture2D*>(nt.object);
117
118 const NvDvpFormat dvpFmt = m_dvpFormat;
119
120 if(nv_dvp_register_d3d11_texture(
121 m_dvpCtx, d3d11Tex, dvpFmt, uint32_t(m_texW), uint32_t(m_texH),
122 &m_dvpTex)
123 != NV_DVP_SUCCESS)
124 {
125 qWarning() << "DVP-IN(D3D11): register_d3d11_texture failed:"
126 << nv_dvp_get_error_string(m_dvpCtx);
127 release();
128 return false;
129 }
130
131 // Sysmem slots: page-locked (vendor policy) for the card's DMA + DVP-
132 // registered for GPU DMA. The card's frame size (cfg.frameByteSize) is
133 // what gets written in; the DVP transfer copies the same byte count out
134 // to the texture.
135 for(auto& slot : m_slots)
136 {
137 slot.sysmem = nv_dvp_aligned_alloc(m_sysmemBytes);
138 if(!slot.sysmem)
139 {
140 qWarning() << "DVP-IN(D3D11): nv_dvp_aligned_alloc(" << m_sysmemBytes
141 << ") failed";
142 release();
143 return false;
144 }
145 std::memset(slot.sysmem, 0, m_sysmemBytes);
146 if(!m_lock.lock(slot.sysmem, m_sysmemBytes))
147 {
148 qWarning() << "DVP-IN(D3D11): DMABufferLock(slot) failed";
149 release();
150 return false;
151 }
152 slot.dmaLocked = true;
153 if(nv_dvp_register_sysmem_buffer(
154 m_dvpCtx, slot.sysmem, dvpFmt, uint32_t(m_texW),
155 uint32_t(m_texH), m_sysmemStrideBytes, &slot.dvpBuf)
156 != NV_DVP_SUCCESS)
157 {
158 qWarning() << "DVP-IN(D3D11): register_sysmem_buffer failed:"
159 << nv_dvp_get_error_string(m_dvpCtx);
160 release();
161 return false;
162 }
163 }
164
165 // One real transfer before claiming the rung. Registering the buffers only
166 // proves DVP accepted the descriptors, not that a transfer can complete --
167 // a device can fail every dvpMapBufferEndAPI(tex) with -1 while the rung
168 // reports itself engaged and produces no frames. Same reasoning as
169 // VendorDmaRegistrar::verifyTransfer on the output side: fail init so the
170 // chain falls back cleanly instead of degrading into a per-frame failure
171 // the renderer cannot recover from. The probe is unproven against a working
172 // DVP setup, so SCORE_GFX_DVP_NO_PROBE=1 skips it without a rebuild.
173 if(!qEnvironmentVariableIsSet("SCORE_GFX_DVP_NO_PROBE"))
174 {
175 if(m_slots[0].dvpBuf && m_dvpTex
176 && nv_dvp_copy_buffer_to_texture(m_dvpCtx, m_slots[0].dvpBuf, m_dvpTex)
177 != NV_DVP_SUCCESS)
178 {
179 qWarning() << "DVP-IN(D3D11): probe transfer failed:"
180 << nv_dvp_get_error_string(m_dvpCtx)
181 << "- DVP capture unusable here, falling back";
182 release();
183 return false;
184 }
185 }
186
187 return true;
188 }
189
190 void release() override
191 {
192 if(m_dvpCtx)
193 {
194 for(auto& slot : m_slots)
195 {
196 if(slot.dvpBuf)
197 {
198 nv_dvp_unregister(m_dvpCtx, slot.dvpBuf);
199 slot.dvpBuf = nullptr;
200 }
201 if(slot.dmaLocked)
202 {
203 m_lock.unlock(slot.sysmem, m_sysmemBytes);
204 slot.dmaLocked = false;
205 }
206 if(slot.sysmem)
207 {
208 nv_dvp_aligned_free(slot.sysmem);
209 slot.sysmem = nullptr;
210 }
211 }
212 if(m_dvpTex)
213 {
214 nv_dvp_unregister(m_dvpCtx, m_dvpTex);
215 m_dvpTex = nullptr;
216 }
217 if(m_threadStarted)
218 {
219 nv_dvp_thread_end(m_dvpCtx);
220 m_threadStarted = false;
221 }
222 nv_dvp_shutdown(m_dvpCtx);
223 m_dvpCtx = nullptr;
224 }
225 // Don't delete cfg.outputTexture — we don't own it.
226 m_dev = nullptr;
227 }
228
232 bool healthy() const noexcept { return !m_dead; }
233
234 std::size_t slotCount() const noexcept override { return kSlotCount; }
235 void* slotBuffer(std::size_t i) const noexcept override
236 {
237 return i < kSlotCount ? m_slots[i].sysmem : nullptr;
238 }
239
240 bool ingestFrame(std::size_t i) override
241 {
242 if(i >= kSlotCount)
243 return false;
244 auto& slot = m_slots[i];
245 if(!slot.dvpBuf || !m_dvpTex)
246 return false;
247 if(m_dead)
248 return false;
249 if(nv_dvp_copy_buffer_to_texture(m_dvpCtx, slot.dvpBuf, m_dvpTex)
250 != NV_DVP_SUCCESS)
251 {
252 // A failed transfer is not transient: every frame takes the same path
253 // with the same buffers. Retrying forever spins the capture thread at
254 // full CPU and starves the consumer, which presents as a hang rather
255 // than as a degraded rung, so give up after a few strikes and let the
256 // caller fall back.
257 if(++m_failures <= 3)
258 qWarning() << "DVP-IN(D3D11): copy_buffer_to_texture failed:"
259 << nv_dvp_get_error_string(m_dvpCtx);
260 if(m_failures == kMaxFailures)
261 {
262 m_dead = true;
263 qWarning() << "DVP-IN(D3D11): giving up after" << kMaxFailures
264 << "consecutive failures; the DVP capture rung is unusable "
265 "on this setup";
266 }
267 return false;
268 }
269 m_failures = 0;
270 return true;
271 }
272
273 QRhiTexture* outputTexture() const noexcept override { return cfg.outputTexture; }
274
275 void acquireForRender() override
276 {
277 if(m_dvpCtx && m_dvpTex)
278 {
279 if(nv_dvp_acquire_texture(m_dvpCtx, m_dvpTex) != NV_DVP_SUCCESS)
280 {
281 qWarning() << "DVP-IN(D3D11): acquire_texture failed:"
282 << nv_dvp_get_error_string(m_dvpCtx);
283 }
284 }
285 }
286
287 void releaseAfterRender() override
288 {
289 if(m_dvpCtx && m_dvpTex)
290 {
291 if(nv_dvp_release_texture(m_dvpCtx, m_dvpTex) != NV_DVP_SUCCESS)
292 {
293 qWarning() << "DVP-IN(D3D11): release_texture failed:"
294 << nv_dvp_get_error_string(m_dvpCtx);
295 }
296 }
297 }
298};
299
300} // namespace score::gfx::interop
Vendor-neutral interface for GPU-direct video CAPTURE strategies.
Definition DvpCaptureD3D11.hpp:48
D3D11 capture strategy via NVIDIA "GPUDirect for Video" (DVP).
Definition DvpCaptureD3D11.hpp:27
std::size_t slotCount() const noexcept override
Definition DvpCaptureD3D11.hpp:234
bool ingestFrame(std::size_t i) override
Definition DvpCaptureD3D11.hpp:240
void * slotBuffer(std::size_t i) const noexcept override
Definition DvpCaptureD3D11.hpp:235
QRhiTexture * outputTexture() const noexcept override
Definition DvpCaptureD3D11.hpp:273
bool healthy() const noexcept
Definition DvpCaptureD3D11.hpp:232
void acquireForRender() override
Definition DvpCaptureD3D11.hpp:275
const char * name() const noexcept override
Definition DvpCaptureD3D11.hpp:60
Definition VideoCaptureStrategy.hpp:43
QRhiTexture * outputTexture
Definition VideoCaptureStrategy.hpp:62
std::uint32_t frameByteSize
Definition VideoCaptureStrategy.hpp:49
Per-graphics-API strategy for getting a captured video frame into a GPU QRhiTexture without CPU stagi...
Definition VideoCaptureStrategy.hpp:84