platforms/drm: Use a software cursor if the cursor image is too big

When dragging files on the desktop, the cursor image might be just too
big for the cursor plane, in which case we need to abandon hardware
cursors for a brief moment and use a software cursor. Once the files
have been dropped and the cursor image is small enough, we can go back
to using hw cursors.

BUG: 424589
This commit is contained in:
Vlad Zahorodnii 2020-10-28 09:03:09 +02:00
parent d5ee009ba5
commit 5442762371
9 changed files with 110 additions and 40 deletions

View file

@ -37,7 +37,7 @@ Platform::Platform(QObject *parent)
: QObject(parent) : QObject(parent)
, m_eglDisplay(EGL_NO_DISPLAY) , m_eglDisplay(EGL_NO_DISPLAY)
{ {
setSoftwareCursor(false); setSoftwareCursorForced(false);
m_colorCorrect = new ColorCorrect::Manager(this); m_colorCorrect = new ColorCorrect::Manager(this);
connect(Cursors::self(), &Cursors::currentCursorRendered, this, &Platform::cursorRendered); connect(Cursors::self(), &Cursors::currentCursorRendered, this, &Platform::cursorRendered);
} }
@ -188,13 +188,11 @@ bool Platform::usesSoftwareCursor() const
void Platform::setSoftwareCursor(bool set) void Platform::setSoftwareCursor(bool set)
{ {
if (qEnvironmentVariableIsSet("KWIN_FORCE_SW_CURSOR")) {
set = true;
}
if (m_softwareCursor == set) { if (m_softwareCursor == set) {
return; return;
} }
m_softwareCursor = set; m_softwareCursor = set;
doSetSoftwareCursor();
if (m_softwareCursor) { if (m_softwareCursor) {
connect(Cursors::self(), &Cursors::positionChanged, this, &Platform::triggerCursorRepaint); connect(Cursors::self(), &Cursors::positionChanged, this, &Platform::triggerCursorRepaint);
connect(Cursors::self(), &Cursors::currentCursorChanged, this, &Platform::triggerCursorRepaint); connect(Cursors::self(), &Cursors::currentCursorChanged, this, &Platform::triggerCursorRepaint);
@ -205,6 +203,33 @@ void Platform::setSoftwareCursor(bool set)
triggerCursorRepaint(); triggerCursorRepaint();
} }
void Platform::doSetSoftwareCursor()
{
}
bool Platform::isSoftwareCursorForced() const
{
return m_softwareCursorForced;
}
void Platform::setSoftwareCursorForced(bool forced)
{
if (qEnvironmentVariableIsSet("KWIN_FORCE_SW_CURSOR")) {
forced = true;
}
if (m_softwareCursorForced == forced) {
return;
}
m_softwareCursorForced = forced;
if (m_softwareCursorForced) {
setSoftwareCursor(true);
} else {
// Do not unset the software cursor yet, the platform will choose the right
// moment when it can be done. There is still a chance that we must continue
// using the software cursor.
}
}
void Platform::triggerCursorRepaint() void Platform::triggerCursorRepaint()
{ {
if (!Compositor::self()) { if (!Compositor::self()) {

View file

@ -284,6 +284,21 @@ public:
*/ */
bool usesSoftwareCursor() const; bool usesSoftwareCursor() const;
/**
* Returns @c true if the software cursor is being forced; otherwise returns @c false.
*
* Note that the value returned by this function not always matches usesSoftwareCursor().
* If this function returns @c true, then it is guaranteed that the compositor will
* use the software cursor. However, this doesn't apply vice versa.
*
* If the compositor uses a software cursor, this function may return @c false. This
* is typically the case if the current cursor image can't be displayed using hardware
* cursors, for example due to buffer size limitations, etc.
*
* @see usesSoftwareCursor()
*/
bool isSoftwareCursorForced() const;
/** /**
* Returns a PlatformCursorImage. By default this is created by softwareCursor and * Returns a PlatformCursorImage. By default this is created by softwareCursor and
* softwareCursorHotspot. An implementing subclass can use this to provide a better * softwareCursorHotspot. An implementing subclass can use this to provide a better
@ -487,6 +502,7 @@ Q_SIGNALS:
protected: protected:
explicit Platform(QObject *parent = nullptr); explicit Platform(QObject *parent = nullptr);
void setSoftwareCursor(bool set); void setSoftwareCursor(bool set);
void setSoftwareCursorForced(bool forced);
void repaint(const QRect &rect); void repaint(const QRect &rect);
void setReady(bool ready); void setReady(bool ready);
QSize initialWindowSize() const { QSize initialWindowSize() const {
@ -532,10 +548,12 @@ protected:
* @see showCursor * @see showCursor
*/ */
virtual void doShowCursor(); virtual void doShowCursor();
virtual void doSetSoftwareCursor();
private: private:
void triggerCursorRepaint(); void triggerCursorRepaint();
bool m_softwareCursor = false; bool m_softwareCursor = false;
bool m_softwareCursorForced = false;
struct { struct {
QRect lastRenderedGeometry; QRect lastRenderedGeometry;
} m_cursor; } m_cursor;

View file

@ -506,7 +506,7 @@ void DrmBackend::initCursor()
break; break;
} }
} }
setSoftwareCursor(needsSoftwareCursor); setSoftwareCursorForced(needsSoftwareCursor);
#endif #endif
if (waylandServer()->seat()->hasPointer()) { if (waylandServer()->seat()->hasPointer()) {
@ -529,41 +529,42 @@ void DrmBackend::initCursor()
connect(Cursors::self(), &Cursors::positionChanged, this, &DrmBackend::moveCursor); connect(Cursors::self(), &Cursors::positionChanged, this, &DrmBackend::moveCursor);
} }
void DrmBackend::setCursor()
{
for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) {
if (!(*it)->showCursor()) {
setSoftwareCursor(true);
}
}
}
void DrmBackend::updateCursor() void DrmBackend::updateCursor()
{ {
if (usesSoftwareCursor()) { if (isSoftwareCursorForced() || isCursorHidden()) {
return;
}
if (isCursorHidden()) {
return; return;
} }
auto cursor = Cursors::self()->currentCursor(); auto cursor = Cursors::self()->currentCursor();
const QImage &cursorImage = cursor->image(); if (cursor->image().isNull()) {
if (cursorImage.isNull()) {
doHideCursor(); doHideCursor();
return; return;
} }
for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) {
(*it)->updateCursor(); bool success = true;
for (DrmOutput *output : qAsConst(m_outputs)) {
success = output->updateCursor();
if (!success) {
qCDebug(KWIN_DRM) << "Failed to update cursor on output" << output->name();
break;
}
success = output->showCursor();
if (!success) {
qCDebug(KWIN_DRM) << "Failed to show cursor on output" << output->name();
break;
}
output->moveCursor();
} }
setCursor(); setSoftwareCursor(!success);
moveCursor();
} }
void DrmBackend::doShowCursor() void DrmBackend::doShowCursor()
{ {
if (usesSoftwareCursor()) {
return;
}
updateCursor(); updateCursor();
} }
@ -577,6 +578,16 @@ void DrmBackend::doHideCursor()
} }
} }
void DrmBackend::doSetSoftwareCursor()
{
if (isCursorHidden() || !usesSoftwareCursor()) {
return;
}
for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) {
(*it)->hideCursor();
}
}
void DrmBackend::moveCursor() void DrmBackend::moveCursor()
{ {
if (isCursorHidden() || usesSoftwareCursor()) { if (isCursorHidden() || usesSoftwareCursor()) {

View file

@ -88,6 +88,8 @@ public Q_SLOTS:
protected: protected:
void doHideCursor() override; void doHideCursor() override;
void doShowCursor() override; void doShowCursor() override;
void doSetSoftwareCursor() override;
private: private:
friend class DrmGpu; friend class DrmGpu;
void addOutput(DrmOutput* output); void addOutput(DrmOutput* output);
@ -98,7 +100,6 @@ private:
void reactivate(); void reactivate();
void deactivate(); void deactivate();
bool updateOutputs(); bool updateOutputs();
void setCursor();
void updateCursor(); void updateCursor();
void moveCursor(); void moveCursor();
void initCursor(); void initCursor();

View file

@ -252,7 +252,7 @@ bool DrmGpu::updateOutputs()
continue; continue;
} }
if (!output->initCursor(m_cursorSize)) { if (!output->initCursor(m_cursorSize)) {
m_backend->setSoftwareCursor(true); m_backend->setSoftwareCursorForced(true);
} }
qCDebug(KWIN_DRM) << "Found new output with uuid" << output->uuid(); qCDebug(KWIN_DRM) << "Found new output with uuid" << output->uuid();

View file

@ -112,11 +112,6 @@ bool DrmOutput::showCursor()
return false; return false;
} }
if (Q_UNLIKELY(m_backend->usesSoftwareCursor())) {
qCCritical(KWIN_DRM) << "DrmOutput::showCursor should never be called when software cursor is enabled";
return true;
}
const bool ret = showCursor(m_cursor[m_cursorIndex].data()); const bool ret = showCursor(m_cursor[m_cursorIndex].data());
if (!ret) { if (!ret) {
qCDebug(KWIN_DRM) << "DrmOutput::showCursor(DrmDumbBuffer) failed"; qCDebug(KWIN_DRM) << "DrmOutput::showCursor(DrmDumbBuffer) failed";
@ -131,25 +126,45 @@ bool DrmOutput::showCursor()
return ret; return ret;
} }
void DrmOutput::updateCursor() static bool isCursorSpriteCompatible(const QImage *buffer, const QImage *sprite)
{
// Note that we need compare the rects in the device independent pixels because the
// buffer and the cursor sprite image may have different scale factors.
const QRect bufferRect(QPoint(0, 0), buffer->size() / buffer->devicePixelRatio());
const QRect spriteRect(QPoint(0, 0), sprite->size() / sprite->devicePixelRatio());
return bufferRect.contains(spriteRect);
}
bool DrmOutput::updateCursor()
{ {
if (m_deleted) { if (m_deleted) {
return; return false;
} }
const Cursor *cursor = Cursors::self()->currentCursor(); const Cursor *cursor = Cursors::self()->currentCursor();
const QImage cursorImage = cursor->image(); const QImage cursorImage = cursor->image();
if (cursorImage.isNull()) { if (cursorImage.isNull()) {
return; return false;
} }
m_hasNewCursor = true;
QImage *c = m_cursor[m_cursorIndex]->image(); QImage *c = m_cursor[m_cursorIndex]->image();
c->setDevicePixelRatio(scale());
if (!isCursorSpriteCompatible(c, &cursorImage)) {
// If the cursor image is too big, fall back to rendering the software cursor.
return false;
}
m_hasNewCursor = true;
c->fill(Qt::transparent); c->fill(Qt::transparent);
QPainter p; QPainter p;
p.begin(c); p.begin(c);
p.setWorldTransform(logicalToNativeMatrix(cursor->rect(), scale(), transform()).toTransform()); p.setWorldTransform(logicalToNativeMatrix(cursor->rect(), 1, transform()).toTransform());
p.drawImage(QPoint(0, 0), cursorImage); p.drawImage(QPoint(0, 0), cursorImage);
p.end(); p.end();
return true;
} }
void DrmOutput::moveCursor() void DrmOutput::moveCursor()

View file

@ -45,7 +45,7 @@ public:
bool showCursor(DrmDumbBuffer *buffer); bool showCursor(DrmDumbBuffer *buffer);
bool showCursor(); bool showCursor();
bool hideCursor(); bool hideCursor();
void updateCursor(); bool updateCursor();
void moveCursor(); void moveCursor();
bool init(drmModeConnector *connector); bool init(drmModeConnector *connector);
bool present(DrmBuffer *buffer); bool present(DrmBuffer *buffer);

View file

@ -67,7 +67,7 @@ QPainterBackend *FramebufferBackend::createQPainterBackend()
void FramebufferBackend::init() void FramebufferBackend::init()
{ {
setSoftwareCursor(true); setSoftwareCursorForced(true);
LogindIntegration *logind = LogindIntegration::self(); LogindIntegration *logind = LogindIntegration::self();
auto takeControl = [logind, this]() { auto takeControl = [logind, this]() {
if (logind->hasSessionControl()) { if (logind->hasSessionControl()) {

View file

@ -59,7 +59,7 @@ void VirtualBackend::init()
m_enabledOutputs << dummyOutput ; m_enabledOutputs << dummyOutput ;
} }
setSoftwareCursor(true); setSoftwareCursorForced(true);
setReady(true); setReady(true);
waylandServer()->seat()->setHasPointer(true); waylandServer()->seat()->setHasPointer(true);
waylandServer()->seat()->setHasKeyboard(true); waylandServer()->seat()->setHasKeyboard(true);