MythTV master
mythvaapiglxinterop.cpp
Go to the documentation of this file.
2
3#include "libmythbase/mythconfig.h"
4
5#define Cursor XCursor // Prevent conflicts with Qt6.
6#define pointer Xpointer // Prevent conflicts with Qt6.
7#if defined(_X11_XLIB_H_) && !defined(Bool)
8#define Bool int
9#endif
10#if CONFIG_VAAPI_X11
11#include <va/va_x11.h>
12#endif // CONFIG_VAAPI_X11
13#include <va/va_glx.h>
14#undef None // X11/X.h defines this. Causes compile failure in Qt6.
15#undef Cursor
16#undef pointer
17#undef Bool // Interferes with cmake moc file compilation
18
19// MythTV
21
22#define LOC QString("VAAPIGLX: ")
23
25 : MythVAAPIInterop(Player, Context, Type)
26{
27}
28
30{
32}
33
35{
36 uint flags = VA_FRAME_PICTURE;
37 if (!Frame)
38 return flags;
39
40 // Set deinterlacing flags if VPP is not available
42 {
43 flags = VA_FRAME_PICTURE;
44 }
45 else if (is_interlaced(Scan))
46 {
47 // As for VDPAU, only VAAPI can deinterlace these frames - so accept any deinterlacer
48 bool doublerate = true;
49 MythDeintType driverdeint = Frame->GetDoubleRateOption(DEINT_DRIVER | DEINT_CPU | DEINT_SHADER);
50 if (!driverdeint)
51 {
52 doublerate = false;
53 driverdeint = Frame->GetSingleRateOption(DEINT_DRIVER | DEINT_CPU | DEINT_SHADER);
54 }
55
56 if (driverdeint)
57 {
58 driverdeint = DEINT_BASIC;
59 if (m_basicDeinterlacer != driverdeint)
60 {
61 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Enabled deinterlacer '%1'")
62 .arg(MythVideoFrame::DeinterlacerName(driverdeint | DEINT_DRIVER, doublerate, FMT_VAAPI)));
63 }
64
65 bool top = Frame->m_interlacedReverse ? !Frame->m_topFieldFirst : Frame->m_topFieldFirst;
66 if (Scan == kScan_Interlaced)
67 {
68 Frame->m_deinterlaceInuse = driverdeint | DEINT_DRIVER;
69 Frame->m_deinterlaceInuse2x = doublerate;
70 flags = top ? VA_TOP_FIELD : VA_BOTTOM_FIELD;
71 }
72 else if (Scan == kScan_Intr2ndField)
73 {
74 Frame->m_deinterlaceInuse = driverdeint | DEINT_DRIVER;
75 Frame->m_deinterlaceInuse2x = doublerate;
76 flags = top ? VA_BOTTOM_FIELD : VA_TOP_FIELD;
77 }
78 m_basicDeinterlacer = driverdeint;
79 }
80 else if (m_basicDeinterlacer)
81 {
82 LOG(VB_PLAYBACK, LOG_INFO, LOC + "Disabled basic VAAPI deinterlacer");
84 }
85 }
86
87 // Update colourspace
89 {
90 switch (Frame->m_colorspace)
91 {
92 case AVCOL_SPC_BT709: m_vaapiColourSpace = VA_SRC_BT709; break;
93 case AVCOL_SPC_SMPTE170M:
94 case AVCOL_SPC_BT470BG: m_vaapiColourSpace = VA_SRC_BT601; break;
95 case AVCOL_SPC_SMPTE240M: m_vaapiColourSpace = VA_SRC_SMPTE_240; break;
96 default:
97 m_vaapiColourSpace = ((Frame->m_width < 1280) ? VA_SRC_BT601 : VA_SRC_BT709); break;
98 }
99 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Using '%1' VAAPI colourspace")
100 .arg((m_vaapiColourSpace == VA_SRC_BT709) ? "bt709" : ((m_vaapiColourSpace == VA_SRC_BT601) ? "bt601" : "smpte240")));
101 }
102 flags |= m_vaapiColourSpace;
103 return flags;
104}
105
107{
109 if (!ColourSpace || !m_vaDisplay)
110 return;
111
113
114 delete [] m_vaapiPictureAttributes;
116 int supported_controls = kPictureAttributeSupported_None;
117 QVector<VADisplayAttribute> supported;
118 int num = vaMaxNumDisplayAttributes(m_vaDisplay);
119 auto* attribs = new VADisplayAttribute[static_cast<unsigned int>(num)];
120
121 int actual = 0;
122 INIT_ST;
123 va_status = vaQueryDisplayAttributes(m_vaDisplay, attribs, &actual);
124 CHECK_ST;
125
126 supported.reserve(actual);
127 for (int i = 0; i < actual; i++)
128 {
129 int type = attribs[i].type;
130 if ((attribs[i].flags & VA_DISPLAY_ATTRIB_SETTABLE) &&
131 (type == VADisplayAttribBrightness ||
132 type == VADisplayAttribContrast ||
133 type == VADisplayAttribHue ||
134 type == VADisplayAttribSaturation ||
135 type == VADisplayAttribCSCMatrix))
136 {
137 supported.push_back(attribs[i]);
138 if (type == VADisplayAttribBrightness)
139 supported_controls += kPictureAttributeSupported_Brightness;
140 if (type == VADisplayAttribHue)
141 supported_controls += kPictureAttributeSupported_Hue;
142 if (type == VADisplayAttribContrast)
143 supported_controls += kPictureAttributeSupported_Contrast;
144 if (type == VADisplayAttribSaturation)
145 supported_controls += kPictureAttributeSupported_Colour;
146 }
147 }
148
149 // Set the supported attributes
150 ColourSpace->SetSupportedAttributes(static_cast<PictureAttributeSupported>(supported_controls));
151 // and listen for changes
153
154 // create
155 delete [] attribs;
156
157 if (supported.isEmpty())
158 return;
159
160 m_vaapiPictureAttributeCount = supported.size();
161 m_vaapiPictureAttributes = new VADisplayAttribute[static_cast<unsigned int>(m_vaapiPictureAttributeCount)];
162 for (int i = 0; i < m_vaapiPictureAttributeCount; i++)
163 m_vaapiPictureAttributes[i] = supported.at(i);
164
165 if (supported_controls & kPictureAttributeSupported_Brightness)
167 if (supported_controls & kPictureAttributeSupported_Hue)
169 if (supported_controls & kPictureAttributeSupported_Contrast)
171 if (supported_controls & kPictureAttributeSupported_Colour)
173}
174
176{
177 if (!m_vaDisplay)
178 return -1;
179
180 int adjustment = 0;
181 VADisplayAttribType attrib = VADisplayAttribBrightness;
182 switch (Attribute)
183 {
185 attrib = VADisplayAttribBrightness;
186 break;
188 attrib = VADisplayAttribContrast;
189 break;
191 attrib = VADisplayAttribHue;
192 adjustment = 50;
193 break;
195 attrib = VADisplayAttribSaturation;
196 break;
197 default:
198 return -1;
199 }
200
201 bool found = false;
202 for (int i = 0; i < m_vaapiPictureAttributeCount; i++)
203 {
204 if (m_vaapiPictureAttributes[i].type == attrib)
205 {
206 Value = std::clamp(Value, 0, 100);
207 int newval = Value + adjustment;
208 if (newval > 100) newval -= 100;
209 qreal range = (m_vaapiPictureAttributes[i].max_value - m_vaapiPictureAttributes[i].min_value) / 100.0;
210 int val = m_vaapiPictureAttributes[i].min_value + qRound(newval * range);
211 m_vaapiPictureAttributes[i].value = val;
212 found = true;
213 break;
214 }
215 }
216
217
218 if (found)
219 {
221 INIT_ST;
222 va_status = vaSetDisplayAttributes(m_vaDisplay,
225 CHECK_ST;
227 return Value;
228 }
229 return -1;
230}
231
233 : MythVAAPIInteropGLX(Player, Context, GL_VAAPIGLXCOPY)
234{
235 Display *display = glXGetCurrentDisplay();
236 if (!display)
237 {
238 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to open GLX display");
239 return;
240 }
241
242 m_vaDisplay = vaGetDisplayGLX(display);
243 if (!m_vaDisplay)
244 {
245 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create GLX VADisplay");
246 return;
247 }
249}
250
252{
254 {
255 LOG(VB_PLAYBACK, LOG_INFO, LOC + "Deleting GLX surface");
257 INIT_ST;
258 va_status = vaDestroySurfaceGLX(m_vaDisplay, m_glxSurface);
259 CHECK_ST;
260 }
261}
262
263std::vector<MythVideoTextureOpenGL*>
265 MythVideoColourSpace* ColourSpace,
267 FrameScanType Scan)
268{
269 std::vector<MythVideoTextureOpenGL*> result;
270 if (!Frame)
271 return result;
272
273 // Retrieve the VASurface
274 VASurfaceID id = VerifySurface(Context, Frame);
275 if (!id || !m_vaDisplay)
276 return result;
277
278 // Initialise colourspace on first frame
279 if (ColourSpace && m_openglTextures.isEmpty())
280 InitPictureAttributes(ColourSpace);
281
282 // Lock
284
285 // we only ever use one glx surface which is updated on each call
286 if (m_openglTextures.isEmpty())
287 {
288 // create a texture
289 // N.B. No apparent 10/12/16bit support here. Can't encourage vaCreateSurfaceGLX
290 // to work with a 16bit texture
292 if (!texture)
293 return result;
294
295 texture->m_plane = 0;
296 texture->m_planeCount = 1;
297 texture->m_frameType = FMT_VAAPI;
298 texture->m_frameFormat = FMT_RGBA32;
299
300 // create an associated GLX surface
301 INIT_ST;
302 va_status = vaCreateSurfaceGLX(m_vaDisplay, texture->m_texture->target(),
303 texture->m_texture->textureId(), &m_glxSurface);
304 CHECK_ST;
305 if (!m_glxSurface)
306 {
307 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create GLX surface.");
309 return result;
310 }
311
312 result.push_back(texture);
313 m_openglTextures.insert(DUMMY_INTEROP_ID, result);
314 }
315
316 if (m_openglTextures.isEmpty() || !m_glxSurface)
317 return result;
319
320 // VPP deinterlacing
321 id = Deinterlace(Frame, id, Scan);
322
323 // Copy surface to texture
324 INIT_ST;
325 va_status = vaCopySurfaceGLX(m_vaDisplay, m_glxSurface, id, GetFlagsForFrame(Frame, Scan));
326 CHECK_ST;
327 return result;
328}
329
330#if CONFIG_VAAPI_X11
331MythVAAPIInteropGLXPixmap::MythVAAPIInteropGLXPixmap(MythPlayerUI* Player, MythRenderOpenGL* Context)
332 : MythVAAPIInteropGLX(Player, Context, GL_VAAPIGLXPIX)
333{
334 m_vaDisplay = vaGetDisplay(glXGetCurrentDisplay());
335 if (!m_vaDisplay)
336 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create X11 VADisplay");
337 else
339 InitPixmaps();
340}
341
342MythVAAPIInteropGLXPixmap::~MythVAAPIInteropGLXPixmap()
343{
344 OpenGLLocker locker(m_openglContext);
345 Display* display = glXGetCurrentDisplay();
346 if (!InitPixmaps() || !display)
347 return;
348
349 LOG(VB_PLAYBACK, LOG_INFO, LOC + "Deleting GLX Pixmaps");
350 if (m_glxPixmap)
351 {
352 m_glxReleaseTexImageEXT(display, m_glxPixmap, GLX_FRONT_EXT);
353 XSync(display, False);
354 glXDestroyPixmap(display, m_glxPixmap);
355 }
356
357 if (m_pixmap)
358 XFreePixmap(display, m_pixmap);
359}
360
361std::vector<MythVideoTextureOpenGL*>
362MythVAAPIInteropGLXPixmap::Acquire(MythRenderOpenGL* Context,
363 MythVideoColourSpace* ColourSpace,
365 FrameScanType Scan)
366{
367 std::vector<MythVideoTextureOpenGL*> result;
368 if (!Frame)
369 return result;
370
371 // Retrieve the VASurface
372 VASurfaceID id = VerifySurface(Context, Frame);
373 if (!id || !m_vaDisplay)
374 return result;
375
376 // Initialise colourspace on first frame
377 if (ColourSpace && m_openglTextures.isEmpty())
378 InitPictureAttributes(ColourSpace);
379
380 // Lock
381 OpenGLLocker locker(m_openglContext);
382 if (!InitPixmaps())
383 return result;
384
385 // we only ever use one pixmap which is updated on each call
386 if (m_openglTextures.isEmpty())
387 {
388 // Get display
389 Display* display = glXGetCurrentDisplay();
390 if (!display)
391 return result;
392
393 // Get a framebuffer config
394 const std::array<const int,23> fbattribs {
395 GLX_RENDER_TYPE, GLX_RGBA_BIT,
396 GLX_X_RENDERABLE, True,
397 GLX_BIND_TO_TEXTURE_RGBA_EXT, True,
398 GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT,
399 GLX_BIND_TO_TEXTURE_TARGETS_EXT, GLX_TEXTURE_2D_BIT_EXT,
400 GLX_Y_INVERTED_EXT, True,
401 GLX_DOUBLEBUFFER, False,
402 GLX_RED_SIZE, 8,
403 GLX_GREEN_SIZE, 8,
404 GLX_BLUE_SIZE, 8,
405 GLX_ALPHA_SIZE, 8,
406 0 };
407 int fbcount = 0;
408 GLXFBConfig *fbs = glXChooseFBConfig(display, DefaultScreen(display), fbattribs.data(), &fbcount);
409 if (!fbcount)
410 {
411 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to retrieve GLX framebuffer config");
412 return result;
413 }
414
415 GLXFBConfig fbconfig = fbs[0];
416 XFree(reinterpret_cast<void*>(fbs));
417
418 // create pixmaps
419 uint width = static_cast<uint>(m_textureSize.width());
420 uint height = static_cast<uint>(m_textureSize.height());
421 XWindowAttributes xwattribs;
422 XGetWindowAttributes(display, DefaultRootWindow(display), &xwattribs);
423 m_pixmap = XCreatePixmap(display, DefaultRootWindow(display),
424 width, height, static_cast<uint>(xwattribs.depth));
425 if (!m_pixmap)
426 {
427 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create Pixmap");
428 return result;
429 }
430
431 const std::array<const int,7> attribs {
432 GLX_TEXTURE_TARGET_EXT, GLX_TEXTURE_2D_EXT,
433 GLX_TEXTURE_FORMAT_EXT, xwattribs.depth == 32 ? GLX_TEXTURE_FORMAT_RGBA_EXT : GLX_TEXTURE_FORMAT_RGB_EXT,
434 GLX_MIPMAP_TEXTURE_EXT, False, 0};
435
436 m_glxPixmap = glXCreatePixmap(display, fbconfig, m_pixmap, attribs.data());
437 if (!m_glxPixmap)
438 {
439 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create GLXPixmap");
440 return result;
441 }
442
443 // Create a texture
444 // N.B. as for GLX Copy there is no obvious 10/12/16bit support here.
445 // too many unknowns in this pipeline
446 std::vector<QSize> size;
447 size.push_back(m_textureSize);
448 std::vector<MythVideoTextureOpenGL*> textures = MythVideoTextureOpenGL::CreateTextures(m_openglContext, FMT_VAAPI, FMT_RGBA32, size);
449 if (textures.empty())
450 return result;
451 result.push_back(textures[0]);
452 m_openglTextures.insert(DUMMY_INTEROP_ID, result);
453 }
454
455 if (m_openglTextures.isEmpty() || !m_glxPixmap || !m_pixmap)
456 return result;
457 result = m_openglTextures[DUMMY_INTEROP_ID];
458
459 // VPP deinterlacing
460 id = Deinterlace(Frame, id, Scan);
461
462 // Copy the surface to the texture
463 INIT_ST;
464 va_status = vaSyncSurface(m_vaDisplay, id);
465 CHECK_ST;
466 auto width = static_cast<unsigned short>(m_textureSize.width());
467 auto height = static_cast<unsigned short>(m_textureSize.height());
468 va_status = vaPutSurface(m_vaDisplay, id, m_pixmap,
469 0, 0, width, height, 0, 0, width, height,
470 nullptr, 0, GetFlagsForFrame(Frame, Scan));
471 CHECK_ST;
472
473 Display* glxdisplay = glXGetCurrentDisplay();
474 if (glxdisplay)
475 {
476 XSync(glxdisplay, False);
477 m_openglContext->glBindTexture(QOpenGLTexture::Target2D, result[0]->m_textureId);
478 m_glxBindTexImageEXT(glxdisplay, m_glxPixmap, GLX_FRONT_EXT, nullptr);
479 m_openglContext->glBindTexture(QOpenGLTexture::Target2D, 0);
480 }
481 return result;
482}
483
484bool MythVAAPIInteropGLXPixmap::InitPixmaps()
485{
486 if (m_glxBindTexImageEXT && m_glxReleaseTexImageEXT)
487 return true;
488
489 OpenGLLocker locker(m_openglContext);
490 m_glxBindTexImageEXT = reinterpret_cast<MYTH_GLXBINDTEXIMAGEEXT>(glXGetProcAddressARB(reinterpret_cast<const GLubyte*>("glXBindTexImageEXT")));
491 m_glxReleaseTexImageEXT = reinterpret_cast<MYTH_GLXRELEASETEXIMAGEEXT>(glXGetProcAddressARB(reinterpret_cast<const GLubyte*>("glXReleaseTexImageEXT")));
492 if (!m_glxBindTexImageEXT || !m_glxReleaseTexImageEXT)
493 {
494 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to resolve 'texture_from_pixmap' functions");
495 return false;
496 }
497 return true;
498}
499
500bool MythVAAPIInteropGLXPixmap::IsSupported(MythRenderOpenGL* Context)
501{
502 if (!Context)
503 return false;
504
505 OpenGLLocker locker(Context);
506 Display* display = glXGetCurrentDisplay();
507 if (!display)
508 return false;
509 int screen = DefaultScreen(display);
510 QByteArray extensions(glXQueryExtensionsString(display, screen));
511 return extensions.contains("GLX_EXT_texture_from_pixmap");
512}
513#endif // CONFIG_VAAPI_X11
514
515#include "moc_mythvaapiglxinterop.cpp"
QOpenGLTexture * m_texture
MythRenderOpenGL * m_openglContext
QHash< unsigned long long, std::vector< MythVideoTextureOpenGL * > > m_openglTextures
std::vector< MythVideoTextureOpenGL * > Acquire(MythRenderOpenGL *Context, MythVideoColourSpace *ColourSpace, MythVideoFrame *Frame, FrameScanType Scan) override
MythVAAPIInteropGLXCopy(MythPlayerUI *Player, MythRenderOpenGL *Context)
MythVAAPIInteropGLX(MythPlayerUI *Player, MythRenderOpenGL *Context, InteropType Type)
void InitPictureAttributes(MythVideoColourSpace *ColourSpace)
VADisplayAttribute * m_vaapiPictureAttributes
MythDeintType m_basicDeinterlacer
uint GetFlagsForFrame(MythVideoFrame *Frame, FrameScanType Scan)
int SetPictureAttribute(PictureAttribute Attribute, int Value)
void InitaliseDisplay(void)
MythDeintType m_deinterlacer
VASurfaceID VerifySurface(MythRenderOpenGL *Context, MythVideoFrame *Frame)
VASurfaceID Deinterlace(MythVideoFrame *Frame, VASurfaceID Current, FrameScanType Scan)
MythVideoColourSpace contains a QMatrix4x4 that can convert YCbCr data to RGB.
void PictureAttributeChanged(PictureAttribute Attribute, int Value)
void SetSupportedAttributes(PictureAttributeSupported Supported)
Enable the given set of picture attributes.
int GetPictureAttribute(PictureAttribute Attribute)
static QString DeinterlacerName(MythDeintType Deint, bool DoubleRate, VideoFrameType Format=FMT_NONE)
Definition: mythframe.cpp:466
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.
static MythVideoTextureOpenGL * CreateTexture(MythRenderOpenGL *Context, QSize Size, GLenum Target=QOpenGLTexture::Target2D, QOpenGLTexture::PixelType PixelType=QOpenGLTexture::UInt8, QOpenGLTexture::PixelFormat PixelFormat=QOpenGLTexture::RGBA, QOpenGLTexture::TextureFormat Format=QOpenGLTexture::NoFormat, QOpenGLTexture::Filter Filter=QOpenGLTexture::Linear, QOpenGLTexture::WrapMode Wrap=QOpenGLTexture::ClampToEdge)
Create and initialise a MythVideoTexture that is backed by a QOpenGLTexture.
static void DeleteTexture(MythRenderOpenGL *Context, MythVideoTextureOpenGL *Texture)
unsigned int uint
Definition: compat.h:60
MythDeintType
Definition: mythframe.h:67
@ DEINT_DRIVER
Definition: mythframe.h:74
@ DEINT_BASIC
Definition: mythframe.h:69
@ DEINT_NONE
Definition: mythframe.h:68
@ DEINT_SHADER
Definition: mythframe.h:73
@ DEINT_CPU
Definition: mythframe.h:72
@ FMT_RGBA32
Definition: mythframe.h:34
@ FMT_VAAPI
Definition: mythframe.h:57
static constexpr uint64_t DUMMY_INTEROP_ID
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
#define LOC
#define CHECK_ST
#define INIT_ST
static eu8 clamp(eu8 value, eu8 low, eu8 high)
Definition: pxsup2dast.c:204
PictureAttributeSupported
@ kPictureAttributeSupported_Colour
@ kPictureAttributeSupported_Brightness
@ kPictureAttributeSupported_Hue
@ kPictureAttributeSupported_Contrast
@ kPictureAttributeSupported_None
FrameScanType
Definition: videoouttypes.h:95
@ kScan_Intr2ndField
Definition: videoouttypes.h:99
@ kScan_Interlaced
Definition: videoouttypes.h:98
PictureAttribute
@ kPictureAttribute_Contrast
@ kPictureAttribute_Brightness
@ kPictureAttribute_Colour
@ kPictureAttribute_Hue
bool is_interlaced(FrameScanType Scan)