-
Notifications
You must be signed in to change notification settings - Fork 5.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
AnimationTimerDelta is way too low for most systems (7ms) #5436
Comments
This is likely the cause of #4854 too. |
This issue still persists and honestly it needs more attention. |
@john-preston I'm happy to try help with a fix for this if you aren't able to find the time, but I don't know how you want it fixed. Can you please take a look at my original report? |
@rburchell Well, I want it to be fixed without affecting the animations frame rate :) It should not drop below current value OR below 60 FPS if current value is higher. Right now they are repainting as fast as they can, partially because of OS X where they were very lagging when I was using only timer to request repaints - it looked like timer events were delayed in favor of mouse move handling etc. I'm not sure. But the result was trying to update the animated widget parts right from the paintEvent handler. |
Another problem is to test the changes in all system versions. For example macOS started working much better with my animations starting from 10.13 (if I remember correctly), but was much more laggy before, on 10.10 / 10.11. And all changes should not break them. |
Ok, well, I don't have the capabilities to do that sort of testing. I can tell you how Qt is working internally, though, if you have questions (I know the right places to look, and am somewhat familiar with most of the code). Yes, timers can be delayed by mouse events (somewhat dependent on the platform: in theory, a platform could try to interleave events of different types, but it's not always a trivial thing to do). Have you considered compressing multiple mouse move events to avoid this becoming a problem? (Something like, on move, start a 0-interval timer to process it, and store the last move event recieved). Assuming mouse move processing is expensive on the application side, this should give quite a large benefit, but of course if you're doing something like paint/draw on move, then it's not really practical to do this without some extra work (because you probably want to draw between every position to avoid non-smooth lines). Generally, I'd say that you should aim to do your painting on paintEvent, unless there are some special circumstances (complex drawing/calculations, in which case, you will probably want caching on top). Animations seem like a bit of a strange case to need that sort of treatment to me, but obviously, I don't know this code well. |
เมื่อ พฤ. 31 ม.ค. 2562 เวลา 22:39 Robin Burchell <notifications@github.com>
เขียนว่า:
… Ok, well, I don't have the capabilities to do that sort of testing. I can
tell you how Qt is working internally, though, if you have questions (I
know the right places to look, and am somewhat familiar with most of the
code).
Yes, timers can be delayed by mouse events (somewhat dependent on the
platform: in theory, a platform could *try* to interleave events of
different types, but it's not always a trivial thing to do).
Have you considered compressing multiple mouse move events to avoid this
becoming a problem? (Something like, on move, start a 0-interval timer to
process it, and store the last move event recieved). Assuming mouse move
processing is expensive on the application side, this should give quite a
large benefit, but of course if you're doing something like paint/draw on
move, then it's not really practical to do this without some extra work
(because you probably want to draw between every position to avoid
non-smooth lines).
Generally, I'd say that you should *aim* to do your painting on
paintEvent, unless there are some special circumstances (complex
drawing/calculations, in which case, you will probably want caching on
top). Animations seem like a bit of a strange case to need that sort of
treatment to me, but obviously, I don't know this code well.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#5436 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ArEZn0Jh91n5aqTB6GHCr2D0ASSDjnn2ks5vIw5FgaJpZM4Y-alJ>
.
|
@rburchell 1.5.15 should be a little bit better. Before the animation timer was running always, even when no animations on the screen were playing (there always was an invisible one, that did nothing, but timer was working). Now it at least should work only when the animation is playing. |
Fantastic, that is a very good improvement! Do you happen to know the commits that changed it? I'd like to take a look. |
Before this commit the connecting status widget (that has a radial progress) was always existing, but just hidden. And it's infinite radial progress animation was always playing (doing nothing — because the widget was hidden and "update" was a no-op). After this commit only "connection status observer" is always existing and it creates a widget right before it is going to be shown — and destroys it right after it is hidden. |
Also I did implement a (possibly) better animations manager, that hooks into QEvent::UpdateRequest and performs an animation step there + after the painting is done it sets a timer for the remaining time to get 60 FPS (if the paint was less than 1ms, the timer will be set for 16ms, if the paint took 6ms, the timer will be set for 10ms, etc). When I find time to test it in at least some places to replace the existing animations and if it will work fine I hope it will improve this as well. |
Telegram 1.5.15 Typing animation now consumes about 30% cpu, while idle telegram takes 5-10% cpu. So, there is a slight improvement. Time will tell if it's consistent, but IMO the client is still cpu-hungry. |
@artyfarty I guess I should try the new animations on the typings first and see if it will make any difference. Also perhaps typing animations can be optimized in terms of painting. |
The problem still persists. A funny storry: we've recently made a group chat at work where people report daily at 13:00 about their plans. As they tend to type long messages, my notebook takes off for several minutes of gorgeous typing animations. |
I tried to make it more like 16ms, but strangely it was not looking good :( I had a couple of reports of not smooth animation and it returned back to normal with delta around 8ms, even with the new engine. You can check out beta version 1.6.6, but I'm afraid it won't be any better. |
This is likely because you're not rendering on paint events from Qt, but running a software timer. A software timer will always result in tearing when not double-buffered, and often result in missed frames, and any timer that is not the exact same as the refresh rate will always waste CPU. For 16ms, you were likely having tearing of some sort—no screen has a refresh rate of 62.5Hz, which is what 16ms gives. :) There is only one correct way to implement continuous rendering, such as animations, which goes like this:
Also, an important note is to ensure that one is not calling excessive paints by using QPainter's repaint() method: Instead, update() should be used so only a single paint will be done. |
Example code (using qt4 - that's what I had lying around). // build with qt4 and `g++ -I. -I /usr/include/QtGui -I /usr/include/QtOpenGL -lQtGui -lQtCore -lQtOpenGL -fPIC -O3 main.cpp`
#include <QtGui>
#include <QtOpenGL>
typedef QGLWidget GLWidget;
class Widget: public GLWidget
{
Q_OBJECT
bool mAnimate;
QTimer mTimer;
QPolygonF mPolygon;
qreal angle;
void paintEvent(QPaintEvent *) {
if (!mAnimate) {
// We're done drawing, stop!
return;
}
int len = qMin(height(), width());
QPainter p(this);
p.translate(width()/2.0, height()/2.0);
p.scale(len*.8, len*.8);
p.rotate(angle);
p.setPen(QPen(Qt::black, 0.01));
p.drawPolygon(mPolygon);
p.end();
// We issue an update to request repaint next time its a good idea.
angle += 1;
update();
}
public Q_SLOTS:
// For simplicity, our animation is simply toggled on/off on a timer.
void toggle() {
mAnimate = !mAnimate;
if (mAnimate) {
update();
// Let it run for four seconds.
mTimer.start(4000);
} else {
// Take a two second break.
mTimer.start(2000);
}
}
public:
Widget(QWidget *parent = 0) : GLWidget(parent) {
mPolygon.resize(2);
mPolygon[0] = QPointF(0, 0);
mPolygon[1] = QPointF(0, 0.5);
setAutoFillBackground(true);
mAnimate = false;
connect(&mTimer, SIGNAL(timeout()), SLOT(toggle()));
toggle();
}
};
// regenerate with `moc-qt4 main.cpp > file.moc`
#include "file.moc"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
} The general flow:
Notice that repaint() is never called, and timers are not used to paint (the timer present is just the demo enable/disable timer). This let's Qt decide the appropriate frame timing, which should follow vblank, which on my machine is a steady 60fps. |
this bug is still valid? |
I just did a test with the last version of Telegram (1.9.8) on my Surface Pro 3 running Windows 10 1909. |
The timer is still there, and still being activated at a very high rate (every 8ms / 125 times a second according to PowerTop / GammaRay). Looking at gdb, I think the timer is this one:
Looking at the code involved, it looks like the animations timer involved is now living at:
With a tick of: constexpr auto kAnimationTick = crl::time(1000) / 120; -- which is 8.3ms (rounded down, giving 8, so indeed, I am quite confident this is the timer at fault). And confirmation: setting a backtrace in Ui::Animations::Manager::update confirms that it is being invoked regularly, and immediately hitting the return here: tdesktop/Telegram/SourceFiles/ui/effects/animations.cpp Lines 124 to 126 in 1e5aa2a
Intriguingly, Ui::Animations::Manager::schedule is not being regularly invoked, so my guess is that an animation is going "rogue" and not being stopped when it should. Potentially noteworthy (?): I have Performance -> Enable animations disabled, with the wishful thinking from a long time ago that it might get around this bug (it did not). |
In case it's not obvious, I would really like to see Telegram not continually drain my battery anymore - this has been going on for a really long time now, so if there's any information I can provide that might be useful here, please ask. If you would like to replicate the testing I'm doing to verify that the timer is behaving after a fix, just attach a copy of GammaRay: https://www.kdab.com/development-resources/qt-tools/gammaray/ and look at the timers section, or stick a breakpoint in the animation manager. (And for a high level "what is sucking my battery" on Linux answer, look at powertop.) |
Still an issue with telegram-desktop 2.2.0 on Fedora 32. Same timer with an 8ms interval. |
@rburchell of course, it is not changed and there's no plan to change afaik |
I think you misunderstand. This timer is running endlessly, burning CPU cycles and battery power, for no reason. Nothing is happening. Nothing is animating. That's the point of the bug report. |
Unfortunately, this doesn't change the situation. Touching that code is dangerous and no one wil fix it and PRs won't be accepted (some forks maybe accept), as preston said :( |
@rburchell Is this in case of animations being disabled in Settings? |
@john-preston I have seen it with animations both on and off. For perhaps a year now, I have had that setting disabled. |
WTF? This is an instant messaging client, not a motor control application. There is simply no scenario in which touching animation code is "dangerous". |
If touching animation will lead to the situation when some callbacks will stop work and application will become very buggy, it could be called dangerous, IMHO |
A race condition in the animations manager can leave a dangling timerEvent() callback firing at a high frequency (>120 FPS) in the main loop even though there is no active animations. An update of the animations manager returns directly when there is no active animations. If there is at least one active animation, it stops the timer and schedules a new update before updating animations. Depending on the Integration implementation, the scheduling call can be postponed after the current update. The actual postponed call unconditionally schedules an update by starting the timer. The issue is that in the meantime the last remaining animation could have been removed and, when the timer callback would be fired, the update would return directly (since there is no active animations) without being able to stop. The explanation above ignores the updateQueued() cases of the postponed call for simplicity. These cases do not result in infinite updates like the timer case but still imply one useless (invoked) update. This fix adds a condition in the postponed call ensuring there is at least one active animation before processing. telegramdesktop/tdesktop#3640 telegramdesktop/tdesktop#4854 telegramdesktop/tdesktop#5436
@rburchell Just stumbled upon your bug report after proposing the PR above. Seems like we had the same experience. I also came to diagnose that the timer event of the animations manager kept firing without actually updating any animations. As described in the fix, this comes down to a race condition where the last animation stops right between the deferring of the scheduling code and its execution. I'm running with that fix for a few hours now and the timer behaves as expected. No more CPU cycles burning, no more battery drain! Two timers are still called once per sec (in minimized, unfocused states), but that's nothing compared to the previous 120+ calls per sec :) |
A race condition in the animations manager can leave a dangling timerEvent() callback firing at a high frequency (>120 FPS) in the main loop even though there is no active animations. An update of the animations manager returns directly when there is no active animations. If there is at least one active animation, it stops the timer and schedules a new update before updating animations. Depending on the Integration implementation, the scheduling call can be postponed after the current update. The actual postponed call unconditionally schedules an update by starting the timer. The issue is that in the meantime the last remaining animation could have been removed and, when the timer callback would be fired, the update would return directly (since there is no active animations) without being able to stop. The explanation above ignores the updateQueued() cases of the postponed call for simplicity. These cases do not result in infinite updates like the timer case but still imply one useless (invoked) update. This fix adds a condition in the postponed call ensuring there is at least one active animation before processing. telegramdesktop/tdesktop#3640 telegramdesktop/tdesktop#4854 telegramdesktop/tdesktop#5436
@loicmolinari Nice catch! That sounds like exactly what I've seen, and tried to find a few times, without success :) |
A race condition in the animations manager can leave a dangling timerEvent() callback firing at a high frequency (>120 FPS) in the main loop even though there is no active animations. An update of the animations manager returns directly when there is no active animations. If there is at least one active animation, it stops the timer and schedules a new update before updating animations. Depending on the Integration implementation, the scheduling call can be postponed after the current update. The actual postponed call unconditionally schedules an update by starting the timer. The issue is that in the meantime the last remaining animation could have been removed and, when the timer callback would be fired, the update would return directly (since there is no active animations) without being able to stop. The explanation above ignores the updateQueued() cases of the postponed call for simplicity. These cases do not result in infinite updates like the timer case but still imply one useless (invoked) update. This fix adds a condition in the postponed call ensuring there is at least one active animation before processing. telegramdesktop/tdesktop#3640 telegramdesktop/tdesktop#4854 telegramdesktop/tdesktop#5436
@Aokromes #3640 and #4854 are likely to be fixed by desktop-app/lib_ui@f783243, 336405b and the commits mentioned above. |
@loicmolinari github closes issues automatically if you're writing like |
Didn't want to take the responsibility :) |
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
Steps to reproduce
Expected behaviour
Wakeups should be limited to a more sensible amount to save CPU & battery resources
Actual behaviour
Notice that telegram-desktop is making ~140 wakeups per second due to a QTimer using a AnimationTimerDelta duration (7ms, 1000 / 7 = 142, but I initially found this by using GammaRay)
Configuration
Fedora 29, T-D 1.4.3, default theming.
Suggested changes
There's a range of options here. I'm not a contributor, so I'm definitely in no position to tell you what to do, but here's my advice as to what the options might be.
Sidenote
I expect this might be contributing to some other complaints about high resource usage like #4941, #4406, #4209, too.
The text was updated successfully, but these errors were encountered: