Loading...
Searching...
No Matches
DvpCaptureGl.hpp
1#pragma once
4#include <nv_dvp_bridge.h>
5
6#include <QtGui/private/qrhigles2_p.h>
7#include <QOpenGLContext>
8
9#include <QDebug>
10#include <QtCore/QtGlobal>
11
12#include <array>
13#include <cstdint>
14#include <cstring>
15
16namespace score::gfx::interop
17{
18
31template <class Lock>
33{
34 DvpCaptureGl(Lock lock, NvDvpFormat dvpFormat, const char* name) noexcept
35 : m_lock{lock}, m_dvpFormat{dvpFormat}, m_name{name} {}
36
37 Lock m_lock;
38 NvDvpFormat m_dvpFormat{};
39 const char* m_name{"DVP-GL"};
40
42
43 QOpenGLContext* m_glCtx{};
44
45 NvDvpContextHandle m_dvpCtx{};
46 NvDvpResourceHandle m_dvpTex{};
47 QRhiTexture* m_ownTexture{};
48 bool m_threadStarted{};
49
50 static constexpr int kMaxFailures = 8;
51 int m_failures{};
52 bool m_dead{};
53
54 static constexpr std::size_t kSlotCount = 3;
55 struct Slot
56 {
57 void* sysmem{};
58 NvDvpResourceHandle dvpBuf{};
59 bool dmaLocked{};
60 };
61 std::array<Slot, kSlotCount> m_slots{};
62
63 uint32_t m_sysmemBytes{};
64 uint32_t m_sysmemStrideBytes{};
65 int m_texW{};
66 int m_texH{};
67
68 const char* name() const noexcept override { return m_name; }
69
70 bool init(const score::gfx::interop::VideoCaptureStrategyConfig& c) override
71 {
72 cfg = c;
73 if(!cfg.rhi || !m_lock.valid() || !cfg.outputTexture)
74 return false;
75
76 auto* native
77 = static_cast<const QRhiGles2NativeHandles*>(cfg.rhi->nativeHandles());
78 if(!native || !native->context)
79 return false;
80 m_glCtx = native->context;
81
82 // dvpInitGLContext binds DVP to the *current* GL context, but init() runs
83 // outside a QRhi frame so nothing is current here. Make the QRhi GL
84 // context current on the render state's offscreen surface first. If this
85 // fails, DVP init would fail anyway (and with a misleading error), so bail
86 // out with a clear message.
87 if(!cfg.state || !cfg.state->surface)
88 {
89 qWarning() << "DVP-IN(GL): no offscreen surface available to bind the "
90 "GL context; cannot init DVP";
91 return false;
92 }
93 if(!m_glCtx->makeCurrent(cfg.state->surface))
94 {
95 qWarning() << "DVP-IN(GL): makeCurrent() failed; cannot bind DVP to "
96 "the GL context";
97 return false;
98 }
99
100 // Validate everything that can fail cheaply BEFORE dvpInitGLContext:
101 // closing a DVP GL binding poisons the context it was bound to, and on
102 // an init-failure exit the caller keeps rendering on this context.
103 const QSize texSize = cfg.outputTexture->pixelSize();
104 m_texW = texSize.width();
105 m_texH = texSize.height();
106 m_sysmemStrideBytes = static_cast<uint32_t>(m_texW) * 4u;
107 m_sysmemBytes = m_sysmemStrideBytes * static_cast<uint32_t>(m_texH);
108
109 if(m_sysmemBytes != cfg.frameByteSize)
110 {
111 qWarning() << "DVP-IN(GL): texture byte size" << m_sysmemBytes
112 << "!=" << cfg.frameByteSize;
113 return false;
114 }
115
116 // DVP writes into a texture through an FBO-attachment path — the plain
117 // decoder input texture (created with no flags) is rejected by
118 // dvpMemcpyLined with DVP_STATUS_ERROR. Create our own RenderTarget-
119 // flagged clone; the capture node swaps it into the decoder's sampler
120 // (strategy-owned texture protocol via outputTexture()).
121 m_ownTexture
122 = cfg.rhi->newTexture(cfg.outputTexture->format(), texSize, 1,
123 QRhiTexture::RenderTarget);
124 if(!m_ownTexture || !m_ownTexture->create())
125 {
126 qWarning() << "DVP-IN(GL): could not create render-target texture";
127 delete m_ownTexture;
128 m_ownTexture = nullptr;
129 return false;
130 }
131
132 auto nt = m_ownTexture->nativeTexture();
133 if(!nt.object)
134 {
135 delete m_ownTexture;
136 m_ownTexture = nullptr;
137 return false;
138 }
139 const uint32_t glTexId = uint32_t(nt.object);
140
141 qDebug() << "DVP-IN(GL): loading dvp.dll...";
142 if(nv_dvp_init_gl(&m_dvpCtx) != NV_DVP_SUCCESS || !m_dvpCtx)
143 {
144 qWarning() << "DVP-IN(GL): init failed:"
145 << nv_dvp_get_error_string(m_dvpCtx);
146 // The commonest failure of all -- every box without libdvp, and every GL
147 // context that is not GLX, lands here. It is before m_threadStarted, so
148 // release() is not safe to call yet: free the render-target texture
149 // created just above by hand.
150 delete m_ownTexture;
151 m_ownTexture = nullptr;
152 return false;
153 }
154 if(nv_dvp_thread_begin(m_dvpCtx) != NV_DVP_SUCCESS)
155 {
156 release();
157 return false;
158 }
159 m_threadStarted = true;
160 qDebug() << "DVP-IN(GL): dvp.dll loaded + GL context bound";
161
162 const NvDvpFormat dvpFmt = m_dvpFormat;
163
164 if(nv_dvp_register_gl_texture(
165 m_dvpCtx, glTexId, dvpFmt, uint32_t(m_texW), uint32_t(m_texH),
166 &m_dvpTex)
167 != NV_DVP_SUCCESS)
168 {
169 qWarning() << "DVP-IN(GL): register_gl_texture failed:"
170 << nv_dvp_get_error_string(m_dvpCtx);
171 release();
172 return false;
173 }
174
175 for(auto& slot : m_slots)
176 {
177 slot.sysmem = nv_dvp_aligned_alloc(m_sysmemBytes);
178 if(!slot.sysmem)
179 {
180 release();
181 return false;
182 }
183 std::memset(slot.sysmem, 0, m_sysmemBytes);
184 if(!m_lock.lock(slot.sysmem, m_sysmemBytes))
185 {
186 release();
187 return false;
188 }
189 slot.dmaLocked = true;
190 if(nv_dvp_register_sysmem_buffer(
191 m_dvpCtx, slot.sysmem, dvpFmt, uint32_t(m_texW),
192 uint32_t(m_texH), m_sysmemStrideBytes, &slot.dvpBuf)
193 != NV_DVP_SUCCESS)
194 {
195 release();
196 return false;
197 }
198 }
199
200 // One real transfer before claiming the rung -- registration proves only
201 // that DVP accepted the descriptors, not that a transfer completes. Same
202 // reasoning as VendorDmaRegistrar::verifyTransfer on the output side.
203 // Escape hatch: the probe is a safety net against an unrecoverable
204 // per-frame failure, but no working DVP setup exists on any machine here
205 // to prove it never false-positives on a healthy one. If it ever refuses a
206 // rung that actually works, SCORE_GFX_DVP_NO_PROBE=1 skips it without a
207 // rebuild.
208 if(!qEnvironmentVariableIsSet("SCORE_GFX_DVP_NO_PROBE"))
209 {
210 if(m_slots[0].dvpBuf && m_dvpTex
211 && nv_dvp_copy_buffer_to_texture(m_dvpCtx, m_slots[0].dvpBuf, m_dvpTex)
212 != NV_DVP_SUCCESS)
213 {
214 qWarning() << "DVP-IN(GL): probe transfer failed:"
215 << nv_dvp_get_error_string(m_dvpCtx)
216 << "- DVP capture unusable here, falling back";
217 release();
218 return false;
219 }
220 }
221 return true;
222 }
223
224 void release() override
225 {
226 // DVP requires its GL context current for unregister/close; nothing
227 // guarantees that at destroyOutput time.
228 if(m_dvpCtx && m_glCtx && cfg.state && cfg.state->surface
229 && QOpenGLContext::currentContext() != m_glCtx)
230 m_glCtx->makeCurrent(cfg.state->surface);
231
232 if(m_dvpCtx)
233 {
234 for(auto& slot : m_slots)
235 {
236 if(slot.dvpBuf)
237 {
238 nv_dvp_unregister(m_dvpCtx, slot.dvpBuf);
239 slot.dvpBuf = nullptr;
240 }
241 if(slot.dmaLocked)
242 {
243 m_lock.unlock(slot.sysmem, m_sysmemBytes);
244 slot.dmaLocked = false;
245 }
246 if(slot.sysmem)
247 {
248 nv_dvp_aligned_free(slot.sysmem);
249 slot.sysmem = nullptr;
250 }
251 }
252 if(m_dvpTex)
253 {
254 nv_dvp_unregister(m_dvpCtx, m_dvpTex);
255 m_dvpTex = nullptr;
256 }
257 if(m_threadStarted)
258 {
259 nv_dvp_thread_end(m_dvpCtx);
260 m_threadStarted = false;
261 }
262 nv_dvp_shutdown(m_dvpCtx);
263 m_dvpCtx = nullptr;
264 }
265 // Don't delete cfg.outputTexture — we don't own it. m_ownTexture is
266 // ours; the capture node detaches it from the decoder before release()
267 // (strategy-owned texture protocol).
268 delete m_ownTexture;
269 m_ownTexture = nullptr;
270 m_glCtx = nullptr;
271 }
272
273 std::size_t slotCount() const noexcept override { return kSlotCount; }
274 void* slotBuffer(std::size_t i) const noexcept override
275 {
276 return i < kSlotCount ? m_slots[i].sysmem : nullptr;
277 }
278
279 // The capture thread only publishes the filled slot: dvpMemcpyLined into a
280 // GL texture fails (DVP_STATUS_ERROR) when issued from a thread without the
281 // owning GL context current, so the actual DVP upload happens in
282 // acquireForRender() on the render thread — same SPSC pattern as the
283 // CUDA capture strategies.
284 CaptureSlotPublisher m_publisher;
285
286 bool ingestFrame(std::size_t i) override
287 {
288 if(i >= kSlotCount)
289 return false;
290 if(!m_slots[i].dvpBuf || !m_dvpTex)
291 return false;
292 m_publisher.publish(i);
293 return true;
294 }
295
296 QRhiTexture* outputTexture() const noexcept override
297 {
298 return m_ownTexture ? m_ownTexture : cfg.outputTexture;
299 }
300
301 void acquireForRender() override
302 {
303 if(!m_dvpCtx || !m_dvpTex)
304 return;
305 // The upload brackets its own EndAPI ... WaitAPI pair (UPBGE shape);
306 // no separate per-frame acquire/release, and no DVP calls at all on
307 // frames without a fresh capture.
308 if(const int i = m_publisher.consume(); i >= 0)
309 {
310 if(m_dead)
311 return;
312 if(nv_dvp_copy_buffer_to_texture(m_dvpCtx, m_slots[i].dvpBuf, m_dvpTex)
313 != NV_DVP_SUCCESS)
314 {
315 // Same reasoning as the D3D11 variant: every frame takes the same
316 // path with the same buffers, so a failure is permanent, and
317 // retrying it forever produces a hang instead of a degraded rung.
318 if(++m_failures <= 3)
319 qWarning() << "DVP-IN(GL): copy_buffer_to_texture failed:"
320 << nv_dvp_get_error_string(m_dvpCtx);
321 if(m_failures == kMaxFailures)
322 {
323 m_dead = true;
324 qWarning() << "DVP-IN(GL): giving up after" << kMaxFailures
325 << "consecutive failures; the DVP capture rung is "
326 "unusable on this setup";
327 }
328 }
329 else
330 {
331 m_failures = 0;
332 }
333 }
334 }
335
336 void releaseAfterRender() override { }
337
340 bool healthy() const noexcept { return !m_dead; }
341};
342
343} // namespace score::gfx::interop
Backend-neutral plumbing shared by GPU-direct video CAPTURE strategies (no graphics-API headers).
Vendor-neutral interface for GPU-direct video CAPTURE strategies.
Lock-free single-producer/single-consumer slot handoff.
Definition CaptureStrategyCommon.hpp:37
int consume() noexcept
Returns the published slot index, or -1 if none is pending.
Definition CaptureStrategyCommon.hpp:45
Definition DvpCaptureGl.hpp:56
OpenGL capture strategy via NVIDIA "GPUDirect for Video" (DVP).
Definition DvpCaptureGl.hpp:33
QRhiTexture * outputTexture() const noexcept override
Definition DvpCaptureGl.hpp:296
bool healthy() const noexcept
Definition DvpCaptureGl.hpp:340
void acquireForRender() override
Definition DvpCaptureGl.hpp:301
const char * name() const noexcept override
Definition DvpCaptureGl.hpp:68
bool ingestFrame(std::size_t i) override
Definition DvpCaptureGl.hpp:286
std::size_t slotCount() const noexcept override
Definition DvpCaptureGl.hpp:273
void * slotBuffer(std::size_t i) const noexcept override
Definition DvpCaptureGl.hpp:274
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