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