Loading...
Searching...
No Matches
DvpOutputGl.hpp
1#pragma once
3#include <Gfx/Graph/encoders/GPUVideoEncoder.hpp>
4#include <nv_dvp_bridge.h>
5
6#include <QtGui/private/qrhigles2_p.h>
7#include <QOpenGLContext>
8
9#include <QDebug>
10
11#include <cstdint>
12#include <cstring>
13#include <functional>
14#include <memory>
15
16namespace score::gfx::interop
17{
18
32template <class Lock>
34{
35 using MakeEncoder = std::function<std::unique_ptr<score::gfx::GPUVideoEncoder>()>;
36 using GetOutputTexture
37 = std::function<QRhiTexture*(score::gfx::GPUVideoEncoder*)>;
38
40 Lock lock, MakeEncoder makeEncoder, GetOutputTexture getOutputTexture,
41 const char* name) noexcept
42 : m_lock{lock}
43 , m_makeEncoder{std::move(makeEncoder)}
44 , m_getOutputTexture{std::move(getOutputTexture)}
45 , m_name{name}
46 {
47 }
48
49 Lock m_lock;
50 MakeEncoder m_makeEncoder;
51 GetOutputTexture m_getOutputTexture;
52 const char* m_name{"DVP-GL"};
53
55
56 QOpenGLContext* m_glCtx{};
57
58 NvDvpContextHandle m_dvpCtx{};
59 NvDvpResourceHandle m_dvpTex{};
60 NvDvpResourceHandle m_dvpBuf{};
61 bool m_threadStarted{};
62
63 std::unique_ptr<score::gfx::GPUVideoEncoder> m_encoder;
64 QRhiTexture* m_encoderOutput{};
65
66 void* m_sysmem{};
67 uint32_t m_sysmemBytes{};
68 uint32_t m_sysmemStrideBytes{};
69 uint32_t m_sysmemRows{};
70 bool m_dmaLocked{};
71
72 const char* name() const noexcept override { return m_name; }
73
74 static bool isSupported(QRhi* rhi) { return rhi != nullptr; }
75
76 bool init(const score::gfx::interop::VideoOutputStrategyConfig& c) override
77 {
78 cfg = c;
79 if(!isSupported(cfg.rhi) || !cfg.state || !m_lock.valid())
80 return false;
81
82 auto* native
83 = static_cast<const QRhiGles2NativeHandles*>(cfg.rhi->nativeHandles());
84 if(!native || !native->context)
85 return false;
86 m_glCtx = native->context;
87
88 if(!cfg.state || !cfg.state->surface)
89 {
90 qWarning() << "DVP(GL): no offscreen surface available to bind the GL "
91 "context; cannot init DVP";
92 return false;
93 }
94 if(!m_glCtx->makeCurrent(cfg.state->surface))
95 {
96 qWarning() << "DVP(GL): makeCurrent() failed; cannot bind DVP to the "
97 "GL context";
98 return false;
99 }
100
101 // Build and validate the encoder BEFORE dvpInitGLContext: closing a DVP
102 // GL binding poisons the context it was bound to, and on an init-failure
103 // exit the caller keeps rendering on this very context (crashes later in
104 // endOffscreenFrame). Everything that can fail cheaply must fail here.
105 auto colorShader = score::gfx::colorMatrixOut(
106 AVCOL_SPC_BT709, AVCOL_TRC_BT709, AVCOL_RANGE_MPEG, AVCOL_PRI_BT709);
107
108 m_encoder = m_makeEncoder();
109 if(!m_encoder)
110 {
111 qWarning() << "DVP(GL): no fragment encoder for format";
112 return false;
113 }
114 m_encoder->init(
115 *cfg.rhi, *cfg.state, cfg.sourceTexture, cfg.width, cfg.height,
116 colorShader);
117 m_encoderOutput = m_getOutputTexture(m_encoder.get());
118 if(!m_encoderOutput)
119 {
120 qWarning() << "DVP(GL): encoder did not produce an output texture";
121 release();
122 return false;
123 }
124
125 const QSize texSize = m_encoderOutput->pixelSize();
126 m_sysmemRows = uint32_t(texSize.height());
127 m_sysmemStrideBytes = uint32_t(texSize.width()) * 4u;
128 m_sysmemBytes = m_sysmemStrideBytes * m_sysmemRows;
129
130 if(m_sysmemBytes != cfg.frameByteSize)
131 {
132 qWarning() << "DVP(GL): encoder output size" << m_sysmemBytes
133 << "does not match frame size" << cfg.frameByteSize;
134 release();
135 return false;
136 }
137
138 qDebug() << "DVP(GL): loading dvp.dll...";
139 if(nv_dvp_init_gl(&m_dvpCtx) != NV_DVP_SUCCESS || !m_dvpCtx)
140 {
141 qWarning() << "DVP(GL): init failed:" << nv_dvp_get_error_string(m_dvpCtx);
142 release();
143 return false;
144 }
145 qDebug() << "DVP(GL): dvp.dll loaded + GL context bound";
146 if(nv_dvp_thread_begin(m_dvpCtx) != NV_DVP_SUCCESS)
147 {
148 qWarning() << "DVP(GL): thread_begin failed:"
149 << nv_dvp_get_error_string(m_dvpCtx);
150 release();
151 return false;
152 }
153 m_threadStarted = true;
154
155 m_sysmem = nv_dvp_aligned_alloc(m_sysmemBytes);
156 if(!m_sysmem)
157 {
158 qWarning() << "DVP(GL): nv_dvp_aligned_alloc(" << m_sysmemBytes
159 << ") failed";
160 release();
161 return false;
162 }
163 std::memset(m_sysmem, 0, m_sysmemBytes);
164
165 if(!m_lock.lock(m_sysmem, m_sysmemBytes))
166 {
167 qWarning() << "DVP(GL): DMABufferLock(sysmem) failed";
168 release();
169 return false;
170 }
171 m_dmaLocked = true;
172
173 auto nt = m_encoderOutput->nativeTexture();
174 if(!nt.object)
175 {
176 qWarning() << "DVP(GL): encoder texture has no native handle";
177 release();
178 return false;
179 }
180 const uint32_t glTexId = uint32_t(nt.object);
181
182 if(nv_dvp_register_gl_texture(
183 m_dvpCtx, glTexId, NV_DVP_FORMAT_RGBA8,
184 uint32_t(texSize.width()), uint32_t(texSize.height()), &m_dvpTex)
185 != NV_DVP_SUCCESS)
186 {
187 qWarning() << "DVP(GL): register_gl_texture failed:"
188 << nv_dvp_get_error_string(m_dvpCtx);
189 release();
190 return false;
191 }
192 if(nv_dvp_register_sysmem_buffer(
193 m_dvpCtx, m_sysmem, NV_DVP_FORMAT_RGBA8, uint32_t(texSize.width()),
194 uint32_t(texSize.height()), m_sysmemStrideBytes, &m_dvpBuf)
195 != NV_DVP_SUCCESS)
196 {
197 qWarning() << "DVP(GL): register_sysmem_buffer failed:"
198 << nv_dvp_get_error_string(m_dvpCtx);
199 release();
200 return false;
201 }
202
203 return true;
204 }
205
206 void release() override
207 {
208 // DVP requires the GL context it was bound to to be current for
209 // unregister/close — at destroyOutput time nothing guarantees that.
210 if(m_dvpCtx && m_glCtx && cfg.state && cfg.state->surface
211 && QOpenGLContext::currentContext() != m_glCtx)
212 m_glCtx->makeCurrent(cfg.state->surface);
213
214 if(m_dvpCtx)
215 {
216 if(m_dvpTex)
217 {
218 nv_dvp_unregister(m_dvpCtx, m_dvpTex);
219 m_dvpTex = nullptr;
220 }
221 if(m_dvpBuf)
222 {
223 nv_dvp_unregister(m_dvpCtx, m_dvpBuf);
224 m_dvpBuf = nullptr;
225 }
226 if(m_threadStarted)
227 {
228 nv_dvp_thread_end(m_dvpCtx);
229 m_threadStarted = false;
230 }
231 nv_dvp_shutdown(m_dvpCtx);
232 m_dvpCtx = nullptr;
233 }
234 if(m_encoder)
235 {
236 m_encoder->release();
237 m_encoder.reset();
238 }
239 m_encoderOutput = nullptr;
240
241 if(m_dmaLocked && m_sysmem)
242 {
243 m_lock.unlock(m_sysmem, m_sysmemBytes);
244 m_dmaLocked = false;
245 }
246 if(m_sysmem)
247 {
248 nv_dvp_aligned_free(m_sysmem);
249 m_sysmem = nullptr;
250 }
251 m_glCtx = nullptr;
252 }
253
254 void encodeFrame(QRhiCommandBuffer& cb) override
255 {
256 /* AcquireTexture before render, ReleaseTexture after - matches
257 * AJA's oglapp.cpp main loop. */
258 if(nv_dvp_acquire_texture(m_dvpCtx, m_dvpTex) != NV_DVP_SUCCESS)
259 {
260 qWarning() << "DVP(GL): acquire_texture failed:"
261 << nv_dvp_get_error_string(m_dvpCtx);
262 }
263 m_encoder->exec(*cfg.rhi, cb);
264 }
265
266 void* prepareNextFrame() override
267 {
268 if(nv_dvp_release_texture(m_dvpCtx, m_dvpTex) != NV_DVP_SUCCESS)
269 {
270 qWarning() << "DVP(GL): release_texture failed:"
271 << nv_dvp_get_error_string(m_dvpCtx);
272 return nullptr;
273 }
274 if(nv_dvp_copy_texture_to_buffer(m_dvpCtx, m_dvpTex, m_dvpBuf)
275 != NV_DVP_SUCCESS)
276 {
277 qWarning() << "DVP(GL): copy_texture_to_buffer failed:"
278 << nv_dvp_get_error_string(m_dvpCtx);
279 return nullptr;
280 }
281 return m_sysmem;
282 }
283};
284
285} // namespace score::gfx::interop
Vendor-neutral interface for GPU-direct video OUTPUT strategies.
Base class for GPU-side video format conversion (RGBA to YUV).
Definition GPUVideoEncoder.hpp:30
OpenGL output strategy via NVIDIA "GPUDirect for Video" (DVP).
Definition DvpOutputGl.hpp:34
void encodeFrame(QRhiCommandBuffer &cb) override
Definition DvpOutputGl.hpp:254
void * prepareNextFrame() override
Definition DvpOutputGl.hpp:266
const char * name() const noexcept override
Definition DvpOutputGl.hpp:72
Generic config passed to VideoOutputStrategy::init. Vendor- specific bits (card handle,...
Definition VideoOutputStrategy.hpp:59
std::uint32_t frameByteSize
Definition VideoOutputStrategy.hpp:79
const score::gfx::RenderState * state
Definition VideoOutputStrategy.hpp:65
QRhiTexture * sourceTexture
Definition VideoOutputStrategy.hpp:70
Per-graphics-API strategy interface for vendor GPU-direct output. Each addon implements one impl per ...
Definition VideoOutputStrategy.hpp:97