Skip to content

Commit

Permalink
perf: reduce repainting in animations
Browse files Browse the repository at this point in the history
By profiling qTox using perf I discovered, that
NotificationIcon::updateGradient takes significant amount of CPU time
even though qTox is idle and no one is typing.

This commit fixes:

1) correctly determine visibility of NotificationIcon
2) only invalidate boundingRect in fixed intervals
3) apply the same fixes to Spinner since it has the same problem
  • Loading branch information
sudden6 committed Nov 28, 2021
1 parent 6a10abf commit 3c58b99
Show file tree
Hide file tree
Showing 6 changed files with 38 additions and 31 deletions.
5 changes: 0 additions & 5 deletions src/chatlog/content/broken.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,6 @@ void Broken::setWidth(qreal width)
Q_UNUSED(width)
}

void Broken::visibilityChanged(bool visible)
{
Q_UNUSED(visible)
}

qreal Broken::getAscent() const
{
return 0.0;
Expand Down
1 change: 0 additions & 1 deletion src/chatlog/content/broken.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ class Broken : public ChatLineContent
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option,
QWidget* widget) override;
void setWidth(qreal width) override;
void visibilityChanged(bool visible) override;
qreal getAscent() const override;

private:
Expand Down
22 changes: 14 additions & 8 deletions src/chatlog/content/notificationicon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,12 @@ NotificationIcon::NotificationIcon(QSize Size)
{
pmap = PixmapCache::getInstance().get(Style::getImagePath("chatArea/typing.svg"), size);

updateTimer = new QTimer(this);
updateTimer->setInterval(1000 / 30);
updateTimer->setSingleShot(false);
// Timer for the animation, if the Widget is not redrawn, no paint events will
// arrive and the timer will not be restarted, so this stops automatically
updateTimer.setInterval(1000 / framerate);
updateTimer.setSingleShot(true);

updateTimer->start();

connect(updateTimer, &QTimer::timeout, this, &NotificationIcon::updateGradient);
connect(&updateTimer, &QTimer::timeout, this, &NotificationIcon::updateGradient);
}

QRectF NotificationIcon::boundingRect() const
Expand All @@ -54,6 +53,10 @@ void NotificationIcon::paint(QPainter* painter, const QStyleOptionGraphicsItem*
painter->fillRect(QRect(0, 0, size.width(), size.height()), grad);
painter->drawPixmap(0, 0, size.width(), size.height(), pmap);

if (!updateTimer.isActive()) {
updateTimer.start();
}

Q_UNUSED(option)
Q_UNUSED(widget)
}
Expand All @@ -70,10 +73,12 @@ qreal NotificationIcon::getAscent() const

void NotificationIcon::updateGradient()
{
// Update for next frame
alpha += 0.01;

if (alpha + dotWidth >= 1.0)
if (alpha + dotWidth >= 1.0) {
alpha = 0.0;
}

grad = QLinearGradient(QPointF(-0.5 * size.width(), 0), QPointF(3.0 / 2.0 * size.width(), 0));
grad.setColorAt(0, Qt::lightGray);
Expand All @@ -82,6 +87,7 @@ void NotificationIcon::updateGradient()
grad.setColorAt(qMin(1.0, alpha + dotWidth), Qt::lightGray);
grad.setColorAt(1, Qt::lightGray);

if (scene() && isVisible())
if (scene() && isVisible()) {
scene()->invalidate(sceneBoundingRect());
}
}
7 changes: 4 additions & 3 deletions src/chatlog/content/notificationicon.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@

#include <QLinearGradient>
#include <QPixmap>

class QTimer;
#include <QTimer>

class NotificationIcon : public ChatLineContent
{
Expand All @@ -42,10 +41,12 @@ private slots:
void updateGradient();

private:
static constexpr int framerate = 30; // 30Hz

QSize size;
QPixmap pmap;
QLinearGradient grad;
QTimer* updateTimer = nullptr;
QTimer updateTimer;

qreal dotWidth = 0.2;
qreal alpha = 0.0;
Expand Down
31 changes: 18 additions & 13 deletions src/chatlog/content/spinner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,18 @@
#include <QTime>
#include <QVariantAnimation>

#include <math.h>

Spinner::Spinner(const QString& img, QSize Size, qreal speed)
: size(Size)
, rotSpeed(speed)
{
pmap = PixmapCache::getInstance().get(img, size);

timer.setInterval(1000 / 30); // 30Hz
timer.setSingleShot(false);
// Timer for the animation, if the Widget is not redrawn, no paint events will
// arrive and the timer will not be restarted, so this stops automatically
timer.setInterval(1000 / framerate);
timer.setSingleShot(true);

blendAnimation = new QVariantAnimation(this);
blendAnimation->setStartValue(0.0);
Expand All @@ -56,14 +60,17 @@ void Spinner::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, Q
{
painter->setClipRect(boundingRect());

QTransform trans = QTransform()
.rotate(QTime::currentTime().msecsSinceStartOfDay() / 1000.0 * rotSpeed)
QTransform trans = QTransform().rotate(curRot)
.translate(-size.width() / 2.0, -size.height() / 2.0);
painter->setOpacity(alpha);
painter->setTransform(trans, true);
painter->setRenderHint(QPainter::SmoothPixmapTransform);
painter->drawPixmap(0, 0, pmap);

if (!timer.isActive()) {
timer.start(); // update bounding rectangle for next frame
}

Q_UNUSED(option)
Q_UNUSED(widget)
}
Expand All @@ -73,21 +80,19 @@ void Spinner::setWidth(qreal width)
Q_UNUSED(width)
}

void Spinner::visibilityChanged(bool visible)
{
if (visible)
timer.start();
else
timer.stop();
}

qreal Spinner::getAscent() const
{
return 0.0;
}

void Spinner::timeout()
{
if (scene())
// Use global time, so the animations are synced
float angle = QTime::currentTime().msecsSinceStartOfDay() / 1000.0f * rotSpeed;
// limit to the range [0.0 - 360.0]
curRot = remainderf(angle, 360.0f);

if (scene()) {
scene()->invalidate(sceneBoundingRect());
}
}
3 changes: 2 additions & 1 deletion src/chatlog/content/spinner.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,17 @@ class Spinner : public ChatLineContent
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option,
QWidget* widget) override;
void setWidth(qreal width) override;
void visibilityChanged(bool visible) override;
qreal getAscent() const override;

private slots:
void timeout();

private:
static constexpr int framerate = 30; // 30Hz
QSize size;
QPixmap pmap;
qreal rotSpeed;
qreal curRot;
QTimer timer;
qreal alpha = 0.0;
QVariantAnimation* blendAnimation;
Expand Down

0 comments on commit 3c58b99

Please sign in to comment.