Add KMS hooks for EGLFS plugin

Change-Id: Ic703334e52726cdd815cccf152d9d01aa63c803c
Reviewed-by: Laszlo Agocs <laszlo.agocs@digia.com>
bb10
Louai Al-Khanji 2014-09-04 16:04:23 +03:00
parent 8f88cc2c0f
commit 2f0fa59d59
2 changed files with 427 additions and 0 deletions

View File

@ -8,6 +8,11 @@ DEFINES += MESA_EGL_NO_X11_HEADERS
# EGLFS_PLATFORM_HOOKS_SOURCES += qeglfshooks_x11.cpp
# LIBS += -lX11 -lX11-xcb -lxcb
# Uncomment these to enable the KMS hooks.
# EGLFS_PLATFORM_HOOKS_SOURCES += qeglfshooks_kms.cpp
# CONFIG += link_pkgconfig
# PKGCONFIG += libdrm gbm
SOURCES += $$PWD/qeglfsintegration.cpp \
$$PWD/qeglfswindow.cpp \
$$PWD/qeglfsscreen.cpp \

View File

@ -0,0 +1,422 @@
/****************************************************************************
**
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the qmake spec of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qeglfshooks.h"
#include <QtPlatformSupport/private/qdevicediscovery_p.h>
#include <QtCore/private/qcore_unix_p.h>
#include <QtCore/QScopedPointer>
#include <QtGui/qpa/qplatformwindow.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <gbm.h>
QT_USE_NAMESPACE
class QEglKmsHooks : public QEglFSHooks
{
public:
QEglKmsHooks();
void platformInit() Q_DECL_OVERRIDE;
void platformDestroy() Q_DECL_OVERRIDE;
EGLNativeDisplayType platformDisplay() const Q_DECL_OVERRIDE;
QSizeF physicalScreenSize() const Q_DECL_OVERRIDE;
QSize screenSize() const Q_DECL_OVERRIDE;
int screenDepth() const Q_DECL_OVERRIDE;
QSurfaceFormat surfaceFormatFor(const QSurfaceFormat &inputFormat) const Q_DECL_OVERRIDE;
EGLNativeWindowType createNativeWindow(QPlatformWindow *platformWindow,
const QSize &size,
const QSurfaceFormat &format) Q_DECL_OVERRIDE;
void destroyNativeWindow(EGLNativeWindowType window) Q_DECL_OVERRIDE;
bool hasCapability(QPlatformIntegration::Capability cap) const Q_DECL_OVERRIDE;
void waitForVSync() const Q_DECL_OVERRIDE;
void waitForVSyncImpl();
bool setup_kms();
struct FrameBuffer {
FrameBuffer() : fb(0) {}
uint32_t fb;
};
FrameBuffer *framebufferForBufferObject(gbm_bo *bo);
private:
// device bits
QByteArray m_device;
int m_dri_fd;
gbm_device *m_gbm_device;
// KMS bits
drmModeConnector *m_drm_connector;
drmModeEncoder *m_drm_encoder;
drmModeModeInfo m_drm_mode;
quint32 m_drm_crtc;
// Drawing bits
gbm_surface *m_gbm_surface;
};
static QEglKmsHooks kms_hooks;
QEglFSHooks *platformHooks = &kms_hooks;
QEglKmsHooks::QEglKmsHooks()
: m_dri_fd(-1)
, m_gbm_device(Q_NULLPTR)
, m_drm_connector(Q_NULLPTR)
, m_drm_encoder(Q_NULLPTR)
, m_drm_crtc(0)
, m_gbm_surface(Q_NULLPTR)
{
}
void QEglKmsHooks::platformInit()
{
QDeviceDiscovery *d = QDeviceDiscovery::create(QDeviceDiscovery::Device_VideoMask);
QStringList devices = d->scanConnectedDevices();
d->deleteLater();
if (devices.isEmpty())
qFatal("Could not find DRM device!");
m_device = devices.first().toLocal8Bit();
m_dri_fd = qt_safe_open(m_device.constData(), O_RDWR | O_CLOEXEC);
if (m_dri_fd == -1) {
qErrnoWarning("Could not open DRM device %s", m_device.constData());
qFatal("DRM device required, aborting.");
}
if (!setup_kms())
qFatal("Could not set up KMS on device %s!", m_device.constData());
m_gbm_device = gbm_create_device(m_dri_fd);
if (!m_gbm_device)
qFatal("Could not initialize gbm on device %s!", m_device.constData());
}
void QEglKmsHooks::platformDestroy()
{
gbm_device_destroy(m_gbm_device);
m_gbm_device = Q_NULLPTR;
if (qt_safe_close(m_dri_fd) == -1)
qErrnoWarning("Could not close DRM device %s", m_device.constData());
m_dri_fd = -1;
}
EGLNativeDisplayType QEglKmsHooks::platformDisplay() const
{
return static_cast<EGLNativeDisplayType>(m_gbm_device);
}
QSizeF QEglKmsHooks::physicalScreenSize() const
{
return QSizeF(m_drm_connector->mmWidth,
m_drm_connector->mmHeight);
}
QSize QEglKmsHooks::screenSize() const
{
return QSize(m_drm_mode.hdisplay,
m_drm_mode.vdisplay);
}
int QEglKmsHooks::screenDepth() const
{
return 32;
}
QSurfaceFormat QEglKmsHooks::surfaceFormatFor(const QSurfaceFormat &inputFormat) const
{
QSurfaceFormat format(inputFormat);
format.setRenderableType(QSurfaceFormat::OpenGLES);
format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
format.setRedBufferSize(8);
format.setGreenBufferSize(8);
format.setBlueBufferSize(8);
return format;
}
EGLNativeWindowType QEglKmsHooks::createNativeWindow(QPlatformWindow *platformWindow,
const QSize &size,
const QSurfaceFormat &format)
{
Q_UNUSED(platformWindow);
Q_UNUSED(size);
Q_UNUSED(format);
if (m_gbm_surface) {
qWarning("Only single window apps supported!");
return 0;
}
m_gbm_surface = gbm_surface_create(m_gbm_device,
screenSize().width(),
screenSize().height(),
GBM_FORMAT_XRGB8888,
GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
if (!m_gbm_surface)
qFatal("Could not initialize GBM surface");
return reinterpret_cast<EGLNativeWindowType>(m_gbm_surface);
}
void QEglKmsHooks::destroyNativeWindow(EGLNativeWindowType window)
{
gbm_surface *surface = reinterpret_cast<gbm_surface *>(window);
if (surface == m_gbm_surface)
m_gbm_surface = Q_NULLPTR;
gbm_surface_destroy(surface);
}
bool QEglKmsHooks::hasCapability(QPlatformIntegration::Capability cap) const
{
switch (cap) {
case QPlatformIntegration::ThreadedPixmaps:
case QPlatformIntegration::OpenGL:
case QPlatformIntegration::ThreadedOpenGL:
case QPlatformIntegration::BufferQueueingOpenGL:
return true;
default:
return false;
}
}
static void gbm_bo_destroyed_callback(gbm_bo *bo, void *data)
{
QEglKmsHooks::FrameBuffer *fb = static_cast<QEglKmsHooks::FrameBuffer *>(data);
if (fb->fb) {
gbm_device *device = gbm_bo_get_device(bo);
drmModeRmFB(gbm_device_get_fd(device), fb->fb);
}
delete fb;
}
QEglKmsHooks::FrameBuffer *QEglKmsHooks::framebufferForBufferObject(gbm_bo *bo)
{
{
FrameBuffer *fb = static_cast<FrameBuffer *>(gbm_bo_get_user_data(bo));
if (fb)
return fb;
}
uint32_t width = gbm_bo_get_width(bo);
uint32_t height = gbm_bo_get_height(bo);
uint32_t stride = gbm_bo_get_stride(bo);
uint32_t handle = gbm_bo_get_handle(bo).u32;
QScopedPointer<FrameBuffer> fb(new FrameBuffer);
int ret = drmModeAddFB(m_dri_fd, width, height, 24, 32,
stride, handle, &fb->fb);
if (ret) {
qWarning("Failed to create KMS FB!");
return Q_NULLPTR;
}
gbm_bo_set_user_data(bo, fb.data(), gbm_bo_destroyed_callback);
return fb.take();
}
static void page_flip_handler(int fd,
unsigned int sequence,
unsigned int tv_sec,
unsigned int tv_usec,
void *user_data)
{
Q_UNUSED(fd);
Q_UNUSED(sequence);
Q_UNUSED(tv_sec);
Q_UNUSED(tv_usec);
// We are no longer flipping
*static_cast<bool *>(user_data) = false;
}
void QEglKmsHooks::waitForVSync() const
{
const_cast<QEglKmsHooks*>(this)->waitForVSyncImpl();
}
void QEglKmsHooks::waitForVSyncImpl()
{
if (!m_gbm_surface) {
qWarning("Cannot sync before platform init!");
return;
}
if (!gbm_surface_has_free_buffers(m_gbm_surface)) {
qWarning("Out of free GBM buffers!");
return;
}
gbm_bo *front_buffer = gbm_surface_lock_front_buffer(m_gbm_surface);
if (!front_buffer) {
qWarning("Could not lock GBM surface front buffer!");
return;
}
QEglKmsHooks::FrameBuffer *fb = framebufferForBufferObject(front_buffer);
int ret = drmModeSetCrtc(m_dri_fd,
m_drm_crtc,
fb->fb,
0, 0,
&m_drm_connector->connector_id, 1,
&m_drm_mode);
if (ret) {
qErrnoWarning("Could not set DRM mode!");
return;
}
bool flipping = true;
ret = drmModePageFlip(m_dri_fd,
m_drm_encoder->crtc_id,
fb->fb,
DRM_MODE_PAGE_FLIP_EVENT,
&flipping);
if (ret) {
qErrnoWarning("Could not queue DRM page flip!");
return;
}
drmEventContext drmEvent = {
DRM_EVENT_CONTEXT_VERSION,
Q_NULLPTR, // vblank handler
page_flip_handler // page flip handler
};
fd_set fds;
FD_ZERO(&fds);
FD_SET(m_dri_fd, &fds);
time_t start, cur;
time(&start);
while (flipping && (time(&cur) < start + 1)) {
timespec v;
memset(&v, 0, sizeof(v));
v.tv_sec = start + 1 - cur;
ret = qt_safe_select(m_dri_fd + 1, &fds, Q_NULLPTR, Q_NULLPTR, &v);
if (ret == 0) {
// timeout
break;
} else if (ret == -1) {
qErrnoWarning("Error while selecting on DRM fd");
break;
} else if (drmHandleEvent(m_dri_fd, &drmEvent)) {
qWarning("Could not handle DRM event!");
}
}
gbm_surface_release_buffer(m_gbm_surface, front_buffer);
}
bool QEglKmsHooks::setup_kms()
{
drmModeRes *resources;
drmModeConnector *connector;
drmModeEncoder *encoder;
quint32 crtc = 0;
int i;
resources = drmModeGetResources(m_dri_fd);
if (!resources) {
qWarning("drmModeGetResources failed");
return false;
}
for (i = 0; i < resources->count_connectors; i++) {
connector = drmModeGetConnector(m_dri_fd, resources->connectors[i]);
if (connector == NULL)
continue;
if (connector->connection == DRM_MODE_CONNECTED &&
connector->count_modes > 0) {
break;
}
drmModeFreeConnector(connector);
}
if (i == resources->count_connectors) {
qWarning("No currently active connector found.");
return false;
}
for (i = 0; i < resources->count_encoders; i++) {
encoder = drmModeGetEncoder(m_dri_fd, resources->encoders[i]);
if (encoder == NULL)
continue;
if (encoder->encoder_id == connector->encoder_id)
break;
drmModeFreeEncoder(encoder);
}
for (int j = 0; j < resources->count_crtcs; j++) {
if ((encoder->possible_crtcs & (1 << j))) {
crtc = resources->crtcs[j];
break;
}
}
if (crtc == 0)
qFatal("No suitable CRTC available");
m_drm_connector = connector;
m_drm_encoder = encoder;
m_drm_mode = connector->modes[0];
m_drm_crtc = crtc;
drmModeFreeResources(resources);
return true;
}