Drive cursor animation with a timer

Using only wl_surface_frame callbacks to update the cursor does so much
more often than needed. In addition, at least GNOME and Weston fire the
callback for the cursor surface immediately, which ends up updating the
cursor at over 3000 Hz here.

Use wl_cursor_frame_and_duration to drive a single shot timer. This
function is also guaranteed to return 0 for single frame cursors, so we
can avoid starting the timer at all.

We wait for both the surface frame callback and the timer to fire before
updating the cursor for the next frame of animation. This reduces our
update rate to the frame rate of the cursor or the rate requested by the
compositor, whichever is lower.

Change-Id: I10277460ebe9b547ebaf7f73424b9ef17614107f
Reviewed-by: Johan Helsing <johan.helsing@qt.io>
This commit is contained in:
Jan Alexander Steffens (heftig) 2019-12-13 22:15:32 +01:00
parent fbc93070ad
commit 85d216ad0f
2 changed files with 36 additions and 3 deletions

View File

@ -143,6 +143,12 @@ QWaylandWindow *QWaylandInputDevice::Keyboard::focusWindow() const
QWaylandInputDevice::Pointer::Pointer(QWaylandInputDevice *seat)
: mParent(seat)
{
#if QT_CONFIG(cursor)
mCursor.frameTimer.setSingleShot(true);
mCursor.frameTimer.callOnTimeout([&]() {
cursorTimerCallback();
});
#endif
}
QWaylandInputDevice::Pointer::~Pointer()
@ -224,7 +230,7 @@ public:
if (animated) {
m_frameCallback.reset(new WlCallback(frame(), [this](uint32_t time){
Q_UNUSED(time);
m_pointer->updateCursor();
m_pointer->cursorFrameCallback();
}));
}
commit();
@ -328,7 +334,8 @@ void QWaylandInputDevice::Pointer::updateCursor()
uint time = seat()->mCursor.animationTimer.elapsed();
if (struct ::wl_cursor *waylandCursor = mCursor.theme->cursor(shape)) {
int frame = wl_cursor_frame(waylandCursor, time);
uint duration = 0;
int frame = wl_cursor_frame_and_duration(waylandCursor, time, &duration);
::wl_cursor_image *image = waylandCursor->images[frame];
struct wl_buffer *buffer = wl_cursor_image_get_buffer(image);
@ -339,7 +346,12 @@ void QWaylandInputDevice::Pointer::updateCursor()
int bufferScale = mCursor.themeBufferScale;
QPoint hotspot = QPoint(image->hotspot_x, image->hotspot_y) / bufferScale;
QSize size = QSize(image->width, image->height) / bufferScale;
bool animated = waylandCursor->image_count > 1 && image->delay > 0;
bool animated = duration > 0;
if (animated) {
mCursor.gotFrameCallback = false;
mCursor.gotTimerCallback = false;
mCursor.frameTimer.start(duration);
}
getOrCreateCursorSurface()->update(buffer, hotspot, size, bufferScale, animated);
return;
}
@ -354,6 +366,22 @@ CursorSurface *QWaylandInputDevice::Pointer::getOrCreateCursorSurface()
return mCursor.surface.get();
}
void QWaylandInputDevice::Pointer::cursorTimerCallback()
{
mCursor.gotTimerCallback = true;
if (mCursor.gotFrameCallback) {
updateCursor();
}
}
void QWaylandInputDevice::Pointer::cursorFrameCallback()
{
mCursor.gotFrameCallback = true;
if (mCursor.gotTimerCallback) {
updateCursor();
}
}
#endif // QT_CONFIG(cursor)
QWaylandInputDevice::Touch::Touch(QWaylandInputDevice *p)

View File

@ -286,6 +286,8 @@ public:
int idealCursorScale() const;
void updateCursorTheme();
void updateCursor();
void cursorTimerCallback();
void cursorFrameCallback();
CursorSurface *getOrCreateCursorSurface();
#endif
QWaylandInputDevice *seat() const { return mParent; }
@ -325,6 +327,9 @@ public:
QWaylandCursorTheme *theme = nullptr;
int themeBufferScale = 0;
QScopedPointer<CursorSurface> surface;
QTimer frameTimer;
bool gotFrameCallback = false;
bool gotTimerCallback = false;
} mCursor;
#endif
QPointF mSurfacePos;