Bug 485927

Summary: Animations in KDE Plasma 6 seem to be capped at 60hz
Product: [Plasma] kwin Reporter: dominickator122102
Component: performanceAssignee: KWin default assignee <kwin-bugs-null>
Status: ASSIGNED ---    
Severity: normal CC: 179, avkhanov, bearoso, breakingspell, copilexp, dts.ramon, dustintownsend25, goingtosleepzzz, goodaqua, hfc2x, ilikefoss, jake, jesusnavarrojr188, jjom771, kde, kdebugs, kurobac, lars, linx.system.adm, madness742, merkurio_92, n.parkanyi, nate, nethshanperis, shibisjm, sort465, sultan, tduck, team, xaver.hugl, yule2000, zupnikszydlik
Priority: NOR    
Version First Reported In: 6.0.4   
Target Milestone: ---   
Platform: Arch Linux   
OS: Linux   
Latest Commit: Version Fixed In:
Sentry Crash Report:
Attachments: Showing the low refresh rate animations and scrolling.
hack to increase qt6 animation tick
Patch using private API to update scene graph timers when window refreshed
Patch using private API to update scene graph timers when window refreshed 2
Patch using public API and setting interval to 4ms
Patch using public API and setting interval to lowest refresh rate on hotplug

Description dominickator122102 2024-04-21 22:13:37 UTC
Created attachment 168770 [details]
Showing the low refresh rate animations and scrolling.

Environment:
 
    Operating System: Arch Linux
    KDE Plasma Version: 6.0.4
    Application: KDE Discover
    KDE Frameworks: 6.1.0
    Kernel Version: 6.8.7-arch1-1 (64-bit)
    Qt Version: 6.7.0
    Windowing System: Wayland
    CPU: Ryzen 7 5700
    GPU: RX 6700XT
    RAM: 32GB DDR4

SUMMARY
When using the overview animations, the window snapping animations, or scrolling in system settings, the frame rate seems to be capped at 60hz, even though I have the refresh rate set to that of my monitor, 165hz. Additionally, when scrolling in system settings the scrolling on the sidebar suddenly becomes a higher refresh rate when the "Colors and Themes" option is clicked. 

STEPS TO REPRODUCE
1. Using a high refresh rate monitor, set the refresh rate of plasmashell to the highest refresh rate.
2. To find the scrolling issue, open system settings, and start scrolling on the sidebar, then click Colors and Themes, and back to the main menu, then start scrolling again, it becomes smooth.
3. Either open the overview or snap a window to the side to see how stuttery they look compared to the same animations in either GNOME or Windows 10/11.

OBSERVED RESULT
Animations that are for some reason capped at 60hz.

EXPECTED RESULT
The animations should happen at the set refresh rate.

ADDITIONAL INFORMATION:
The animations all appear to be smoother on x11 then wayland, which is odd, because everything else is smoother on wayland for me.
Comment 1 DT 2024-05-01 00:28:56 UTC
I've encountered the same behavior on my 240hz display. The overview and window snapping animations are the most noticeable, and appear (mostly) correct on the X11 session. Peculiarly, the overview animation on the X11 session renders at a lower framerate *until* a window is snapped to an edge, following which it will begin rendering at the correct framerate. I found a user on Reddit: https://old.reddit.com/r/kde/comments/1c1lkiw/strange_lag_symptoms_with_the_overview_effect/ who reported similar issues (along with the quirk in the X11 session I described).

> Additionally, when scrolling in system settings the scrolling on the sidebar suddenly becomes a higher refresh rate when the "Colors and Themes" option is clicked. 
I was able to reproduce this as well.
Comment 2 179 2024-06-22 12:46:21 UTC
Can still reproduce on 6.1. So far, I've found the overview, desktop grid, cube and new edit mode to be affected.
With a 165hz monitor and a 60hz one right beside it and spamming the overview, it is very obviously running at 60hz. Even if I boot with only the 165hz one physically connected, the problem persists. 
I have verified with the 'Show FPS' effect that the desktop barely drops frames when activating any of these effects.

Couple notes for the overview:
    - Switching to another workspace (clicking, arrow keys, shortcut) in the overview also runs at 60hz. 
In comparison, the 'slide' effect when switching virtual desktops outside the overview is significantly smoother.
    - When dragging a window here, the window moves at the correct framerate.

The new edit mode is also interesting, because the animations for entering and exiting it are visibly choppy (seemingly below 60fps even), while the animation that plays when opening a panel configuration (desktop being pushed away to make space) is hilariously smooth in comparison. The animation for opening the 'Add widgets' sidebar for example also runs at the correct framerate.

Regarding the scrolling, I've tested a couple applications that have smooth scrolling.

Not scrolling at the correct framerate:
    - System Monitor/Info Center
    - Dolphin (the F3 sidebar sliding animations is also running at 60hz)
    - Okular

Scrolling at the correct framerate (extra notes for these below):
    - System Monitor (process tables)
    - Discover
    - Emoji selector
    - Filelight (sidebar)
    - Clipboard applet (needs a plasmashell restart as well after applying below fixes)

If I boot with a secondary 60hz monitor enabled, these applications also have the 60hz scrolling. If I turn that monitor off and then on again, they seem to be fixed until next reboot (this fix seems to persist even through logging out). If I boot only with the 165hz one connected, they are fine.

Operating System: Fedora Linux 40
KDE Plasma Version: 6.1.0
KDE Frameworks Version: 6.3.0
Qt Version: 6.7.1
Kernel Version: 6.9.4-200.fc40.x86_64 (64-bit)
Graphics Platform: Wayland
Graphics Processor: AMD Radeon RX 7600
Comment 3 Orlando Rodriguez 2024-09-09 14:27:20 UTC
(In reply to DT from comment #1)
> I've encountered the same behavior on my 240hz display. The overview and
> window snapping animations are the most noticeable, and appear (mostly)
> correct on the X11 session. Peculiarly, the overview animation on the X11
> session renders at a lower framerate *until* a window is snapped to an edge,
> following which it will begin rendering at the correct framerate. I found a
> user on Reddit:
> https://old.reddit.com/r/kde/comments/1c1lkiw/
> strange_lag_symptoms_with_the_overview_effect/ who reported similar issues
> (along with the quirk in the X11 session I described).
> 
> > Additionally, when scrolling in system settings the scrolling on the sidebar suddenly becomes a higher refresh rate when the "Colors and Themes" option is clicked. 
> I was able to reproduce this as well.

Same here. I've had this problem with the overview effects and desktop grid since 5.XX. For X11 I created a post-login script to Alt-tab automatically as fast as possible (this brings the same effect than a windows snapped) and the animations were amazing again, however, that workaround has never worked on Wayland. It would be great if some day this issue could be solved, it looks difficult and probably the priority is low due to the effects are pretty functional as they are, but for me I have been searching for a solution, even I was thinking on thinkering the effects by myself, but in KDE6 they are not in the effects folders anymore.
Comment 4 goingtosleep 2024-09-16 10:10:01 UTC
I encountered the same issue on wayland, with 165hz monitor, AMD RX 6600 XT, Plasma 6.1.5 on kernel 6.10.10.
On X11 the animation is smooth, but every startup, I have to unsnap and snap a window to make it smooth, otherwise it's stutter.
Basically like others mentioned.
Comment 5 nethshanperis 2024-10-24 04:59:30 UTC
Can confirm that the issue persists in Plasma 6.2. As a laptop user this feels more noticeable especially when the using touchpad gestures to scroll through the desktops in overview mode. 

Operating System: Aurora 40
KDE Plasma Version: 6.2.1
KDE Frameworks Version: 6.7.0
Qt Version: 6.7.2
Kernel Version: 6.11.3-200.fc40.x86_64 (64-bit)
Graphics Platform: Wayland
Processors: 22 × Intel® Core™ Ultra 7 155H
Memory: 15.0 GiB of RAM
Graphics Processor: Mesa Intel® Arc
Manufacturer: HP
Product Name: HP Spectre x360 2-in-1 Laptop 14-eu0xxx
Comment 6 justas 2024-11-03 15:58:53 UTC
Still hasn't been fixed as of KDE Plasma 6.2.2.
Comment 7 copilexp 2024-11-24 09:44:07 UTC
I get strange behavior on X11, It is "low refresh rate" when I log in, but it somehow fixes itself after like two minutes, and then it is silky smooth. However, the same cannot be said for Wayland.
Comment 8 copilexp 2024-11-24 09:50:31 UTC
(In reply to copilexp from comment #7)
> I get strange behavior on X11, It is "low refresh rate" when I log in, but
> it somehow fixes itself after like two minutes, and then it is silky smooth.
> However, the same cannot be said for Wayland.

Nope, seems to be triggered by something.
Comment 9 n.parkanyi 2024-12-01 07:17:06 UTC
It looks like these animations are all being driven by QUnifiedTimer, which uses a shared global tick, and the tick interval just defaults to 16ms. And if some component ends up calling QUnifiedTimer::setTimingInterval(), it would affect other animations since it's shared. It might explain why it fixes itself sometimes.

If I apply the attached patch to qt6-base to change to default tick interval to 6ms, all the animations look smooth on my 144hz display (overview effect, system settings scrolling, the smooth scrolling in okular).
Comment 10 n.parkanyi 2024-12-01 07:18:46 UTC
Created attachment 176255 [details]
hack to increase qt6 animation tick
Comment 11 Blazer Silving 2024-12-01 21:22:04 UTC
At first glance, this works. Boy howdy! 

May also resolve BUG 455585, BUG 493208
Comment 12 Blazer Silving 2024-12-01 21:40:00 UTC
*** Bug 493208 has been marked as a duplicate of this bug. ***
Comment 13 fililip 2024-12-03 20:32:27 UTC
This has been bothering me for a while, thanks for the find! With the patch it's much, much better now.
Comment 14 dominickator122102 2024-12-04 02:37:10 UTC
WOW! This completely solves everything!!! I'm still kind of new to the whole Linux bug submitting experience, and I assume this is upstream with qt. Does anyone know who I have to contact in order to upstream this?
Comment 15 Paul 2024-12-04 21:58:23 UTC
Tried the patch on my system, X11 and Wayland both butter smooth now. (I used 4 ms to have a high enough refresh rate for a 240 Hz monitor).

Arch Linux
KDE Plasma: 6.2.4
KDE Frameworks: 6.8.0
Qt: 6.8.1
Kernel: 6.12.1-arch1-1
AMD Radeon RX 6950 XT
Comment 16 Blazer Silving 2024-12-04 22:00:26 UTC
> I assume this is upstream with qt. Does anyone know who I have to contact in order to upstream this?

A case can be opened at https://bugreports.qt.io/projects/QTBUG/issues, buuuuut......

This is still technically an issue with Kwin or whichever component is responsible for setting the timing interval properly. Changing the default lib value works (and it works beautifully), but it's still just a hack to prove this works. The value likely will need to be calculated dynamically based on monitor hz. 

> It looks like these animations are all being driven by QUnifiedTimer

I'm curious how you found this! 

I've been scouring the plasma codebase and can't find anywhere that QUnifiedTimer::setTimingInterval() is referenced, or even portions of it. I checked user-installed plasmoids and other locations as well. 

If it can be properly set and retained globally as Kwin starts, it would have the same effect as changing the default upstream.
Comment 17 n.parkanyi 2024-12-08 02:13:49 UTC
(In reply to Blazer Silving from comment #16)
> > It looks like these animations are all being driven by QUnifiedTimer
> 
> I'm curious how you found this! 

This was bothering me, so I was grepping around in the qt code for potential culprits. QUnifiedTimer does seem to be a private API to qt. It could be that the animations that never have this issue are driven by something else. But at least in the case of system settings, it does _something_ that ends up fixing this (maybe in kirigami?).
Comment 18 Orlando Rodriguez 2024-12-08 03:03:59 UTC
It's great having a possible solution for this issue!!! Thanks!!!. Just a quick question, is there any guide about how to patch qtbase and reinstall it, so I can also test this? I have been googling a little bit and I found the repo and the file for qt, but I still don't know how to replace qtbase so this fix could work. Any information would be really appreciate it.  (I am using Fedora KDE 41 right now)
Comment 19 Blazer Silving 2024-12-08 04:13:05 UTC
(In reply to n.parkanyi from comment #17)
> (In reply to Blazer Silving from comment #16)
> > > It looks like these animations are all being driven by QUnifiedTimer
> > 
> > I'm curious how you found this! 
> 
> This was bothering me, so I was grepping around in the qt code for potential
> culprits. QUnifiedTimer does seem to be a private API to qt. It could be
> that the animations that never have this issue are driven by something else.
> But at least in the case of system settings, it does _something_ that ends
> up fixing this (maybe in kirigami?).

It bothered me too, especially how intermittent the behavior seemed to be. This does cover the behavior described in BUG 455585 that we'd been tracking for some time. 
Great find, for real. This may indeed be an upstream QT bug that needs to be filed and looked at officially, they may have pointers on how to better handle it on the KDE side as well. 

(In reply to Orlando Rodriguez from comment #18)
> It's great having a possible solution for this issue!!! Thanks!!!. Just a
> quick question, is there any guide about how to patch qtbase and reinstall
> it, so I can also test this? I have been googling a little bit and I found
> the repo and the file for qt, but I still don't know how to replace qtbase
> so this fix could work. Any information would be really appreciate it.  (I
> am using Fedora KDE 41 right now)

The quickest way to test a one-line fix like this is to build a patched package for your distro, and install it over the system version. 
Most offer tools and helpers for this (PKGBUILD for Arch, fedpkg for Fedora, etc). Generally the package manager will overwrite the patched version on the next repo update. Even cooler is setting up an isolated container to do the build, so you don't have to install all the build dependencies on your main system :)
Comment 20 Orlando Rodriguez 2024-12-08 05:58:17 UTC
(In reply to Blazer Silving from comment #19)
> The quickest way to test a one-line fix like this is to build a patched
> package for your distro, and install it over the system version. 
> Most offer tools and helpers for this (PKGBUILD for Arch, fedpkg for Fedora,
> etc). Generally the package manager will overwrite the patched version on
> the next repo update. Even cooler is setting up an isolated container to do
> the build, so you don't have to install all the build dependencies on your
> main system :)

Thank you so much!! I was able to apply the path by using fedpkg (I did not know about it). And for me it works really really well. Now KDE looks just AWESOME!!! Big difference in my desktop machine with 144hz monitors.
Comment 21 Blazer Silving 2025-01-06 21:39:46 UTC
Kindly, can we get acknowledgement from someone KDE-side here? A knowledgable dev could know Kwin's role in this timing value, or insight on how this could be handled upstream in QT
Comment 22 Brandon Wright 2025-02-25 17:41:03 UTC
I've been trying to investigate this a bit, and here are some findings:

In qabstractanimation.cpp:

1. QUnifiedTimer isn't actually used directly in this case. It's only user, QDefaultAnimationTimer just pulls in the value for timingInterval whenever it is instantiated. The timingInterval value of 16 is *never* changed.

2. Because the value is never changed, animations using that timer in Qt will always be 60fps. This is a Qt bug.

3. With kwin on X11, some actions like the ALT-TAB switcher seem to trigger a change such that kwin doesn't use that unified timer any more. It doesn't alter the timings, it just uses an entirely different timer. On Wayland, this change never occurs, so it's always stuttery.

I'm speculating that some component of legacy kwin on X11 is accidentally creating a new animation timer over the top of the original default one. The solution in kwin would then be to change out that original default timer with whatever kwin/X11 is accidentally overwriting it with. That way both X11 and Wayland are smooth.
Comment 23 Nate Graham 2025-03-31 20:20:33 UTC
*** Bug 502178 has been marked as a duplicate of this bug. ***
Comment 24 David Edmundson 2025-04-02 09:30:28 UTC
Blazer, can you try cherry-picking the commit from: work/d_ed/animation_driver in kwin.

I wrote it ages ago, when looking into the same issue, but it all looked the same to me. You might have a better eye for this sort of thing
Comment 25 Blazer Silving 2025-04-02 13:28:43 UTC
(In reply to David Edmundson from comment #24)
> Blazer, can you try cherry-picking the commit from:
> work/d_ed/animation_driver in kwin.
> 
> I wrote it ages ago, when looking into the same issue, but it all looked the
> same to me. You might have a better eye for this sort of thing

Thanks for the pointer, I believe it's this commit: https://invent.kde.org/plasma/kwin/-/commit/7fdcfb89cfbb4b54a4715c34449e0d0896278f5a. However it only seems to be a cross-check regarding the kwin animationDuration value when kwinrc changes were made back then, this issue is more related to the timer/tick counter. It didn't seem to make a difference when using unpatched qt6-base. 

The findings in Brandon Wright's post are spot-on, a legacy kwin tick/timer must be acting up. Inspecting and unifying the timer(s) seems like the best approach so far, but it may be more difficult to test in a debugging session due to the high framerate required to replicate the issue.
Comment 26 Brandon Wright 2025-04-03 20:35:08 UTC
Unfortunately, I don't think this will be fixed. The problem started when kwin switched over to a QtQuick scene for the window switching effects. QtQuick scene animations have only ever operated at 60hz:

* Qt is hardcoded with that define to wait 16ms before considering an animation step
* That timer is non-public.
* The hardcoded value can't change because QtQuick is heavily depended upon in embedded commercial applications, and that timer was deliberately chosen to get low battery and low latency.

If Qt adds a public API to access the timer then kwin could change the timer step before initiating a window switch effect. This isn't likely because over QtQuick hasn't ever been tested with higher frame rates due to that hardcoded value. If Qt can't change, this would need a reversion of the use of QtQuick scenes in kwin, but I don't think they're going to go for that.

What's happening when X11 becomes smooth after some time is a Qt bug. The timer is accidentally overwritten because they're all pooled and reused, and the timer ends up triggering instantly.
Comment 27 Merkurio 2025-04-03 23:37:13 UTC
Is there a way to apply that patch, but explained for complete beginners?  

I'm using NixOS (which is inherently different due to its declarative nature), but I'm also experiencing this issue with Plasma 6.3 in both X11 and Wayland sessions while using a 4K 240Hz monitor, and I'd like to fix it.  

At 120Hz, I don't have major issues, but as soon as I switch to 240Hz, the whole system feels like it's running at 60Hz (I even confirmed this with Blur Busters). Interestingly, a clean initial installation worked correctly, but now, after installing everything I need for my system, it's behaving erratically, and I can't take full advantage of my monitor's maximum refresh rate.
Comment 28 Zamundaaa 2025-04-04 14:32:35 UTC
(In reply to Merkurio from comment #27)
> At 120Hz, I don't have major issues, but as soon as I switch to 240Hz, the
> whole system feels like it's running at 60Hz (I even confirmed this with
> Blur Busters).
That's definitely not related to this, please open a separate bug report.
Comment 29 Blazer Silving 2025-04-04 16:24:02 UTC
> If Qt adds a public API to access the timer then kwin could change the timer step before initiating a window switch effect. 
> If Qt can't change, this would need a reversion of the use of QtQuick scenes in kwin, but I don't think they're going to go for that.

I can't speak for kwin developers but yes, very highly doubt they'll walk back these Windowheap effects or port to another system. I'm sure this condition can be counted for without a full revert. 

It's still worth reporting upstream to QT, considering the mission-criticality of the value. If I understand, they only need to provide a separate public, dynamic timer for QtQuick to utilize, then kwin would need to integrate it.
Comment 30 Brandon Wright 2025-04-04 19:41:07 UTC
(In reply to Blazer Silving from comment #29)
> It's still worth reporting upstream to QT, considering the
> mission-criticality of the value. If I understand, they only need to provide
> a separate public, dynamic timer for QtQuick to utilize, then kwin would
> need to integrate it.
According to https://doc.qt.io/qt-6/qtquick-visualcanvas-scenegraph.html
there's some way to replace the default animation driver with a custom one that doesn't run at 60Hz. I'm looking into this.
Comment 31 Brandon Wright 2025-04-04 20:49:44 UTC
(In reply to Brandon Wright from comment #30)
> (In reply to Blazer Silving from comment #29)
> > It's still worth reporting upstream to QT, considering the
> > mission-criticality of the value. If I understand, they only need to provide
> > a separate public, dynamic timer for QtQuick to utilize, then kwin would
> > need to integrate it.
> According to https://doc.qt.io/qt-6/qtquick-visualcanvas-scenegraph.html
> there's some way to replace the default animation driver with a custom one
> that doesn't run at 60Hz. I'm looking into this.
Nope, no go. That just describes what *they* did under the hood. It's all private and hidden. What we need is a simple setTimerInterval for the QQuickWindow class that drills down through three or four private layers of the scene graph and sets that value. Qt seems to desperately not want anyone to change it.
Comment 32 Zamundaaa 2025-04-04 20:52:07 UTC
(In reply to Brandon Wright from comment #22)
> I've been trying to investigate this a bit, and here are some findings:
> 
> In qabstractanimation.cpp:
> 
> 1. QUnifiedTimer isn't actually used directly in this case. It's only user,
> QDefaultAnimationTimer just pulls in the value for timingInterval whenever
> it is instantiated. The timingInterval value of 16 is *never* changed.
> 
> 2. Because the value is never changed, animations using that timer in Qt
> will always be 60fps. This is a Qt bug.
QSGRenderThread::syncAndRender advances the animation driver every time right before rendering, afaict that is not throttled below the display refresh rate.
Comment 33 Brandon Wright 2025-04-04 21:02:33 UTC
(In reply to Zamundaaa from comment #32)
> (In reply to Brandon Wright from comment #22)
> > I've been trying to investigate this a bit, and here are some findings:
> > 
> > In qabstractanimation.cpp:
> > 
> > 1. QUnifiedTimer isn't actually used directly in this case. It's only user,
> > QDefaultAnimationTimer just pulls in the value for timingInterval whenever
> > it is instantiated. The timingInterval value of 16 is *never* changed.
> > 
> > 2. Because the value is never changed, animations using that timer in Qt
> > will always be 60fps. This is a Qt bug.
> QSGRenderThread::syncAndRender advances the animation driver every time
> right before rendering, afaict that is not throttled below the display
> refresh rate.
I don't think that's public. If it was, you could use your own timer, but for all I can see, it's all encapsulated.
Comment 34 Zamundaaa 2025-04-07 14:34:06 UTC
There's nothing to be public, this happens when KWin tells Qt to render the window.
Comment 35 Brandon Wright 2025-04-07 16:06:08 UTC
(In reply to Zamundaaa from comment #34)
> There's nothing to be public, this happens when KWin tells Qt to render the
> window.
It definitely says it's supposed to, but that doesn't seem to be true. I traced the advance function through and it doesn't ever update the actual scene graph. It finally eventually reaches AnimatorController, which at the lowest level just calls commit on a job, which in its base class is completely empty, and doesn't do much in the one derived class:

> void QQuickTransformAnimatorJob::Helper::commit()
> {
>     if (!wasChanged || !node)
>         return;
> 
>     QMatrix4x4 m;
>     m.translate(dx, dy);
>     m.translate(ox, oy);
>     m.scale(scale);
>     m.rotate(rotation, 0, 0, 1);
>     m.translate(-ox, -oy);
>     node->setMatrix(m);
> 
>     wasChanged = false;
> }
> 
> void QQuickTransformAnimatorJob::commit()
> {
>     if (m_helper)
>         m_helper->commit();
> 
> }

Then it calls update() to refresh the window. That's it. From the name, advance is clearly supposed to actually advance something, but it doesn't. So this is definitely a Qt bug.
Comment 36 Brandon Wright 2025-04-09 22:40:33 UTC
I looked into it further, and most of what Zamundaaa says is correct, except this: 
> this happens when KWin tells Qt to render the window.
The only time that syncAndRender is called is directly from the render loop. The individual sync and render functions called from kwin are not the same thing. The render loop constantly calls syncAndRender, so theoretically it would be constantly updating, but it's not. Eventually, it all just narrows back to that QUnifiedTimer's trigger rate, which continues to be ever unchanging at 16ms. 

Another thing that confused me: there's two different advance functions, one in QQuickAnimationController which does almost nothing, the other in QAnimationDriver used by syncAndRender still throttles on the timer.
Comment 37 David Edmundson 2025-04-10 11:11:41 UTC
>QSGRenderThread::syncAndRender advances the animation driver every time right before rendering, afaict that is not throttled below the display refresh rate.

KWin won't use that.

Kwin *currently at least* only uses the main thread render loop
Comment 38 Zamundaaa 2025-04-10 14:34:12 UTC
hmm right, QQuickRenderControl that we do use doesn't seem to have equivalent logic.
Comment 39 Brandon Wright 2025-04-13 20:46:52 UTC
Ok, here's a hacky example of how to fix it:

> diff --git a/src/effect/offscreenquickview.cpp b/src/effect/offscreenquickview.cpp
> index 3dc83f9b2e..44ed0e8eae 100644
> --- a/src/effect/offscreenquickview.cpp
> +++ b/src/effect/offscreenquickview.cpp
> @@ -31,10 +31,13 @@
>  #include <QQuickRenderTarget>
>  #include <QTimer>
>  #include <private/qeventpoint_p.h> // for QMutableEventPoint
> +#include <private/qabstractanimation_p.h>
>  
>  namespace KWin
>  {
>  
> +static bool instantiatedTimer = false;
> +
>  class Q_DECL_HIDDEN OffscreenQuickView::Private
>  {
>  public:
> @@ -146,6 +149,13 @@ OffscreenQuickView::OffscreenQuickView(ExportMode exportMode, bool alpha)
>      d->m_repaintTimer->setSingleShot(true);
>      d->m_repaintTimer->setInterval(10);
>  
> +    if (!instantiatedTimer)
> +    {
> +        auto timerInstance = QUnifiedTimer::instance();
> +        timerInstance->setTimingInterval(1);
> +        instantiatedTimer = true;
> +    }
> +
>      connect(d->m_repaintTimer.get(), &QTimer::timeout, this, &OffscreenQuickView::update);
>      connect(d->m_renderControl.get(), &QQuickRenderControl::renderRequested, this, &OffscreenQuickView::handleRenderRequested);
>      connect(d->m_renderControl.get(), &QQuickRenderControl::sceneChanged, this, &OffscreenQuickView::handleSceneChanged);

This isn't the best. It's just to show an example that works. It uses private APIs. The timing interval would need to be set appropriate to the appropriate time of the fastest refresh rate and the use of a static global would need to be changed to something else and moved to the actual kwin initialization.

The presumably sanctioned alternative is to inherit QAnimationDriver and use the install() method, which replaces the global animation driver. This would need to add its own logic and run the advanceAnimation() method to update the scene graph. If you wanted it to be driven by the window being refreshed like Zamundaaa says is supposed to happen, you could hook the refresh signal up and just call advanceAnimation instead of doing any timers. That might cause problems if the scene graph stuff is being used elsewhere.
Comment 40 Brandon Wright 2025-04-13 20:57:57 UTC
I didn't notice the overload on instance() which lets you keep the current, so here's a better version using the private API:
> diff --git a/src/effect/offscreenquickview.cpp b/src/effect/offscreenquickview.cpp
> index 3dc83f9b2e..f556e3e14f 100644
> --- a/src/effect/offscreenquickview.cpp
> +++ b/src/effect/offscreenquickview.cpp
> @@ -31,6 +31,7 @@
>  #include <QQuickRenderTarget>
>  #include <QTimer>
>  #include <private/qeventpoint_p.h> // for QMutableEventPoint
> +#include <private/qabstractanimation_p.h>
>  
>  namespace KWin
>  {
> @@ -145,6 +146,7 @@ OffscreenQuickView::OffscreenQuickView(ExportMode exportMode, bool alpha)
>      d->m_repaintTimer = std::make_unique<QTimer>();
>      d->m_repaintTimer->setSingleShot(true);
>      d->m_repaintTimer->setInterval(10);
> +    QUnifiedTimer::instance(false)->setTimingInterval(1);
>  
>      connect(d->m_repaintTimer.get(), &QTimer::timeout, this, &OffscreenQuickView::update);
>      connect(d->m_renderControl.get(), &QQuickRenderControl::renderRequested, this, &OffscreenQuickView::handleRenderRequested);

Change the 1 in setTimingInterval to be lower than smallest refresh rate interval of all available displays.
Comment 41 Brandon Wright 2025-04-13 21:10:35 UTC
Sticking
QUnifiedTimer::instance(false)->updateAnimationTimers(); at the beginning of OffscreenQuickView::update()
is probably the wanted behavior.
Comment 42 Brandon Wright 2025-04-14 14:39:29 UTC
Created attachment 180257 [details]
Patch using private API to update scene graph timers when window refreshed

I've attached a patch for kwin that uses the private API to update the timers before rendering. This is pretty simple and seems to fix the problem for me. Let's see if it works for anyone else.
Comment 43 David Edmundson 2025-04-14 21:45:42 UTC
>The presumably sanctioned alternative is to inherit QAnimationDriver and use the install() method, which replaces the global animation driver.

Yeah, that's what I had in #24, though it seemed that got lost in the next comment. 

I rebased it and putting a direct myself this time :)
https://invent.kde.org/plasma/kwin/-/commits/work/d_ed/animation_driver

---

I'll test your patch too and see if that is more noticeable for me.
Comment 44 Brandon Wright 2025-04-15 00:41:01 UTC
(In reply to David Edmundson from comment #43)
> >The presumably sanctioned alternative is to inherit QAnimationDriver and use the install() method, which replaces the global animation driver.
> 
> Yeah, that's what I had in #24, though it seemed that got lost in the next
> comment. 
> 
> I rebased it and putting a direct myself this time :)
> https://invent.kde.org/plasma/kwin/-/commits/work/d_ed/animation_driver
> 
> ---
> 
> I'll test your patch too and see if that is more noticeable for me.
Yeah, I only saw the duration patch before. I tried the animation driver one and it causes the animation to not update at all. Because it's installed globally, I'd guess a subclassed animation driver would need a lot of duplicated boilerplate so it reacts similarly enough to QDefaultAnimationTimer to not break things. QUnifiedTimer::updateAnimationTimers is essentially exactly what we needed. It doesn't have any side-effects because it just triggers an early update and then lets the timer get on with its business. It's unfortunate that it's a private API.

I'm also wondering whether other components using QtQuick would need changes. Are they using the full render loop or doing a partially custom thing like kwin?.
Comment 45 David Edmundson 2025-04-15 09:22:15 UTC
>I'm also wondering whether other components using QtQuick would need changes.

Most others are fine. If you're using threaded rendering (which is the default) you get a custom animation driver provided by QSGGuiThreadedRenderLoop. It makes use of the quick animation driver being backed by thread-local storage to end up having a custom animation driver per window that gets driven.

That remains an option for kwin, but introducing threads always has some risk!
Comment 46 Brandon Wright 2025-04-15 13:31:55 UTC
Created attachment 180290 [details]
Patch using private API to update scene graph timers when window refreshed 2

Update patch for possible null pointer dereference.
Comment 47 Brandon Wright 2025-04-15 15:24:59 UTC
Created attachment 180293 [details]
Patch using public API and setting interval to 4ms

Here's a patch using the public QAnimationDriver API and using it to set the interval to 16ms by default, then 4ms in workspace.cpp as an example, which makes things smoother. I tried using Workspace::init like you did to set the interval lower than the fastest refresh rate, which would be (1000000 / fastestRefreshRate), but it just doesn't detect any displays at that point. It probably needs to be done wherever kwin does display hotplugging and/or video mode changes. 

I also tried doing a singleton instance and running advance on the window update, but this doesn't work right because it accelerates the animation. QDefaultAnimationDriver has access to QUnifiedTimer's private API and can hook the timer update properly, but with what we're given it's not possible to do something like my simpler patch. You could have the animation driver not do timed updates at all and then update on refresh, but I worry that it would break something else--especially if kwin adds other things using QtQuick.
Comment 48 Brandon Wright 2025-04-17 15:53:02 UTC
Created attachment 180356 [details]
Patch using public API and setting interval to lowest refresh rate on hotplug

Here's a better patch that might be of appropriate enough quality. This goes in quickeffect and sets the minimum interval when displays are added to the QuickSceneEffect.
Comment 49 Brandon Wright 2025-04-17 16:34:59 UTC
(In reply to Brandon Wright from comment #48)
> Created attachment 180356 [details]
> Patch using public API and setting interval to lowest refresh rate on hotplug
> 
> Here's a better patch that might be of appropriate enough quality. This goes
> in quickeffect and sets the minimum interval when displays are added to the
> QuickSceneEffect.

Actually, ignore this one. It's not as smooth for some reason. I can't seem to get the same smoothness as the simple private API patch. I think it's necessary to have access to the private members of QAnimationDriver and/or QUnifiedTimer to fix correctly.
Comment 50 Merkurio 2025-05-08 10:11:40 UTC
(In reply to Zamundaaa from comment #28)
> (In reply to Merkurio from comment #27)
> > At 120Hz, I don't have major issues, but as soon as I switch to 240Hz, the
> > whole system feels like it's running at 60Hz (I even confirmed this with
> > Blur Busters).
> That's definitely not related to this, please open a separate bug report.

I opened a post earlier in the KDE discussion forum with the details of my situation before opening a new bug report, as you suggested:

https://discuss.kde.org/t/240-hz-feels-like-60-hz-system-wide-nixos-user/33826

But... are we sure it's not the same issue as this one?
Comment 51 Brandon Wright 2025-06-03 14:03:52 UTC
Could we get attachment 180290 [details], "Patch using private API to update scene graph timers when window refreshed 2" added in for kwin 6.4? Aside from using private API, which kwin already does in that file, it's the simplest and most direct fix. It simply makes the animation timers current, which means kwin doesn't draw the same scene over again when the refresh rate is higher than 62.5Hz. The code is already updating and redrawing at full refresh, it's only the scene positions that aren't updated every frame because they rely on internal animation timers that only the private QUnifiedTimer API can access. This patch even fixes some judder at 60Hz from skipped animation positions.
Comment 52 hfc2x 2025-08-05 15:56:03 UTC
So, there are actually 2 bugs at play here that have very similar (and possibly related) symptoms and both of them are happening on Wayland. I realized this yesterday because I'm experiencing this Qt bug in a laptop with a 120 Hz built-in screen that's connected to a 144 Hz external monitor.

Turns out, yes, Qt indeed has a timer bug because I have the exact same symptoms described on Comment 2, with all Plasma animations looking super choppy in the external monitor (dragging windows around, maximizing, minimizing, etc), with the notable exceptions of the Emoji Picker, System Monitor, and Discover being super smooth. This doesn't happen in the built-in screen, where everything is smooth regardless.

This particular laptop has an Optimus setup (dual Intel Graphics/NVIDIA GPU), and there seems to be an NVIDIA bug that has a lot of things in common with this Qt bug. This is, however, not exclusively affecting NVIDIA, as some of the people in this comment section have AMD Radeon graphics.

On the NVIDIA side at least, it seems that something (this Qt bug?) is causing a delay in per-frame notification between the compositor and the GPU, causing the NVIDIA card to miss the vblank interval every other frame, leading to a halved refresh rate (more info here: https://github.com/NVIDIA/open-gpu-kernel-modules/issues/650#issuecomment-2668160546). This problem affects both the NVIDIA open and proprietary drivers. I could actually verify this being the case for this particular laptop, because the Blur Busters UFO test would actually show 70-72 FPS on the external monitor indicating that the refresh rate of Plasma desktop was exactly half of the 144 Hz of the external monitor. Moving the browser window into the built-in display would cause the UFO test to immediately jump to 117-120 FPS, matching the 120 Hz of the built-in screen. At the very least, on the NVIDIA side, this is a widespread bug that not only affects Kwin, but other Wayland compositors as well (see: https://forums.developer.nvidia.com/t/nvidia-please-get-it-together-with-external-monitors-on-wayland/301684).

I haven't tested this on AMD, because I don't have any laptops that have a Radeon dGPU, and my desktop PC isn't affected by this bug. However, a workaround for the time being is forcing Kwin to change GPU priority by editing /etc/environment and use the KWIN_DRM_DEVICES environment variable to force dGPU preference (I personally set it to "/dev/dri/by-path/pci-0000\:01\:00.0-card:/dev/dri/by-path/pci-0000\:00\:02.0-card"). This solves the framerate issue on the external monitor by forcing the dGPU to draw the Plasma desktop, but it's not ideal because of the high power consumption when on battery. This probably also helps with AMD GPUs, but someone else would have to test it.

Still, like I said, this is only a workaround for the time being, and should not be used as a final solution.
Comment 53 Blazer Silving 2025-08-05 16:25:08 UTC
(In reply to Brandon Wright from comment #51)
> Could we get attachment 180290 [details], "Patch using private API to update
> scene graph timers when window refreshed 2" added in for kwin 6.4? Aside
> from using private API, which kwin already does in that file, it's the
> simplest and most direct fix. It simply makes the animation timers current,
> which means kwin doesn't draw the same scene over again when the refresh
> rate is higher than 62.5Hz. The code is already updating and redrawing at
> full refresh, it's only the scene positions that aren't updated every frame
> because they rely on internal animation timers that only the private
> QUnifiedTimer API can access. This patch even fixes some judder at 60Hz from
> skipped animation positions.

Brandon, do you have a KDE Invent account? I can create a merge request upstream, but I want to ensure you receive credit for the fix. I don't think anyone else is lining up to do the paperwork, but i'd be honored to if you like! 

I can't speak for NVIDIA systems, the patch you provide resolves the timing issue for me on AMDGPU in Wayland and X11 at 144hz, butter-smooth 100% of the time. I have been building it into my system kwin package (qt6-base before) all year, and I notice immediately that it's missing when testing the master branch.
Comment 54 Brandon Wright 2025-08-05 17:10:35 UTC
(In reply to Blazer Silving from comment #53)
> Brandon, do you have a KDE Invent account? I can create a merge request
> upstream, but I want to ensure you receive credit for the fix. I don't think
> anyone else is lining up to do the paperwork, but i'd be honored to if you
> like! 

Feel free to create a merge request wherever. It's such a simple fix, I don't need any credit for it.
Comment 55 Bug Janitor Service 2025-08-05 18:14:52 UTC
A possibly relevant merge request was started @ https://invent.kde.org/plasma/kwin/-/merge_requests/7980
Comment 56 Bug Janitor Service 2025-08-05 18:14:55 UTC
A possibly relevant merge request was started @ https://invent.kde.org/plasma/kwin-x11/-/merge_requests/68
Comment 57 Blazer Silving 2025-08-05 18:43:25 UTC
(In reply to Brandon Wright from comment #54)
> (In reply to Blazer Silving from comment #53)
> > Brandon, do you have a KDE Invent account? I can create a merge request
> > upstream, but I want to ensure you receive credit for the fix. I don't think
> > anyone else is lining up to do the paperwork, but i'd be honored to if you
> > like! 
> 
> Feel free to create a merge request wherever. It's such a simple fix, I
> don't need any credit for it.

I'll certainly thank you myself for the research you took to get to the simple fix :) 

Just noticed the build does fail on the automatic CI tests: 

> error: use of undeclared identifier 'QUnifiedTimer'

Now that it's on the invent board, that can be fixed. Hopefully won't be much more chatter here until it's merged or not!
Comment 58 Brandon Wright 2025-08-05 19:03:28 UTC
You're missing the 
#include <private/qabstractanimation_p.h>
line. FYI The use of Qt private API is the only reason why this patch is in any way controversial.
Comment 59 Blazer Silving 2025-08-05 19:20:40 UTC
(In reply to Brandon Wright from comment #58)
> You're missing the 
> #include <private/qabstractanimation_p.h>
> line. FYI The use of Qt private API is the only reason why this patch is in
> any way controversial.

Thanks, fixed! To note, <private/qeventpoint_p.h> is also being called, so it wouldn't be the first exception.
Comment 60 Sultan Alsawaf 2025-09-14 21:26:23 UTC
I applied https://invent.kde.org/plasma/kwin/-/merge_requests/7980 and unfortunately it doesn't fix this issue for me. Pretty much all Wayland apps still suffer from a low refresh rate. I can still reproduce the issue as described in the first comment, where scrolling in System Settings occurs at a low refresh rate until clicking "Colors and Themes".

The issue doesn't occur when forcing Xwayland for an app. My laptop is an HP ZBook Ultra G1a, which uses an AMD iGPU (no dGPU).

Hardware/environment info:
> $ inxi -CGMSm -y 80 --wrap-max 80
> System:    Host: sultan-box Kernel: 6.16.3 x86_64 bits: 64
>            Desktop: KDE Plasma 6.4.5 Distro: Arch Linux
> Machine:   Type: Laptop System: HP
>            product: HP ZBook Ultra G1a 14 inch Mobile Workstation PC
>            v: SBKPF,SBKPFV2 serial: <superuser required>
>            Mobo: HP model: 8D01 v: KBC Version 35.2B.00
>            serial: <superuser required> UEFI: HP v: X89 Ver. 01.03.00
>            date: 04/25/2025
> Memory:    RAM: total: 125.09 GiB used: 3.87 GiB (3.1%)
>            RAM Report:
>            unknown-error: Unknown dmidecode error. Unable to generate data.
> CPU:       Info: 16-Core model: AMD RYZEN AI MAX+ PRO 395 w/ Radeon 8060S
>            bits: 64 type: MT MCP cache: L2: 16 MiB
>            Speed: 1913 MHz min/max: 2000/5188 MHz Core speeds (MHz): 1: 1770
>            2: 2000 3: 2000 4: 2000 5: 2000 6: 2000 7: 2000 8: 2000 9: 3510
>            10: 3509 11: 1752 12: 2000 13: 2635 14: 1750 15: 3249 16: 2000
>            17: 2000 18: 2000 19: 2000 20: 2000 21: 2000 22: 1952 23: 2000
>            24: 2000 25: 1826 26: 2000 27: 3542 28: 2000 29: 3551 30: 2000
>            31: 2000 32: 2000
> Graphics:  Device-1: AMD Strix Halo [Radeon Graphics / Radeon 8050S Graphics /
>            Radeon 8060S Graphics]
>            driver: amdgpu v: kernel
>            Display: wayland server: X.Org 24.1.8 driver: loaded: modesetting
>            resolution: 2880x1800~120Hz
>            OpenGL: renderer: Radeon 8060S Graphics (radeonsi gfx1151 LLVM
>            20.1.8 DRM 3.64 6.16.3)
>            v: 4.6 Mesa 25.2.2-arch1.2
Comment 61 Brandon Wright 2025-09-14 23:33:13 UTC
(In reply to Sultan Alsawaf from comment #60)
> I applied https://invent.kde.org/plasma/kwin/-/merge_requests/7980 and
> unfortunately it doesn't fix this issue for me. Pretty much all Wayland apps
> still suffer from a low refresh rate. I can still reproduce the issue as
> described in the first comment, where scrolling in System Settings occurs at
> a low refresh rate until clicking "Colors and Themes".
> 
> The issue doesn't occur when forcing Xwayland for an app. My laptop is an HP
> ZBook Ultra G1a, which uses an AMD iGPU (no dGPU).

Those patches only affect the kwin effects. The system settings problem is the same thing in a different location, probably part of kirigami. If you're getting an overall low refresh rate in Wayland, that isn't this bug.
Comment 62 Ramon Dantas 2025-09-22 16:11:34 UTC
Hi, can we get some attention/updates on the PRs above? From what I've read there, the discussion seems to have come to a stalemate

It's a bit sad having to rebuild KWin from source every patch release for a 2 line diff =(
Comment 63 Blazer Silving 2025-10-15 05:19:26 UTC
*** Bug 510621 has been marked as a duplicate of this bug. ***