MythTV master
mythvdpauinterop.cpp
Go to the documentation of this file.
1// MythTV
3
7
8#define LOC QString("VDPAUInterop: ")
9
11 MythRenderOpenGL* Context,
12 MythCodecID CodecId)
13{
14 if (!(Context && Player))
15 return nullptr;
16
18 GetVDPAUTypes(Context, types);
19 if (auto vdpau = types.find(FMT_VDPAU); vdpau != types.end())
20 if (std::any_of(vdpau->second.cbegin(), vdpau->second.cend(), [](auto Type) { return Type == GL_VDPAU; }))
21 return new MythVDPAUInterop(Player, Context, CodecId);
22
23 return nullptr;
24}
25
27{
29 return;
30 if (Render->hasExtension("GL_NV_vdpau_interop"))
31 {
32 Types[FMT_VDPAU] = { GL_VDPAU };
33 return;
34 }
35 LOG(VB_GENERAL, LOG_WARNING, LOC + "GL_NV_vdpau_interop is not available");
36}
37
39 : MythOpenGLInterop(Context, GL_VDPAU, Player),
40 m_codec(CodecId)
41{
42}
43
45{
46 if (!m_openglContext)
47 return;
48
49 if (m_colourSpace)
51
54 Cleanup();
55 delete m_helper;
56}
57
59{
61
62 // per the spec, this should automatically release any registered
63 // and mapped surfaces
64 if (m_finiNV)
65 m_finiNV();
66
67 if (m_helper && !m_preempted)
68 {
71 }
72
73 m_mixer = 0;
77 m_mixerSize = QSize();
78 m_mixerChroma = VDP_CHROMA_TYPE_420;
79 m_mapped = false;
81}
82
84{
85 while (!m_referenceFrames.isEmpty())
86 {
87 AVBufferRef* ref = m_referenceFrames.takeLast();
88 av_buffer_unref(&ref);
89 }
90}
91
93{
94 if (!Buffer)
95 return;
96
97 // don't retain twice for double rate
98 if (!m_referenceFrames.empty() &&
99 (static_cast<VdpVideoSurface>(reinterpret_cast<uintptr_t>(m_referenceFrames[0]->data)) ==
100 static_cast<VdpVideoSurface>(reinterpret_cast<uintptr_t>(Buffer->data))))
101 {
102 return;
103 }
104
105 m_referenceFrames.push_front(av_buffer_ref(Buffer));
106
107 // release old frames
108 while (m_referenceFrames.size() > 3)
109 {
110 AVBufferRef* ref = m_referenceFrames.takeLast();
111 av_buffer_unref(&ref);
112 }
113}
114
115bool MythVDPAUInterop::InitNV(AVVDPAUDeviceContext* DeviceContext)
116{
117 if (!DeviceContext || !m_openglContext)
118 return false;
119
122 return true;
123
125 m_initNV = reinterpret_cast<MYTH_VDPAUINITNV>(m_openglContext->GetProcAddress("glVDPAUInitNV"));
126 m_finiNV = reinterpret_cast<MYTH_VDPAUFININV>(m_openglContext->GetProcAddress("glVDPAUFiniNV"));
127 m_registerNV = reinterpret_cast<MYTH_VDPAUREGOUTSURFNV>(m_openglContext->GetProcAddress("glVDPAURegisterOutputSurfaceNV"));
128 m_accessNV = reinterpret_cast<MYTH_VDPAUSURFACCESSNV>(m_openglContext->GetProcAddress("glVDPAUSurfaceAccessNV"));
129 m_mapNV = reinterpret_cast<MYTH_VDPAUMAPSURFNV>(m_openglContext->GetProcAddress("glVDPAUMapSurfacesNV"));
130 m_unmapNV = reinterpret_cast<MYTH_VDPAUMAPSURFNV>(m_openglContext->GetProcAddress("glVDPAUUnmapSurfacesNV"));
131
132 delete m_helper;
133 m_helper = nullptr;
134
136 {
137 m_helper = new MythVDPAUHelper(DeviceContext);
138 if (m_helper->IsValid())
139 {
141 LOG(VB_PLAYBACK, LOG_INFO, LOC + "Ready");
142 return true;
143 }
144 delete m_helper;
145 m_helper = nullptr;
146 }
147
148 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to retrieve procs");
149 return false;
150}
151
152bool MythVDPAUInterop::InitVDPAU(AVVDPAUDeviceContext* DeviceContext, VdpVideoSurface Surface,
153 MythDeintType Deint, bool DoubleRate)
154{
155 if (!m_helper || !m_openglContext || !Surface || !DeviceContext)
156 return false;
157
158 VdpChromaType chroma = VDP_CHROMA_TYPE_420;
159 QSize size = m_helper->GetSurfaceParameters(Surface, chroma);
160
161 if (m_mixer && (chroma != m_mixerChroma || size != m_mixerSize || Deint != m_deinterlacer))
162 Cleanup();
163
164 if (!m_mixer)
165 {
166 m_mixer = m_helper->CreateMixer(size, chroma, Deint);
167 m_deinterlacer = Deint;
168 m_mixerChroma = chroma;
169 m_mixerSize = size;
171 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Setup deinterlacer '%1'")
173 }
174
175 if (!m_outputSurface)
176 {
178 if (m_outputSurface)
179 {
180 std::vector<QSize> sizes;
181 sizes.push_back(size);
182 std::vector<MythVideoTextureOpenGL*> textures =
184 if (textures.empty())
185 return false;
186 m_openglTextures.insert(DUMMY_INTEROP_ID, textures);
187 }
188 }
189
191 {
192 if (!m_outputSurfaceReg && !m_openglTextures.empty())
193 {
194 // This may fail if another interop is registered (but should not happen if
195 // decoder creation is working properly). Subsequent surface
196 // registration will then fail and we will try again on the next pass
197 // NOLINTBEGIN(performance-no-int-to-ptr)
198 m_initNV(reinterpret_cast<void*>(static_cast<uintptr_t>(DeviceContext->device)),
199 reinterpret_cast<const void*>(DeviceContext->get_proc_address));
200 GLuint texid = m_openglTextures[DUMMY_INTEROP_ID][0]->m_textureId;
201 m_outputSurfaceReg = m_registerNV(reinterpret_cast<void*>(static_cast<uintptr_t>(m_outputSurface)),
202 QOpenGLTexture::Target2D, 1, &texid);
203 // NOLINTEND(performance-no-int-to-ptr)
204 // this happens if there is another interop registered to this OpenGL context
206 {
207 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to register VdpOutputSurface. Will retry.");
208 }
209 else
210 {
211 m_accessNV(m_outputSurfaceReg, QOpenGLBuffer::ReadOnly);
212 }
213 }
214 return true;
215 }
216
217 return (m_mixer != 0U) && (m_outputSurface != 0U);
218}
219
227std::vector<MythVideoTextureOpenGL*>
229 MythVideoColourSpace* ColourSpace,
231 FrameScanType Scan)
232{
233 std::vector<MythVideoTextureOpenGL*> result;
234 if (!Frame)
235 return result;
236
237 if (m_preempted)
238 {
239 // Don't spam the logs with this warning
241 LOG(VB_PLAYBACK, LOG_INFO, LOC + "Display preempted. Decoder needs to be reset");
242 m_preemptedWarning = true;
243 return result;
244 }
245
246 if (Context && (Context != m_openglContext))
247 LOG(VB_GENERAL, LOG_WARNING, LOC + "Mismatched OpenGL contexts");
248
249 // Check size
250 QSize surfacesize(Frame->m_width, Frame->m_height);
251 if (m_textureSize != surfacesize)
252 {
253 if (!m_textureSize.isEmpty())
254 LOG(VB_GENERAL, LOG_WARNING, LOC + "Video texture size changed!");
255 m_textureSize = surfacesize;
256 }
257
258 // Lock
260
261 // Retrieve hardware frames context and AVVDPAUDeviceContext
262 if ((Frame->m_pixFmt != AV_PIX_FMT_VDPAU) || (Frame->m_type != FMT_VDPAU) ||
263 !Frame->m_buffer || !Frame->m_priv[1])
264 return result;
265
266 auto* buffer = reinterpret_cast<AVBufferRef*>(Frame->m_priv[1]);
267 if (!buffer || !buffer->data)
268 return result;
269 auto* frames = reinterpret_cast<AVHWFramesContext*>(buffer->data);
270 if (!frames || !frames->device_ctx)
271 return result;
272 auto *devicecontext = reinterpret_cast<AVVDPAUDeviceContext*>(frames->device_ctx->hwctx);
273 if (!devicecontext)
274 return result;
275
276 // Initialise
277 if (!InitNV(devicecontext))
278 return result;
279
280 // Retrieve surface - we need its size to create the mixer and output surface
281 auto surface = static_cast<VdpVideoSurface>(reinterpret_cast<uintptr_t>(Frame->m_buffer));
282 if (!surface)
283 return result;
284
285 // Workaround HEVC interlaced bug
286 // VDPAU driver hangs if we try to render progressive HEVC as interlaced (tested with version 418.56)
287 // FFmpeg clearly currently has issues with interlaced HEVC (https://trac.ffmpeg.org/ticket/4141).
288 // Streams are always return with the field height.
289 // Deinterlacing does work with (some?) HEVC material flagged as interlaced.
290 if ((kCodec_HEVC_VDPAU == m_codec) && is_interlaced(Scan) && !Frame->m_interlaced)
291 {
292 // This should only be logged a couple of times before the scan is detected as progressive
293 LOG(VB_GENERAL, LOG_INFO, LOC + "Ignoring scan for non-interlaced HEVC frame");
294 Scan = kScan_Progressive;
295 }
296
297 // Check for deinterlacing - VDPAU deinterlacers trump all others as we can only
298 // deinterlace VDPAU frames here. So accept any deinterlacer.
299 // N.B. basic deinterlacing requires no additional setup and is managed with
300 // the field/frame parameter
301 bool doublerate = true;
302 MythDeintType deinterlacer = DEINT_BASIC;
303 if (is_interlaced(Scan))
304 {
305 MythDeintType driverdeint = Frame->GetDoubleRateOption(DEINT_DRIVER | DEINT_CPU | DEINT_SHADER,
306 DEINT_ALL);
307 if (!driverdeint)
308 {
309 doublerate = false;
310 driverdeint = Frame->GetSingleRateOption(DEINT_DRIVER | DEINT_CPU | DEINT_SHADER, DEINT_ALL);
311 }
312
313 if (driverdeint)
314 {
315 Frame->m_deinterlaceInuse = driverdeint | DEINT_DRIVER;
316 Frame->m_deinterlaceInuse2x = doublerate;
317 deinterlacer = driverdeint;
318 }
319 }
320
321 if ((deinterlacer == DEINT_HIGH) || (deinterlacer == DEINT_MEDIUM))
322 {
323 if (qAbs(Frame->m_frameCounter - m_discontinuityCounter) > 1)
325 RotateReferenceFrames(reinterpret_cast<AVBufferRef*>(Frame->m_priv[0]));
326 }
327 else
328 {
330 }
331 m_discontinuityCounter = Frame->m_frameCounter;
332
333 // We need a mixer, an output surface and mapped texture
334 if (!InitVDPAU(devicecontext, surface, deinterlacer, doublerate))
335 return result;
336
337 // Update colourspace and initialise on first frame - after mixer is created
338 if (ColourSpace)
339 {
340 if (!m_colourSpace)
341 {
342 if (m_helper->IsAttributeAvailable(VDP_VIDEO_MIXER_ATTRIBUTE_CSC_MATRIX))
343 {
346 }
347 else
348 {
349 // N.B. CSC matrix support should always be available so there is no fallback.
351 LOG(VB_GENERAL, LOG_WARNING, LOC + "No VDPAU CSC matrix support");
352 }
353
354 ColourSpace->IncrRef();
355 m_colourSpace = ColourSpace;
356 }
357 ColourSpace->UpdateColourSpace(Frame);
358 }
359
360 // Render surface
361 if (m_mapped)
364 static_cast<int>(Frame->m_interlacedReverse ? !Frame->m_topFieldFirst :
365 Frame->m_topFieldFirst), m_referenceFrames);
367 m_mapped = true;
369}
370
371void MythVDPAUInterop::UpdateColourSpace(bool /*PrimariesChanged*/)
372{
374 return;
375
378}
379
381{
382 // N.B. Pre-emption is irrecoverable here. We ensure the error state is recorded
383 // and when AvFormatDecoder/MythCodecContext hit a problem, IsPreempted is checked.
384 // The decoder context is then released, along with the associated interop
385 // class (i.e. this) and a new interop is created.
386 LOG(VB_GENERAL, LOG_INFO, LOC + "VDPAU display preempted");
387 m_preempted = true;
388}
389
391{
392 return m_preempted;
393}
uint64_t m_discontinuityCounter
std::map< VideoFrameType, InteropTypes > InteropMap
virtual void DeleteTextures()
MythRenderOpenGL * m_openglContext
QHash< unsigned long long, std::vector< MythVideoTextureOpenGL * > > m_openglTextures
QFunctionPointer GetProcAddress(const QString &Proc) const
A simple wrapper around VDPAU functionality.
void DeleteOutputSurface(VdpOutputSurface Surface)
VdpOutputSurface CreateOutputSurface(QSize Size)
void MixerRender(VdpVideoMixer Mixer, VdpVideoSurface Source, VdpOutputSurface Dest, FrameScanType Scan, int TopFieldFirst, QVector< AVBufferRef * > &Frames)
bool IsValid(void) const
QSize GetSurfaceParameters(VdpVideoSurface Surface, VdpChromaType &Chroma)
void DeleteMixer(VdpVideoMixer Mixer)
static bool HaveVDPAU(bool Reinit=false)
void SetCSCMatrix(VdpVideoMixer Mixer, MythVideoColourSpace *ColourSpace)
VdpVideoMixer CreateMixer(QSize Size, VdpChromaType ChromaType=VDP_CHROMA_TYPE_420, MythDeintType Deinterlacer=DEINT_BASIC)
void DisplayPreempted(void)
bool IsAttributeAvailable(uint Attribute)
VdpVideoMixer m_mixer
VdpChromaType m_mixerChroma
MythVDPAUSurfaceNV m_outputSurfaceReg
VdpOutputSurface m_outputSurface
bool InitVDPAU(AVVDPAUDeviceContext *DeviceContext, VdpVideoSurface Surface, MythDeintType Deint, bool DoubleRate)
std::vector< MythVideoTextureOpenGL * > Acquire(MythRenderOpenGL *Context, MythVideoColourSpace *ColourSpace, MythVideoFrame *Frame, FrameScanType Scan) override
Map VDPAU video surfaces to an OpenGL texture.
MYTH_VDPAUFININV m_finiNV
static void GetVDPAUTypes(MythRenderOpenGL *Render, MythInteropGPU::InteropMap &Types)
MythCodecID m_codec
MythVDPAUHelper * m_helper
MYTH_VDPAUREGOUTSURFNV m_registerNV
void RotateReferenceFrames(AVBufferRef *Buffer)
MYTH_VDPAUMAPSURFNV m_mapNV
MythDeintType m_deinterlacer
QVector< AVBufferRef * > m_referenceFrames
MythVideoColourSpace * m_colourSpace
MYTH_VDPAUSURFACCESSNV m_accessNV
~MythVDPAUInterop() override
void UpdateColourSpace(bool PrimariesChanged)
MYTH_VDPAUINITNV m_initNV
bool InitNV(AVVDPAUDeviceContext *DeviceContext)
bool IsPreempted(void) const
static MythVDPAUInterop * CreateVDPAU(MythPlayerUI *Player, MythRenderOpenGL *Context, MythCodecID CodecId)
void CleanupDeinterlacer(void)
MYTH_VDPAUMAPSURFNV m_unmapNV
void DisplayPreempted(void)
MythVDPAUInterop(MythPlayerUI *Player, MythRenderOpenGL *Context, MythCodecID CodecID)
MythVideoColourSpace contains a QMatrix4x4 that can convert YCbCr data to RGB.
void SetSupportedAttributes(PictureAttributeSupported Supported)
Enable the given set of picture attributes.
void Updated(bool PrimariesChanged)
bool UpdateColourSpace(const MythVideoFrame *Frame)
Set the current colourspace to use.
static QString DeinterlacerName(MythDeintType Deint, bool DoubleRate, VideoFrameType Format=FMT_NONE)
Definition: mythframe.cpp:462
static std::vector< MythVideoTextureOpenGL * > CreateTextures(MythRenderOpenGL *Context, VideoFrameType Type, VideoFrameType Format, std::vector< QSize > Sizes, GLenum Target=QOpenGLTexture::Target2D)
Create a set of textures suitable for the given Type and Format.
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
virtual int IncrRef(void)
Increments reference count.
static const struct wl_interface * types[]
MythCodecID
Definition: mythcodecid.h:14
@ kCodec_HEVC_VDPAU
Definition: mythcodecid.h:49
MythDeintType
Definition: mythframe.h:67
@ DEINT_HIGH
Definition: mythframe.h:71
@ DEINT_DRIVER
Definition: mythframe.h:74
@ DEINT_MEDIUM
Definition: mythframe.h:70
@ DEINT_BASIC
Definition: mythframe.h:69
@ DEINT_NONE
Definition: mythframe.h:68
@ DEINT_SHADER
Definition: mythframe.h:73
@ DEINT_ALL
Definition: mythframe.h:75
@ DEINT_CPU
Definition: mythframe.h:72
@ FMT_RGBA32
Definition: mythframe.h:34
@ FMT_VDPAU
Definition: mythframe.h:56
static constexpr uint64_t DUMMY_INTEROP_ID
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
#define LOC
MythVDPAUSurfaceNV(APIENTRY *)(const void *, GLenum, GLsizei, const GLuint *) MYTH_VDPAUREGOUTSURFNV
void(APIENTRY *)(void) MYTH_VDPAUFININV
void(APIENTRY *)(GLsizei, MythVDPAUSurfaceNV *) MYTH_VDPAUMAPSURFNV
void(APIENTRY *)(const void *, const void *) MYTH_VDPAUINITNV
void(APIENTRY *)(MythVDPAUSurfaceNV, GLenum) MYTH_VDPAUSURFACCESSNV
Definition: surface.h:4
@ kPictureAttributeSupported_None
FrameScanType
Definition: videoouttypes.h:95
@ kScan_Progressive
bool is_interlaced(FrameScanType Scan)
#define ALL_PICTURE_ATTRIBUTES