Skip to content

Commit

Permalink
VAAPI: Split out VAAPI GLX and DRM interops into separate files
Browse files Browse the repository at this point in the history
- just makes the code a little more manageable
  • Loading branch information
mark-kendall committed Nov 10, 2019
1 parent d3d2924 commit 400db72
Show file tree
Hide file tree
Showing 7 changed files with 997 additions and 971 deletions.
6 changes: 4 additions & 2 deletions mythtv/libs/libmythtv/libmythtv.pro
Original file line number Diff line number Diff line change
Expand Up @@ -486,8 +486,10 @@ using_frontend {

using_vaapi:using_opengl_video {
DEFINES += USING_VAAPI
HEADERS += decoders/mythvaapicontext.h opengl/mythvaapiinterop.h
SOURCES += decoders/mythvaapicontext.cpp opengl/mythvaapiinterop.cpp
HEADERS += decoders/mythvaapicontext.h opengl/mythvaapiinterop.h
SOURCES += decoders/mythvaapicontext.cpp opengl/mythvaapiinterop.cpp
HEADERS += opengl/mythvaapidrminterop.h opengl/mythvaapiglxinterop.h
SOURCES += opengl/mythvaapidrminterop.cpp opengl/mythvaapiglxinterop.cpp
LIBS += -lva -lva-x11 -lva-glx -lEGL
}

Expand Down
374 changes: 374 additions & 0 deletions mythtv/libs/libmythtv/opengl/mythvaapidrminterop.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,374 @@
// MythTV
#include "mythcorecontext.h"
#include "mythegldefs.h"
#include "videocolourspace.h"
#include "fourcc.h"
#include "mythvaapidrminterop.h"

#define LOC QString("VAAPIDRM: ")

MythVAAPIInteropDRM::MythVAAPIInteropDRM(MythRenderOpenGL *Context)
: MythVAAPIInterop(Context, VAAPIEGLDRM)
{
QString device = gCoreContext->GetSetting("VAAPIDevice");
if (device.isEmpty())
device = "/dev/dri/renderD128";
m_drmFile.setFileName(device);
if (m_drmFile.open(QIODevice::ReadWrite))
{
m_vaDisplay = vaGetDisplayDRM(m_drmFile.handle());
if (!m_vaDisplay)
{
LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create DRM VADisplay");
return;
}
}
else
{
LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to open %1").arg(device));
return;
}
InitaliseDisplay();
}

MythVAAPIInteropDRM::~MythVAAPIInteropDRM()
{
OpenGLLocker locker(m_context);

CleanupReferenceFrames();
DestroyDeinterlacer();
DeleteTextures();

if (m_drmFile.isOpen())
m_drmFile.close();
}

void MythVAAPIInteropDRM::DeleteTextures(void)
{
OpenGLLocker locker(m_context);

if (!m_openglTextures.isEmpty() && m_context->IsEGL())
{
LOG(VB_PLAYBACK, LOG_INFO, LOC + "Deleting DRM buffers");
QHash<unsigned long long, vector<MythVideoTexture*> >::const_iterator it = m_openglTextures.constBegin();
for ( ; it != m_openglTextures.constEnd(); ++it)
{
vector<MythVideoTexture*> textures = it.value();
vector<MythVideoTexture*>::iterator it2 = textures.begin();
for ( ; it2 != textures.end(); ++it2)
{
if ((*it2)->m_data)
{
m_context->eglDestroyImageKHR(m_context->GetEGLDisplay(), (*it2)->m_data);
(*it2)->m_data = nullptr;
}
}
}
}

MythVAAPIInterop::DeleteTextures();
}

void MythVAAPIInteropDRM::DestroyDeinterlacer(void)
{
if (m_filterGraph)
{
LOG(VB_PLAYBACK, LOG_INFO, LOC + "Deleting deinterlacer frame cache");
DeleteTextures();
}
MythVAAPIInterop::DestroyDeinterlacer();
}

void MythVAAPIInteropDRM::PostInitDeinterlacer(void)
{
// remove the old, non-deinterlaced frame cache
LOG(VB_PLAYBACK, LOG_INFO, LOC + "Deleting progressive frame cache");
DeleteTextures();
}

void MythVAAPIInteropDRM::CleanupReferenceFrames(void)
{
while (!m_referenceFrames.isEmpty())
{
AVBufferRef* ref = m_referenceFrames.takeLast();
av_buffer_unref(&ref);
}
}

void MythVAAPIInteropDRM::RotateReferenceFrames(AVBufferRef *Buffer)
{
if (!Buffer)
return;

// don't retain twice for double rate
if ((m_referenceFrames.size() > 0) &&
(static_cast<VASurfaceID>(reinterpret_cast<uintptr_t>(m_referenceFrames[0]->data)) ==
static_cast<VASurfaceID>(reinterpret_cast<uintptr_t>(Buffer->data))))
{
return;
}

m_referenceFrames.push_front(av_buffer_ref(Buffer));

// release old frames
while (m_referenceFrames.size() > 3)
{
AVBufferRef* ref = m_referenceFrames.takeLast();
av_buffer_unref(&ref);
}
}

vector<MythVideoTexture*> MythVAAPIInteropDRM::GetReferenceFrames(void)
{
vector<MythVideoTexture*> result;
int size = m_referenceFrames.size();
if (size < 1)
return result;

VASurfaceID next = static_cast<VASurfaceID>(reinterpret_cast<uintptr_t>(m_referenceFrames[0]->data));
VASurfaceID current = static_cast<VASurfaceID>(reinterpret_cast<uintptr_t>(m_referenceFrames[size > 1 ? 1 : 0]->data));
VASurfaceID last = static_cast<VASurfaceID>(reinterpret_cast<uintptr_t>(m_referenceFrames[size > 2 ? 2 : 0]->data));

if (!m_openglTextures.contains(next) || !m_openglTextures.contains(current) ||
!m_openglTextures.contains(last))
{
LOG(VB_GENERAL, LOG_ERR, LOC + "Reference frame error");
return result;
}

result = m_openglTextures[last];
foreach (MythVideoTexture* tex, m_openglTextures[current])
result.push_back(tex);
foreach (MythVideoTexture* tex, m_openglTextures[next])
result.push_back(tex);
return result;
}

vector<MythVideoTexture*> MythVAAPIInteropDRM::Acquire(MythRenderOpenGL *Context,
VideoColourSpace *ColourSpace,
VideoFrame *Frame,
FrameScanType Scan)
{
vector<MythVideoTexture*> result;
if (!Frame)
return result;

VASurfaceID id = VerifySurface(Context, Frame);
if (!id || !m_vaDisplay)
return result;

// Update frame colourspace and initialise on first frame
if (ColourSpace)
{
if (m_openglTextures.isEmpty())
ColourSpace->SetSupportedAttributes(ALL_PICTURE_ATTRIBUTES);
ColourSpace->UpdateColourSpace(Frame);
}

// Deinterlacing
bool needreferenceframes = false;

if (is_interlaced(Scan))
{
// allow GLSL deinterlacers
Frame->deinterlace_allowed = Frame->deinterlace_allowed | DEINT_SHADER;

// is GLSL preferred - and if so do we need reference frames
bool glsldeint = false;

// we explicitly use a shader if preferred over driver. If CPU only
// is preferred, the default will be to use the driver instead and if that
// fails we fall back to GLSL
MythDeintType shader = GetDoubleRateOption(Frame, DEINT_SHADER);
MythDeintType driver = GetDoubleRateOption(Frame, DEINT_DRIVER);
if (m_filterError)
shader = GetDoubleRateOption(Frame, DEINT_SHADER | DEINT_CPU | DEINT_DRIVER, DEINT_ALL);
if (shader && !driver)
{
glsldeint = true;
needreferenceframes = shader == DEINT_HIGH;
Frame->deinterlace_double = Frame->deinterlace_double | DEINT_SHADER;
}
else if (!shader && !driver) // singlerate
{
shader = GetSingleRateOption(Frame, DEINT_SHADER);
driver = GetSingleRateOption(Frame, DEINT_DRIVER);
if (m_filterError)
shader = GetSingleRateOption(Frame, DEINT_SHADER | DEINT_CPU | DEINT_DRIVER, DEINT_ALL);
if (shader && !driver)
{
glsldeint = true;
needreferenceframes = shader == DEINT_HIGH;
Frame->deinterlace_single = Frame->deinterlace_single | DEINT_SHADER;
}
}

// driver deinterlacing
if (!glsldeint)
id = Deinterlace(Frame, id, Scan);

// fallback to shaders if VAAPI deints fail
if (m_filterError)
Frame->deinterlace_allowed = Frame->deinterlace_allowed & ~DEINT_DRIVER;
}
else if (m_deinterlacer)
{
DestroyDeinterlacer();
}

if (needreferenceframes)
{
if (abs(Frame->frameCounter - m_discontinuityCounter) > 1)
CleanupReferenceFrames();
RotateReferenceFrames(reinterpret_cast<AVBufferRef*>(Frame->priv[0]));
}
else
{
CleanupReferenceFrames();
}
m_discontinuityCounter = Frame->frameCounter;

// return cached texture if available
if (m_openglTextures.contains(id))
{
if (needreferenceframes)
return GetReferenceFrames();
else
return m_openglTextures[id];
}

OpenGLLocker locker(m_context);

VAImage vaimage;
memset(&vaimage, 0, sizeof(vaimage));
vaimage.buf = vaimage.image_id = VA_INVALID_ID;
INIT_ST;
va_status = vaDeriveImage(m_vaDisplay, id, &vaimage);
CHECK_ST;
uint count = vaimage.num_planes;

VABufferInfo vabufferinfo;
memset(&vabufferinfo, 0, sizeof(vabufferinfo));
vabufferinfo.mem_type = VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME;
va_status = vaAcquireBufferHandle(m_vaDisplay, vaimage.buf, &vabufferinfo);
CHECK_ST;

VideoFrameType format = VATypeToMythType(vaimage.format.fourcc);
if (format == FMT_NONE)
{
LOG(VB_GENERAL, LOG_ERR, LOC + QString("Unsupported VA fourcc: %1")
.arg(fourcc_str(static_cast<int32_t>(vaimage.format.fourcc))));
}
else
{
if (count != planes(format))
{
LOG(VB_GENERAL, LOG_ERR, LOC + QString("Inconsistent plane count %1 != %2")
.arg(count).arg(planes(format)));
}
else
{
vector<QSize> sizes;
for (uint plane = 0 ; plane < count; ++plane)
{
QSize size(vaimage.width, vaimage.height);
if (plane > 0)
size = QSize(vaimage.width >> 1, vaimage.height >> 1);
sizes.push_back(size);
}

vector<MythVideoTexture*> textures = MythVideoTexture::CreateTextures(m_context, FMT_VAAPI, format, sizes);
if (textures.size() != count)
{
LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create all textures");
}
else
{
for (uint i = 0; i < textures.size(); ++i)
textures[i]->m_allowGLSLDeint = true;
CreateDRMBuffers(format, textures, vabufferinfo.handle, vaimage);
result = textures;
}
}
}

va_status = vaReleaseBufferHandle(m_vaDisplay, vaimage.buf);
CHECK_ST;
va_status = vaDestroyImage(m_vaDisplay, vaimage.image_id);
CHECK_ST;

m_openglTextures.insert(id, result);
if (needreferenceframes)
return GetReferenceFrames();
return result;
}

VideoFrameType MythVAAPIInteropDRM::VATypeToMythType(uint32_t Fourcc)
{
switch (Fourcc)
{
case VA_FOURCC_IYUV:
case VA_FOURCC_I420: return FMT_YV12;
case VA_FOURCC_NV12: return FMT_NV12;
case VA_FOURCC_YUY2: return FMT_YUY2;
case VA_FOURCC_UYVY: return FMT_YUY2; // ?
case VA_FOURCC_P010: return FMT_P010;
case VA_FOURCC_P016: return FMT_P016;
case VA_FOURCC_ARGB: return FMT_ARGB32;
case VA_FOURCC_RGBA: return FMT_RGBA32;
}
return FMT_NONE;
}

#ifndef DRM_FORMAT_R8
#define DRM_FORMAT_R8 MKTAG('R', '8', ' ', ' ')
#define DRM_FORMAT_GR88 MKTAG('G', 'R', '8', '8')
#define DRM_FORMAT_R16 MKTAG('R', '1', '6', ' ')
#define DRM_FORMAT_GR32 MKTAG('G', 'R', '3', '2')
#endif

/*! \brief Create a set of EGL images/DRM buffers associated with the given textures
*/
void MythVAAPIInteropDRM::CreateDRMBuffers(VideoFrameType Format,
vector<MythVideoTexture*> Textures,
uintptr_t Handle, VAImage &Image)
{
for (uint plane = 0; plane < Textures.size(); ++plane)
{
MythVideoTexture* texture = Textures[plane];
int fourcc = (Format == FMT_P010) ? DRM_FORMAT_R16 : DRM_FORMAT_R8;
if (plane > 0)
fourcc = (Format == FMT_P010) ? DRM_FORMAT_GR32 : DRM_FORMAT_GR88;
const EGLint attributes[] = {
EGL_LINUX_DRM_FOURCC_EXT, fourcc,
EGL_WIDTH, texture->m_size.width(),
EGL_HEIGHT, texture->m_size.height(),
EGL_DMA_BUF_PLANE0_FD_EXT, static_cast<EGLint>(Handle),
EGL_DMA_BUF_PLANE0_OFFSET_EXT, static_cast<EGLint>(Image.offsets[plane]),
EGL_DMA_BUF_PLANE0_PITCH_EXT, static_cast<EGLint>(Image.pitches[plane]),
EGL_NONE
};

EGLImageKHR image = m_context->eglCreateImageKHR(m_context->GetEGLDisplay(), EGL_NO_CONTEXT,
EGL_LINUX_DMA_BUF_EXT, nullptr, attributes);
if (!image)
LOG(VB_GENERAL, LOG_ERR, LOC + QString("No EGLImage for plane %1 %2")
.arg(plane).arg(m_context->GetEGLError()));

m_context->glBindTexture(texture->m_target, texture->m_textureId);
m_context->eglImageTargetTexture2DOES(texture->m_target, image);
m_context->glBindTexture(texture->m_target, 0);
texture->m_data = static_cast<unsigned char *>(image);
}
}

bool MythVAAPIInteropDRM::IsSupported(MythRenderOpenGL *Context)
{
if (!Context)
return false;

OpenGLLocker locker(Context);
return Context->IsEGL() &&
Context->HasEGLExtension("EGL_EXT_image_dma_buf_import") &&
Context->hasExtension("GL_OES_EGL_image");
}
Loading

0 comments on commit 400db72

Please sign in to comment.