eglfs: Support virtual desktops in the OpenGL cursor

The GBM-based hardware cursor already has this. Let's implement it
in the commonly used OpenGL cursor too. The main user will be the
EGLDevice backend which does not currently have a hardware cursor
but supports multiple screens.

This also means QEglFSCursor must be capable of operating on different
contexts (and what's more, non-sharing contexts).

Task-number: QTBUG-55161
Change-Id: Ie23bba1e6aab34b04d689f26a84c19a2bde518da
Reviewed-by: Andy Nichols <andy.nichols@qt.io>
This commit is contained in:
Laszlo Agocs 2016-08-08 11:37:43 +02:00
parent e930425228
commit b305702d1e
9 changed files with 198 additions and 76 deletions

View File

@ -59,12 +59,11 @@
QT_BEGIN_NAMESPACE
QEglFSCursor::QEglFSCursor(QPlatformScreen *screen)
: m_visible(true),
m_screen(static_cast<QEglFSScreen *>(screen)),
m_program(0),
m_textureEntry(0),
m_deviceListener(0),
m_updateRequested(false)
: m_visible(true),
m_screen(static_cast<QEglFSScreen *>(screen)),
m_activeScreen(nullptr),
m_deviceListener(0),
m_updateRequested(false)
{
QByteArray hideCursorVal = qgetenv("QT_QPA_EGLFS_HIDECURSOR");
if (!hideCursorVal.isEmpty())
@ -116,15 +115,14 @@ void QEglFSCursorDeviceListener::onDeviceListChanged(QInputDeviceManager::Device
void QEglFSCursor::resetResources()
{
if (QOpenGLContext::currentContext()) {
delete m_program;
glDeleteTextures(1, &m_cursor.customCursorTexture);
glDeleteTextures(1, &m_cursorAtlas.texture);
if (QOpenGLContext *ctx = QOpenGLContext::currentContext()) {
GraphicsContextData &gfx(m_gfx[ctx]);
delete gfx.program;
glDeleteTextures(1, &gfx.customCursorTexture);
glDeleteTextures(1, &gfx.atlasTexture);
gfx = GraphicsContextData();
}
m_program = 0;
m_cursor.customCursorTexture = 0;
m_cursor.customCursorPending = !m_cursor.customCursorImage.isNull();
m_cursorAtlas.texture = 0;
}
void QEglFSCursor::createShaderPrograms()
@ -146,15 +144,16 @@ void QEglFSCursor::createShaderPrograms()
" gl_FragColor = texture2D(texture, textureCoord).bgra;\n"
"}\n";
m_program = new QOpenGLShaderProgram;
m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, textureVertexProgram);
m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, textureFragmentProgram);
m_program->bindAttributeLocation("vertexCoordEntry", 0);
m_program->bindAttributeLocation("textureCoordEntry", 1);
m_program->link();
GraphicsContextData &gfx(m_gfx[QOpenGLContext::currentContext()]);
gfx.program = new QOpenGLShaderProgram;
gfx.program->addShaderFromSourceCode(QOpenGLShader::Vertex, textureVertexProgram);
gfx.program->addShaderFromSourceCode(QOpenGLShader::Fragment, textureFragmentProgram);
gfx.program->bindAttributeLocation("vertexCoordEntry", 0);
gfx.program->bindAttributeLocation("textureCoordEntry", 1);
gfx.program->link();
m_textureEntry = m_program->uniformLocation("texture");
m_matEntry = m_program->uniformLocation("mat");
gfx.textureEntry = gfx.program->uniformLocation("texture");
gfx.matEntry = gfx.program->uniformLocation("mat");
}
void QEglFSCursor::createCursorTexture(uint *texture, const QImage &image)
@ -214,7 +213,7 @@ void QEglFSCursor::changeCursor(QCursor *cursor, QWindow *window)
Q_UNUSED(window);
const QRect oldCursorRect = cursorRect();
if (setCurrentCursor(cursor))
update(oldCursorRect | cursorRect());
update(oldCursorRect | cursorRect(), false);
}
bool QEglFSCursor::setCurrentCursor(QCursor *cursor)
@ -238,16 +237,17 @@ bool QEglFSCursor::setCurrentCursor(QCursor *cursor)
hs * (m_cursor.shape / m_cursorAtlas.cursorsPerRow),
ws, hs);
m_cursor.hotSpot = m_cursorAtlas.hotSpots[m_cursor.shape];
m_cursor.texture = m_cursorAtlas.texture;
m_cursor.useCustomCursor = false;
m_cursor.size = QSize(m_cursorAtlas.cursorWidth, m_cursorAtlas.cursorHeight);
} else {
QImage image = cursor->pixmap().toImage();
m_cursor.textureRect = QRectF(0, 0, 1, 1);
m_cursor.hotSpot = cursor->hotSpot();
m_cursor.texture = 0; // will get updated in the next render()
m_cursor.useCustomCursor = false; // will get updated in the next render()
m_cursor.size = image.size();
m_cursor.customCursorImage = image;
m_cursor.customCursorPending = true;
m_cursor.customCursorKey = m_cursor.customCursorImage.cacheKey();
}
return true;
@ -257,17 +257,20 @@ bool QEglFSCursor::setCurrentCursor(QCursor *cursor)
class CursorUpdateEvent : public QEvent
{
public:
CursorUpdateEvent(const QPoint &pos, const QRegion &rgn)
CursorUpdateEvent(const QPoint &pos, const QRect &rect, bool allScreens)
: QEvent(QEvent::Type(QEvent::User + 1)),
m_pos(pos),
m_region(rgn)
m_rect(rect),
m_allScreens(allScreens)
{ }
QPoint pos() const { return m_pos; }
QRegion region() const { return m_region; }
QRegion rect() const { return m_rect; }
bool allScreens() const { return m_allScreens; }
private:
QPoint m_pos;
QRegion m_region;
QRect m_rect;
bool m_allScreens;
};
bool QEglFSCursor::event(QEvent *e)
@ -275,21 +278,30 @@ bool QEglFSCursor::event(QEvent *e)
if (e->type() == QEvent::User + 1) {
CursorUpdateEvent *ev = static_cast<CursorUpdateEvent *>(e);
m_updateRequested = false;
QWindowSystemInterface::handleExposeEvent(m_screen->topLevelAt(ev->pos()), ev->region());
QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents);
if (!ev->allScreens()) {
QWindow *w = m_screen->topLevelAt(ev->pos()); // works for the entire virtual desktop, no need to loop
if (w) {
QWindowSystemInterface::handleExposeEvent(w, ev->rect());
QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents);
}
} else {
for (QWindow *w : qGuiApp->topLevelWindows())
QWindowSystemInterface::handleExposeEvent(w, w->geometry());
QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents);
}
return true;
}
return QPlatformCursor::event(e);
}
void QEglFSCursor::update(const QRegion &rgn)
void QEglFSCursor::update(const QRect &rect, bool allScreens)
{
if (!m_updateRequested) {
// Must not flush the window system events directly from here since we are likely to
// be a called directly from QGuiApplication's processMouseEvents. Flushing events
// could cause reentering by dispatching more queued mouse events.
m_updateRequested = true;
QCoreApplication::postEvent(this, new CursorUpdateEvent(m_cursor.pos, rgn));
QCoreApplication::postEvent(this, new CursorUpdateEvent(m_cursor.pos, rect, allScreens));
}
}
@ -308,8 +320,9 @@ void QEglFSCursor::setPos(const QPoint &pos)
QGuiApplicationPrivate::inputDeviceManager()->setCursorPos(pos);
const QRect oldCursorRect = cursorRect();
m_cursor.pos = pos;
update(oldCursorRect | cursorRect());
m_screen->handleCursorMove(m_cursor.pos);
update(oldCursorRect | cursorRect(), false);
for (QPlatformScreen *screen : m_screen->virtualSiblings())
static_cast<QEglFSScreen *>(screen)->handleCursorMove(m_cursor.pos);
}
void QEglFSCursor::pointerEvent(const QMouseEvent &event)
@ -318,8 +331,9 @@ void QEglFSCursor::pointerEvent(const QMouseEvent &event)
return;
const QRect oldCursorRect = cursorRect();
m_cursor.pos = event.screenPos().toPoint();
update(oldCursorRect | cursorRect());
m_screen->handleCursorMove(m_cursor.pos);
update(oldCursorRect | cursorRect(), false);
for (QPlatformScreen *screen : m_screen->virtualSiblings())
static_cast<QEglFSScreen *>(screen)->handleCursorMove(m_cursor.pos);
}
void QEglFSCursor::paintOnScreen()
@ -327,15 +341,35 @@ void QEglFSCursor::paintOnScreen()
if (!m_visible)
return;
const QRectF cr = cursorRect();
const QRect screenRect(m_screen->geometry());
const GLfloat x1 = 2 * (cr.left() / screenRect.width()) - 1;
const GLfloat x2 = 2 * (cr.right() / screenRect.width()) - 1;
const GLfloat y1 = 1 - (cr.top() / screenRect.height()) * 2;
const GLfloat y2 = 1 - (cr.bottom() / screenRect.height()) * 2;
QRectF r(QPointF(x1, y1), QPointF(x2, y2));
QRect cr = cursorRect(); // hotspot included
draw(r);
// Support virtual desktop too. Backends with multi-screen support (e.g. all
// variants of KMS/DRM) will enable this by default. In this case all
// screens are siblings of each other. When not enabled, the sibling list
// only contains m_screen itself.
for (QPlatformScreen *screen : m_screen->virtualSiblings()) {
if (screen->geometry().contains(cr.topLeft() + m_cursor.hotSpot)
&& QOpenGLContext::currentContext()->screen() == screen->screen())
{
cr.translate(-screen->geometry().topLeft());
const QSize screenSize = screen->geometry().size();
const GLfloat x1 = 2 * (cr.left() / GLfloat(screenSize.width())) - 1;
const GLfloat x2 = 2 * (cr.right() / GLfloat(screenSize.width())) - 1;
const GLfloat y1 = 1 - (cr.top() / GLfloat(screenSize.height())) * 2;
const GLfloat y2 = 1 - (cr.bottom() / GLfloat(screenSize.height())) * 2;
QRectF r(QPointF(x1, y1), QPointF(x2, y2));
draw(r);
if (screen != m_activeScreen) {
m_activeScreen = screen;
// Do not want a leftover cursor on the screen the cursor just left.
update(cursorRect(), true);
}
break;
}
}
}
// In order to prevent breaking code doing custom OpenGL rendering while
@ -437,30 +471,33 @@ void QEglFSCursor::draw(const QRectF &r)
{
StateSaver stateSaver;
if (!m_program) {
GraphicsContextData &gfx(m_gfx[QOpenGLContext::currentContext()]);
if (!gfx.program) {
// one time initialization
initializeOpenGLFunctions();
createShaderPrograms();
if (!m_cursorAtlas.texture) {
createCursorTexture(&m_cursorAtlas.texture, m_cursorAtlas.image);
if (!gfx.atlasTexture) {
createCursorTexture(&gfx.atlasTexture, m_cursorAtlas.image);
if (m_cursor.shape != Qt::BitmapCursor)
m_cursor.texture = m_cursorAtlas.texture;
m_cursor.useCustomCursor = false;
}
}
if (m_cursor.shape == Qt::BitmapCursor && m_cursor.customCursorPending) {
if (m_cursor.shape == Qt::BitmapCursor && (m_cursor.customCursorPending || m_cursor.customCursorKey != gfx.customCursorKey)) {
// upload the custom cursor
createCursorTexture(&m_cursor.customCursorTexture, m_cursor.customCursorImage);
m_cursor.texture = m_cursor.customCursorTexture;
createCursorTexture(&gfx.customCursorTexture, m_cursor.customCursorImage);
m_cursor.useCustomCursor = true;
m_cursor.customCursorPending = false;
gfx.customCursorKey = m_cursor.customCursorKey;
}
Q_ASSERT(m_cursor.texture);
GLuint cursorTexture = !m_cursor.useCustomCursor ? gfx.atlasTexture : gfx.customCursorTexture;
Q_ASSERT(cursorTexture);
m_program->bind();
gfx.program->bind();
const GLfloat x1 = r.left();
const GLfloat x2 = r.right();
@ -485,20 +522,20 @@ void QEglFSCursor::draw(const QRectF &r)
};
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_cursor.texture);
glBindTexture(GL_TEXTURE_2D, cursorTexture);
if (stateSaver.vaoHelper->isValid())
stateSaver.vaoHelper->glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
m_program->enableAttributeArray(0);
m_program->enableAttributeArray(1);
m_program->setAttributeArray(0, cursorCoordinates, 2);
m_program->setAttributeArray(1, textureCoordinates, 2);
gfx.program->enableAttributeArray(0);
gfx.program->enableAttributeArray(1);
gfx.program->setAttributeArray(0, cursorCoordinates, 2);
gfx.program->setAttributeArray(1, textureCoordinates, 2);
m_program->setUniformValue(m_textureEntry, 0);
m_program->setUniformValue(m_matEntry, m_rotationMatrix);
gfx.program->setUniformValue(gfx.textureEntry, 0);
gfx.program->setUniformValue(gfx.matEntry, m_rotationMatrix);
glDisable(GL_CULL_FACE);
glFrontFace(GL_CCW);
@ -508,9 +545,9 @@ void QEglFSCursor::draw(const QRectF &r)
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
m_program->disableAttributeArray(0);
m_program->disableAttributeArray(1);
m_program->release();
gfx.program->disableAttributeArray(0);
gfx.program->disableAttributeArray(1);
gfx.program->release();
}
QT_END_NAMESPACE

View File

@ -105,30 +105,29 @@ private:
bool setCurrentCursor(QCursor *cursor);
#endif
void draw(const QRectF &rect);
void update(const QRegion &region);
void update(const QRect &rect, bool allScreens);
void createShaderPrograms();
void createCursorTexture(uint *texture, const QImage &image);
void initCursorAtlas();
// current cursor information
struct Cursor {
Cursor() : texture(0), shape(Qt::BlankCursor), customCursorTexture(0), customCursorPending(false) { }
uint texture; // a texture from 'image' or the atlas
Cursor() : shape(Qt::BlankCursor), customCursorPending(false), customCursorKey(0), useCustomCursor(false) { }
Qt::CursorShape shape;
QRectF textureRect; // normalized rect inside texture
QSize size; // size of the cursor
QPoint hotSpot;
QImage customCursorImage;
QPoint pos; // current cursor position
uint customCursorTexture;
bool customCursorPending;
qint64 customCursorKey;
bool useCustomCursor;
} m_cursor;
// cursor atlas information
struct CursorAtlas {
CursorAtlas() : cursorsPerRow(0), texture(0), cursorWidth(0), cursorHeight(0) { }
CursorAtlas() : cursorsPerRow(0), cursorWidth(0), cursorHeight(0) { }
int cursorsPerRow;
uint texture;
int width, height; // width and height of the atlas
int cursorWidth, cursorHeight; // width and height of cursors inside the atlas
QList<QPoint> hotSpots;
@ -137,12 +136,22 @@ private:
bool m_visible;
QEglFSScreen *m_screen;
QOpenGLShaderProgram *m_program;
int m_textureEntry;
int m_matEntry;
QPlatformScreen *m_activeScreen;
QEglFSCursorDeviceListener *m_deviceListener;
bool m_updateRequested;
QMatrix4x4 m_rotationMatrix;
struct GraphicsContextData {
GraphicsContextData() : program(nullptr), textureEntry(0), matEntry(0),
customCursorTexture(0), atlasTexture(0), customCursorKey(0) { }
QOpenGLShaderProgram *program;
int textureEntry;
int matEntry;
uint customCursorTexture;
uint atlasTexture;
qint64 customCursorKey;
};
QHash<QOpenGLContext *, GraphicsContextData> m_gfx;
};
QT_END_NAMESPACE

View File

@ -41,6 +41,7 @@
#include "qeglfsintegration.h"
#include "qeglfscursor_p.h"
#include "qeglfswindow_p.h"
#include "qeglfsscreen_p.h"
#include "qeglfshooks_p.h"
#include <QtPlatformSupport/private/qeglconvenience_p.h>
@ -311,7 +312,7 @@ bool QEglFSDeviceIntegration::hasCapability(QPlatformIntegration::Capability cap
QPlatformCursor *QEglFSDeviceIntegration::createCursor(QPlatformScreen *screen) const
{
return new QEglFSCursor(screen);
return new QEglFSCursor(static_cast<QEglFSScreen *>(screen));
}
void QEglFSDeviceIntegration::waitForVSync(QPlatformSurface *surface) const

View File

@ -40,11 +40,15 @@
#include "qeglfskmsegldevice.h"
#include "qeglfskmsegldevicescreen.h"
#include "qeglfskmsegldeviceintegration.h"
#include "private/qeglfscursor_p.h"
#include <QtCore/private/qcore_unix_p.h>
QT_BEGIN_NAMESPACE
QEglFSKmsEglDevice::QEglFSKmsEglDevice(QEglFSKmsIntegration *integration, const QString &path)
: QEglFSKmsDevice(integration, path)
: QEglFSKmsDevice(integration, path),
m_globalCursor(nullptr)
{
}
@ -52,6 +56,8 @@ bool QEglFSKmsEglDevice::open()
{
Q_ASSERT(fd() == -1);
qCDebug(qLcEglfsKmsDebug, "Opening DRM device %s", qPrintable(devicePath()));
int fd = drmOpen(devicePath().toLocal8Bit().constData(), Q_NULLPTR);
if (Q_UNLIKELY(fd < 0))
qFatal("Could not open DRM device");
@ -63,6 +69,8 @@ bool QEglFSKmsEglDevice::open()
void QEglFSKmsEglDevice::close()
{
qCDebug(qLcEglfsKmsDebug, "Closing DRM device");
if (qt_safe_close(fd()) == -1)
qErrnoWarning("Could not close DRM device");
@ -74,7 +82,26 @@ EGLNativeDisplayType QEglFSKmsEglDevice::nativeDisplay() const
return static_cast<QEglFSKmsEglDeviceIntegration *>(m_integration)->eglDevice();
}
QEglFSKmsScreen *QEglFSKmsEglDevice::createScreen(QEglFSKmsIntegration *integration, QEglFSKmsDevice *device, QEglFSKmsOutput output, QPoint position)
QEglFSKmsScreen *QEglFSKmsEglDevice::createScreen(QEglFSKmsIntegration *integration, QEglFSKmsDevice *device,
QEglFSKmsOutput output, QPoint position)
{
return new QEglFSKmsEglDeviceScreen(integration, device, output, position);
QEglFSKmsScreen *screen = new QEglFSKmsEglDeviceScreen(integration, device, output, position);
if (!m_globalCursor && !integration->separateScreens()) {
qCDebug(qLcEglfsKmsDebug, "Creating new global mouse cursor");
m_globalCursor = new QEglFSCursor(screen);
}
return screen;
}
void QEglFSKmsEglDevice::destroyGlobalCursor()
{
if (m_globalCursor) {
qCDebug(qLcEglfsKmsDebug, "Destroying global mouse cursor");
delete m_globalCursor;
m_globalCursor = nullptr;
}
}
QT_END_NAMESPACE

View File

@ -42,6 +42,10 @@
#include <qeglfskmsdevice.h>
QT_BEGIN_NAMESPACE
class QPlatformCursor;
class QEglFSKmsEglDevice: public QEglFSKmsDevice
{
public:
@ -56,6 +60,14 @@ public:
QEglFSKmsDevice *device,
QEglFSKmsOutput output,
QPoint position) Q_DECL_OVERRIDE;
QPlatformCursor *globalCursor() { return m_globalCursor; }
void destroyGlobalCursor();
private:
QPlatformCursor *m_globalCursor;
};
QT_END_NAMESPACE
#endif // QEGLFSKMSEGLDEVICE_H

View File

@ -41,6 +41,7 @@
#include "qeglfskmsegldeviceintegration.h"
#include <QtPlatformSupport/private/qeglconvenience_p.h>
#include "private/qeglfswindow_p.h"
#include "private/qeglfscursor_p.h"
#include "qeglfskmsegldevice.h"
#include "qeglfskmsscreen.h"
#include <QLoggingCategory>
@ -258,4 +259,9 @@ bool QEglFSKmsEglDeviceIntegration::query_egl_device()
return true;
}
QPlatformCursor *QEglFSKmsEglDeviceIntegration::createCursor(QPlatformScreen *screen) const
{
return separateScreens() ? new QEglFSCursor(screen) : nullptr;
}
QT_END_NAMESPACE

View File

@ -65,6 +65,7 @@ public:
protected:
QEglFSKmsDevice *createDevice(const QString &devicePath) Q_DECL_OVERRIDE;
QPlatformCursor *createCursor(QPlatformScreen *screen) const Q_DECL_OVERRIDE;
private:
bool setup_kms();

View File

@ -39,12 +39,32 @@
#include "qeglfskmsegldevicescreen.h"
#include "qeglfskmsegldevice.h"
#include <QGuiApplication>
QT_BEGIN_NAMESPACE
QEglFSKmsEglDeviceScreen::QEglFSKmsEglDeviceScreen(QEglFSKmsIntegration *integration, QEglFSKmsDevice *device, QEglFSKmsOutput output, QPoint position)
: QEglFSKmsScreen(integration, device, output, position)
{
}
QEglFSKmsEglDeviceScreen::~QEglFSKmsEglDeviceScreen()
{
const int remainingScreenCount = qGuiApp->screens().count();
qCDebug(qLcEglfsKmsDebug, "Screen dtor. Remaining screens: %d", remainingScreenCount);
if (!remainingScreenCount && !m_integration->separateScreens())
static_cast<QEglFSKmsEglDevice *>(device())->destroyGlobalCursor();
}
QPlatformCursor *QEglFSKmsEglDeviceScreen::cursor() const
{
// The base class creates a cursor via integration->createCursor()
// in its ctor. With separateScreens just use that. Otherwise
// there's a virtual desktop and the device has a global cursor
// and the base class has no dedicated cursor at all.
return m_integration->separateScreens() ? QEglFSScreen::cursor() : static_cast<QEglFSKmsEglDevice *>(device())->globalCursor();
}
void QEglFSKmsEglDeviceScreen::waitForFlip()
{
if (!output().mode_set) {
@ -76,3 +96,5 @@ void QEglFSKmsEglDeviceScreen::waitForFlip()
}
}
QT_END_NAMESPACE

View File

@ -42,6 +42,8 @@
#include <qeglfskmsscreen.h>
QT_BEGIN_NAMESPACE
class QEglFSKmsEglDeviceScreen : public QEglFSKmsScreen
{
public:
@ -49,8 +51,13 @@ public:
QEglFSKmsDevice *device,
QEglFSKmsOutput output,
QPoint position);
~QEglFSKmsEglDeviceScreen();
QPlatformCursor *cursor() const Q_DECL_OVERRIDE;
void waitForFlip() Q_DECL_OVERRIDE;
};
QT_END_NAMESPACE
#endif // QEGLFSKMSEGLDEVICESCREEN_H