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 for (int i = 0; i < actual; i++)
127 {
128 int type = attribs[i].type;
129 if ((attribs[i].flags & VA_DISPLAY_ATTRIB_SETTABLE) &&
130 (type == VADisplayAttribBrightness ||
131 type == VADisplayAttribContrast ||
132 type == VADisplayAttribHue ||
133 type == VADisplayAttribSaturation ||
134 type == VADisplayAttribCSCMatrix))
135 {
136 supported.push_back(attribs[i]);
137 if (type == VADisplayAttribBrightness)
138 supported_controls += kPictureAttributeSupported_Brightness;
139 if (type == VADisplayAttribHue)
140 supported_controls += kPictureAttributeSupported_Hue;
141 if (type == VADisplayAttribContrast)
142 supported_controls += kPictureAttributeSupported_Contrast;
143 if (type == VADisplayAttribSaturation)
144 supported_controls += kPictureAttributeSupported_Colour;
145 }
146 }
147
148 // Set the supported attributes
149 ColourSpace->SetSupportedAttributes(static_cast<PictureAttributeSupported>(supported_controls));
150 // and listen for changes
152
153 // create
154 delete [] attribs;
155
156 if (supported.isEmpty())
157 return;
158
159 m_vaapiPictureAttributeCount = supported.size();
160 m_vaapiPictureAttributes = new VADisplayAttribute[static_cast<unsigned int>(m_vaapiPictureAttributeCount)];
161 for (int i = 0; i < m_vaapiPictureAttributeCount; i++)
162 m_vaapiPictureAttributes[i] = supported.at(i);
163
164 if (supported_controls & kPictureAttributeSupported_Brightness)
166 if (supported_controls & kPictureAttributeSupported_Hue)
168 if (supported_controls & kPictureAttributeSupported_Contrast)
170 if (supported_controls & kPictureAttributeSupported_Colour)
172}
173
175{
176 if (!m_vaDisplay)
177 return -1;
178
179 int adjustment = 0;
180 VADisplayAttribType attrib = VADisplayAttribBrightness;
181 switch (Attribute)
182 {
184 attrib = VADisplayAttribBrightness;
185 break;
187 attrib = VADisplayAttribContrast;
188 break;
190 attrib = VADisplayAttribHue;
191 adjustment = 50;
192 break;
194 attrib = VADisplayAttribSaturation;
195 break;
196 default:
197 return -1;
198 }
199
200 bool found = false;
201 for (int i = 0; i < m_vaapiPictureAttributeCount; i++)
202 {
203 if (m_vaapiPictureAttributes[i].type == attrib)
204 {
205 Value = std::clamp(Value, 0, 100);
206 int newval = Value + adjustment;
207 if (newval > 100) newval -= 100;
208 qreal range = (m_vaapiPictureAttributes[i].max_value - m_vaapiPictureAttributes[i].min_value) / 100.0;
209 int val = m_vaapiPictureAttributes[i].min_value + qRound(newval * range);
210 m_vaapiPictureAttributes[i].value = val;
211 found = true;
212 break;
213 }
214 }
215
216
217 if (found)
218 {
220 INIT_ST;
221 va_status = vaSetDisplayAttributes(m_vaDisplay,
224 CHECK_ST;
226 return Value;
227 }
228 return -1;
229}
230
232 : MythVAAPIInteropGLX(Player, Context, GL_VAAPIGLXCOPY)
233{
234 Display *display = glXGetCurrentDisplay();
235 if (!display)
236 {
237 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to open GLX display");
238 return;
239 }
240
241 m_vaDisplay = vaGetDisplayGLX(display);
242 if (!m_vaDisplay)
243 {
244 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create GLX VADisplay");
245 return;
246 }
248}
249
251{
253 {
254 LOG(VB_PLAYBACK, LOG_INFO, LOC + "Deleting GLX surface");
256 INIT_ST;
257 va_status = vaDestroySurfaceGLX(m_vaDisplay, m_glxSurface);
258 CHECK_ST;
259 }
260}
261
262std::vector<MythVideoTextureOpenGL*>
264 MythVideoColourSpace* ColourSpace,
266 FrameScanType Scan)
267{
268 std::vector<MythVideoTextureOpenGL*> result;
269 if (!Frame)
270 return result;
271
272 // Retrieve the VASurface
273 VASurfaceID id = VerifySurface(Context, Frame);
274 if (!id || !m_vaDisplay)
275 return result;
276
277 // Initialise colourspace on first frame
278 if (ColourSpace && m_openglTextures.isEmpty())
279 InitPictureAttributes(ColourSpace);
280
281 // Lock
283
284 // we only ever use one glx surface which is updated on each call
285 if (m_openglTextures.isEmpty())
286 {
287 // create a texture
288 // N.B. No apparent 10/12/16bit support here. Can't encourage vaCreateSurfaceGLX
289 // to work with a 16bit texture
291 if (!texture)
292 return result;
293
294 texture->m_plane = 0;
295 texture->m_planeCount = 1;
296 texture->m_frameType = FMT_VAAPI;
297 texture->m_frameFormat = FMT_RGBA32;
298
299 // create an associated GLX surface
300 INIT_ST;
301 va_status = vaCreateSurfaceGLX(m_vaDisplay, texture->m_texture->target(),
302 texture->m_texture->textureId(), &m_glxSurface);
303 CHECK_ST;
304 if (!m_glxSurface)
305 {
306 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create GLX surface.");
308 return result;
309 }
310
311 result.push_back(texture);
312 m_openglTextures.insert(DUMMY_INTEROP_ID, result);
313 }
314
315 if (m_openglTextures.isEmpty() || !m_glxSurface)
316 return result;
318
319 // VPP deinterlacing
320 id = Deinterlace(Frame, id, Scan);
321
322 // Copy surface to texture
323 INIT_ST;
324 va_status = vaCopySurfaceGLX(m_vaDisplay, m_glxSurface, id, GetFlagsForFrame(Frame, Scan));
325 CHECK_ST;
326 return result;
327}
328
329#if CONFIG_VAAPI_X11
330MythVAAPIInteropGLXPixmap::MythVAAPIInteropGLXPixmap(MythPlayerUI* Player, MythRenderOpenGL* Context)
331 : MythVAAPIInteropGLX(Player, Context, GL_VAAPIGLXPIX)
332{
333 m_vaDisplay = vaGetDisplay(glXGetCurrentDisplay());
334 if (!m_vaDisplay)
335 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create X11 VADisplay");
336 else
338 InitPixmaps();
339}
340
341MythVAAPIInteropGLXPixmap::~MythVAAPIInteropGLXPixmap()
342{
343 OpenGLLocker locker(m_openglContext);
344 Display* display = glXGetCurrentDisplay();
345 if (!InitPixmaps() || !display)
346 return;
347
348 LOG(VB_PLAYBACK, LOG_INFO, LOC + "Deleting GLX Pixmaps");
349 if (m_glxPixmap)
350 {
351 m_glxReleaseTexImageEXT(display, m_glxPixmap, GLX_FRONT_EXT);
352 XSync(display, False);
353 glXDestroyPixmap(display, m_glxPixmap);
354 }
355
356 if (m_pixmap)
357 XFreePixmap(display, m_pixmap);
358}
359
360std::vector<MythVideoTextureOpenGL*>
361MythVAAPIInteropGLXPixmap::Acquire(MythRenderOpenGL* Context,
362 MythVideoColourSpace* ColourSpace,
364 FrameScanType Scan)
365{
366 std::vector<MythVideoTextureOpenGL*> result;
367 if (!Frame)
368 return result;
369
370 // Retrieve the VASurface
371 VASurfaceID id = VerifySurface(Context, Frame);
372 if (!id || !m_vaDisplay)
373 return result;
374
375 // Initialise colourspace on first frame
376 if (ColourSpace && m_openglTextures.isEmpty())
377 InitPictureAttributes(ColourSpace);
378
379 // Lock
380 OpenGLLocker locker(m_openglContext);
381 if (!InitPixmaps())
382 return result;
383
384 // we only ever use one pixmap which is updated on each call
385 if (m_openglTextures.isEmpty())
386 {
387 // Get display
388 Display* display = glXGetCurrentDisplay();
389 if (!display)
390 return result;
391
392 // Get a framebuffer config
393 const std::array<const int,23> fbattribs {
394 GLX_RENDER_TYPE, GLX_RGBA_BIT,
395 GLX_X_RENDERABLE, True,
396 GLX_BIND_TO_TEXTURE_RGBA_EXT, True,
397 GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT,
398 GLX_BIND_TO_TEXTURE_TARGETS_EXT, GLX_TEXTURE_2D_BIT_EXT,
399 GLX_Y_INVERTED_EXT, True,
400 GLX_DOUBLEBUFFER, False,
401 GLX_RED_SIZE, 8,
402 GLX_GREEN_SIZE, 8,
403 GLX_BLUE_SIZE, 8,
404 GLX_ALPHA_SIZE, 8,
405 0 };
406 int fbcount = 0;
407 GLXFBConfig *fbs = glXChooseFBConfig(display, DefaultScreen(display), fbattribs.data(), &fbcount);
408 if (!fbcount)
409 {
410 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to retrieve GLX framebuffer config");
411 return result;
412 }
413
414 GLXFBConfig fbconfig = fbs[0];
415 XFree(reinterpret_cast<void*>(fbs));
416
417 // create pixmaps
418 uint width = static_cast<uint>(m_textureSize.width());
419 uint height = static_cast<uint>(m_textureSize.height());
420 XWindowAttributes xwattribs;
421 XGetWindowAttributes(display, DefaultRootWindow(display), &xwattribs);
422 m_pixmap = XCreatePixmap(display, DefaultRootWindow(display),
423 width, height, static_cast<uint>(xwattribs.depth));
424 if (!m_pixmap)
425 {
426 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create Pixmap");
427 return result;
428 }
429
430 const std::array<const int,7> attribs {
431 GLX_TEXTURE_TARGET_EXT, GLX_TEXTURE_2D_EXT,
432 GLX_TEXTURE_FORMAT_EXT, xwattribs.depth == 32 ? GLX_TEXTURE_FORMAT_RGBA_EXT : GLX_TEXTURE_FORMAT_RGB_EXT,
433 GLX_MIPMAP_TEXTURE_EXT, False, 0};
434
435 m_glxPixmap = glXCreatePixmap(display, fbconfig, m_pixmap, attribs.data());
436 if (!m_glxPixmap)
437 {
438 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create GLXPixmap");
439 return result;
440 }
441
442 // Create a texture
443 // N.B. as for GLX Copy there is no obvious 10/12/16bit support here.
444 // too many unknowns in this pipeline
445 std::vector<QSize> size;
446 size.push_back(m_textureSize);
447 std::vector<MythVideoTextureOpenGL*> textures = MythVideoTextureOpenGL::CreateTextures(m_openglContext, FMT_VAAPI, FMT_RGBA32, size);
448 if (textures.empty())
449 return result;
450 result.push_back(textures[0]);
451 m_openglTextures.insert(DUMMY_INTEROP_ID, result);
452 }
453
454 if (m_openglTextures.isEmpty() || !m_glxPixmap || !m_pixmap)
455 return result;
456 result = m_openglTextures[DUMMY_INTEROP_ID];
457
458 // VPP deinterlacing
459 id = Deinterlace(Frame, id, Scan);
460
461 // Copy the surface to the texture
462 INIT_ST;
463 va_status = vaSyncSurface(m_vaDisplay, id);
464 CHECK_ST;
465 auto width = static_cast<unsigned short>(m_textureSize.width());
466 auto height = static_cast<unsigned short>(m_textureSize.height());
467 va_status = vaPutSurface(m_vaDisplay, id, m_pixmap,
468 0, 0, width, height, 0, 0, width, height,
469 nullptr, 0, GetFlagsForFrame(Frame, Scan));
470 CHECK_ST;
471
472 Display* glxdisplay = glXGetCurrentDisplay();
473 if (glxdisplay)
474 {
475 XSync(glxdisplay, False);
476 m_openglContext->glBindTexture(QOpenGLTexture::Target2D, result[0]->m_textureId);
477 m_glxBindTexImageEXT(glxdisplay, m_glxPixmap, GLX_FRONT_EXT, nullptr);
478 m_openglContext->glBindTexture(QOpenGLTexture::Target2D, 0);
479 }
480 return result;
481}
482
483bool MythVAAPIInteropGLXPixmap::InitPixmaps()
484{
485 if (m_glxBindTexImageEXT && m_glxReleaseTexImageEXT)
486 return true;
487
488 OpenGLLocker locker(m_openglContext);
489 m_glxBindTexImageEXT = reinterpret_cast<MYTH_GLXBINDTEXIMAGEEXT>(glXGetProcAddressARB(reinterpret_cast<const GLubyte*>("glXBindTexImageEXT")));
490 m_glxReleaseTexImageEXT = reinterpret_cast<MYTH_GLXRELEASETEXIMAGEEXT>(glXGetProcAddressARB(reinterpret_cast<const GLubyte*>("glXReleaseTexImageEXT")));
491 if (!m_glxBindTexImageEXT || !m_glxReleaseTexImageEXT)
492 {
493 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to resolve 'texture_from_pixmap' functions");
494 return false;
495 }
496 return true;
497}
498
499bool MythVAAPIInteropGLXPixmap::IsSupported(MythRenderOpenGL* Context)
500{
501 if (!Context)
502 return false;
503
504 OpenGLLocker locker(Context);
505 Display* display = glXGetCurrentDisplay();
506 if (!display)
507 return false;
508 int screen = DefaultScreen(display);
509 QByteArray extensions(glXQueryExtensionsString(display, screen));
510 return extensions.contains("GLX_EXT_texture_from_pixmap");
511}
512#endif // CONFIG_VAAPI_X11
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: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.
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:206
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)