1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00

perf: reduce repainting in animations

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
This commit is contained in:
sudden6 2021-11-28 12:39:37 +01:00
parent 6a10abf1b3
commit 3c58b992c6
No known key found for this signature in database
GPG Key ID: 279509B499E032B9
6 changed files with 38 additions and 31 deletions

View File

@ -50,11 +50,6 @@ void Broken::setWidth(qreal width)
Q_UNUSED(width) Q_UNUSED(width)
} }
void Broken::visibilityChanged(bool visible)
{
Q_UNUSED(visible)
}
qreal Broken::getAscent() const qreal Broken::getAscent() const
{ {
return 0.0; return 0.0;

View File

@ -33,7 +33,6 @@ public:
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, void paint(QPainter* painter, const QStyleOptionGraphicsItem* option,
QWidget* widget) override; QWidget* widget) override;
void setWidth(qreal width) override; void setWidth(qreal width) override;
void visibilityChanged(bool visible) override;
qreal getAscent() const override; qreal getAscent() const override;
private: private:

View File

@ -30,13 +30,12 @@ NotificationIcon::NotificationIcon(QSize Size)
{ {
pmap = PixmapCache::getInstance().get(Style::getImagePath("chatArea/typing.svg"), size); pmap = PixmapCache::getInstance().get(Style::getImagePath("chatArea/typing.svg"), size);
updateTimer = new QTimer(this); // Timer for the animation, if the Widget is not redrawn, no paint events will
updateTimer->setInterval(1000 / 30); // arrive and the timer will not be restarted, so this stops automatically
updateTimer->setSingleShot(false); 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 QRectF NotificationIcon::boundingRect() const
@ -54,6 +53,10 @@ void NotificationIcon::paint(QPainter* painter, const QStyleOptionGraphicsItem*
painter->fillRect(QRect(0, 0, size.width(), size.height()), grad); painter->fillRect(QRect(0, 0, size.width(), size.height()), grad);
painter->drawPixmap(0, 0, size.width(), size.height(), pmap); painter->drawPixmap(0, 0, size.width(), size.height(), pmap);
if (!updateTimer.isActive()) {
updateTimer.start();
}
Q_UNUSED(option) Q_UNUSED(option)
Q_UNUSED(widget) Q_UNUSED(widget)
} }
@ -70,10 +73,12 @@ qreal NotificationIcon::getAscent() const
void NotificationIcon::updateGradient() void NotificationIcon::updateGradient()
{ {
// Update for next frame
alpha += 0.01; alpha += 0.01;
if (alpha + dotWidth >= 1.0) if (alpha + dotWidth >= 1.0) {
alpha = 0.0; alpha = 0.0;
}
grad = QLinearGradient(QPointF(-0.5 * size.width(), 0), QPointF(3.0 / 2.0 * size.width(), 0)); grad = QLinearGradient(QPointF(-0.5 * size.width(), 0), QPointF(3.0 / 2.0 * size.width(), 0));
grad.setColorAt(0, Qt::lightGray); grad.setColorAt(0, Qt::lightGray);
@ -82,6 +87,7 @@ void NotificationIcon::updateGradient()
grad.setColorAt(qMin(1.0, alpha + dotWidth), Qt::lightGray); grad.setColorAt(qMin(1.0, alpha + dotWidth), Qt::lightGray);
grad.setColorAt(1, Qt::lightGray); grad.setColorAt(1, Qt::lightGray);
if (scene() && isVisible()) if (scene() && isVisible()) {
scene()->invalidate(sceneBoundingRect()); scene()->invalidate(sceneBoundingRect());
}
} }

View File

@ -23,8 +23,7 @@
#include <QLinearGradient> #include <QLinearGradient>
#include <QPixmap> #include <QPixmap>
#include <QTimer>
class QTimer;
class NotificationIcon : public ChatLineContent class NotificationIcon : public ChatLineContent
{ {
@ -42,10 +41,12 @@ private slots:
void updateGradient(); void updateGradient();
private: private:
static constexpr int framerate = 30; // 30Hz
QSize size; QSize size;
QPixmap pmap; QPixmap pmap;
QLinearGradient grad; QLinearGradient grad;
QTimer* updateTimer = nullptr; QTimer updateTimer;
qreal dotWidth = 0.2; qreal dotWidth = 0.2;
qreal alpha = 0.0; qreal alpha = 0.0;

View File

@ -26,14 +26,18 @@
#include <QTime> #include <QTime>
#include <QVariantAnimation> #include <QVariantAnimation>
#include <math.h>
Spinner::Spinner(const QString& img, QSize Size, qreal speed) Spinner::Spinner(const QString& img, QSize Size, qreal speed)
: size(Size) : size(Size)
, rotSpeed(speed) , rotSpeed(speed)
{ {
pmap = PixmapCache::getInstance().get(img, size); pmap = PixmapCache::getInstance().get(img, size);
timer.setInterval(1000 / 30); // 30Hz // Timer for the animation, if the Widget is not redrawn, no paint events will
timer.setSingleShot(false); // arrive and the timer will not be restarted, so this stops automatically
timer.setInterval(1000 / framerate);
timer.setSingleShot(true);
blendAnimation = new QVariantAnimation(this); blendAnimation = new QVariantAnimation(this);
blendAnimation->setStartValue(0.0); blendAnimation->setStartValue(0.0);
@ -56,14 +60,17 @@ void Spinner::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, Q
{ {
painter->setClipRect(boundingRect()); painter->setClipRect(boundingRect());
QTransform trans = QTransform() QTransform trans = QTransform().rotate(curRot)
.rotate(QTime::currentTime().msecsSinceStartOfDay() / 1000.0 * rotSpeed)
.translate(-size.width() / 2.0, -size.height() / 2.0); .translate(-size.width() / 2.0, -size.height() / 2.0);
painter->setOpacity(alpha); painter->setOpacity(alpha);
painter->setTransform(trans, true); painter->setTransform(trans, true);
painter->setRenderHint(QPainter::SmoothPixmapTransform); painter->setRenderHint(QPainter::SmoothPixmapTransform);
painter->drawPixmap(0, 0, pmap); painter->drawPixmap(0, 0, pmap);
if (!timer.isActive()) {
timer.start(); // update bounding rectangle for next frame
}
Q_UNUSED(option) Q_UNUSED(option)
Q_UNUSED(widget) Q_UNUSED(widget)
} }
@ -73,14 +80,6 @@ void Spinner::setWidth(qreal width)
Q_UNUSED(width) Q_UNUSED(width)
} }
void Spinner::visibilityChanged(bool visible)
{
if (visible)
timer.start();
else
timer.stop();
}
qreal Spinner::getAscent() const qreal Spinner::getAscent() const
{ {
return 0.0; return 0.0;
@ -88,6 +87,12 @@ qreal Spinner::getAscent() const
void Spinner::timeout() 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()); scene()->invalidate(sceneBoundingRect());
}
} }

View File

@ -37,16 +37,17 @@ public:
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, void paint(QPainter* painter, const QStyleOptionGraphicsItem* option,
QWidget* widget) override; QWidget* widget) override;
void setWidth(qreal width) override; void setWidth(qreal width) override;
void visibilityChanged(bool visible) override;
qreal getAscent() const override; qreal getAscent() const override;
private slots: private slots:
void timeout(); void timeout();
private: private:
static constexpr int framerate = 30; // 30Hz
QSize size; QSize size;
QPixmap pmap; QPixmap pmap;
qreal rotSpeed; qreal rotSpeed;
qreal curRot;
QTimer timer; QTimer timer;
qreal alpha = 0.0; qreal alpha = 0.0;
QVariantAnimation* blendAnimation; QVariantAnimation* blendAnimation;