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)
}
void Broken::visibilityChanged(bool visible)
{
Q_UNUSED(visible)
}
qreal Broken::getAscent() const
{
return 0.0;

View File

@ -33,7 +33,6 @@ public:
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:

View File

@ -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
@ -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)
}
@ -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);
@ -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());
}
}

View File

@ -23,8 +23,7 @@
#include <QLinearGradient>
#include <QPixmap>
class QTimer;
#include <QTimer>
class NotificationIcon : public ChatLineContent
{
@ -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;

View File

@ -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);
@ -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)
}
@ -73,14 +80,6 @@ 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;
@ -88,6 +87,12 @@ qreal Spinner::getAscent() const
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());
}
}

View File

@ -37,16 +37,17 @@ public:
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;