gef-classic icon indicating copy to clipboard operation
gef-classic copied to clipboard

Huge number of paint events triggered by overlay scrollbars on Linux/GTK3

Open wiresketch opened this issue 1 year ago • 18 comments

I'd like to file another issue that I've observed while working on #425. Trying to debug performance issues in my application on Linux/GTK3 I am noticing another problem with overlay scrollbars that negatively impacts performance. I'd like to open this issue to have a discussion for possible solutions and workarounds.

What happens is that when overlay scrollbars are visible for a control, the GTK system will show/hide them automatically on user activity, specifically on mouse moves. For example if the mouse stops moving over the scrolled area, scrollbars will fade away. If the mouse starts moving again - scrollbars will fade in. This animation for scrollbars triggers hundreds of SWT.Paint events that are then handled by LightweightSytem/DeferredUpdateManager.

At first I thought it would be logical for the content to be redrawn under the scrollbars, as long as the paint event is sent with the right clipping that only includes the region under the scrollbar. However I was surprised to find out that this is not the case. The entire client area is being forced to be repainted.

Since this behavior is actually the native behavior, it can be observed with a simple SWT snippet. I've attached it here. ScrollbarsTest.java.txt

Try moving the mouse and observe the number of logged paint events.

The same example can be launched with GTK_OVERLAY_SCROLLING=0 to disable overlay scrollbars, in which case a very limited number of paint events can be observed, and none while moving the mouse.

Since it looks like this is the native behavior on Linux/GTK3 not specific to Draw2d/GEF I would like to explore here possible ways to solve this performance issue.

One obvious option that I see is to force the Draw2d/GEF applications to be always used with overlay scrollbars disabled to achieve optimum performance on Linux. This means using GTK_OVERLAY_SCROLLING=0 variable on GTK3 when launching the application. However this has the side effect of disabling overlay scrollbars for all views in the application, not only for FigureCanvas control. Another problem is that this is not future proof as this environment variable is no longer being used on GTK4.

An improved solution would be to disable overlay scrollbars just for the FigureCanvas. This is actually possible using GTK3/GTK4 API by using the gtk_scrolled_window_set_overlay_scrolling method. The symmetrical gtk_scrolled_window_get_overlay_scrolling is already being using by SWT's Scrollable.getScrollbarsMode() method, so perhaps it wouldn't be too much to ask from the SWT team to add a Scrollable.setScrollbarsMode(int) method that would allow to disable overlay scrollbars on a specific FigureCanvas.

Other possible solutions are less clear to me. Perhaps something in DeferredUpdatedManager could be done to improve these repaints. Perhaps some kind of custom double buffering could do the trick.

I'd like to hear from others what they think should be done here.

wiresketch avatar Apr 18 '24 09:04 wiresketch

One obvious option that I see is to force the Draw2d/GEF applications to be always used with overlay scrollbars disabled to achieve optimum performance on Linux.

I'm leaning towards this approach. I fear that any attempt to change the native behavior within Draw2D will just result in a messy hack. It also has the effect that the controls would look the same across all platforms. Given the performance problem, I think it makes sense to have it be the default. But clients should still have the option to switch back to the current behavior via e.g. a system property or a static variable.

However this has the side effect of disabling overlay scrollbars for all views in the application, not only for FigureCanvas control.

I think that's the crucial part. Draw2D/GEF must not alter the behavior of other UI components. Otherwise this will ruin someones day, the moment something unrelated suddenly breaks.

An improved solution would be to disable overlay scrollbars just for the FigureCanvas. This is actually possible using GTK3/GTK4 API by using the gtk_scrolled_window_set_overlay_scrolling method. The symmetrical gtk_scrolled_window_get_overlay_scrolling is already being using by SWT's Scrollable.getScrollbarsMode() method, so perhaps it wouldn't be too much to ask from the SWT team to add a Scrollable.setScrollbarsMode(int) method that would allow to disable overlay scrollbars on a specific FigureCanvas.

That sounds like a good approach to me. If at all possible, I'd like to avoid calling any platform-specific methods in Draw2D. If the SWT team can provide an interface for this, then that would already help out a lot. Otherwise either Draw2D or downstream projects would have to compile their own native binary files and ship it with their jar. Both options I'm not overly excited about.

ptziegler avatar Apr 18 '24 14:04 ptziegler

I see you've already created an issue in the SWT project. Awesome! 😄 I'll try to also have a look at (at least) the Draw2d side during the weekend.

ptziegler avatar Apr 19 '24 09:04 ptziegler

I've created #432. My idea is to call setScrollbarsMode(...) inside the constructor of FigureCanvas. By default, SWT.NONE is used, which effectively disables overlay scrolling.

If clients want the old behavior, they can do so by calling the static method FigureCanvas.setScrollbarsMode(SWT.SCROLLBAR_OVERLAY) inside e.g. the Activator.

ptziegler avatar Apr 20 '24 14:04 ptziegler

I was more curios about the possibility of some kind of double buffering to optimize the paints. Is this something that could have some use? Or is the conclusion here that overlay scrollbars should never be used with FigureCanvas on Linux/GTK?

If Scrollable.setScrollbarsMode method gets added to SWT then it will be easy to change the scrollbars mode in RCP apps directly using something like this:

	protected void configureGraphicalViewer() {
		((Scrollable) getGraphicalViewer().getControl()).setScrollbarsMode(SWT.SCROLLBAR_OVERLAY);
	}

With SWT versions checks and the use of reflection if needed. I have a lot of those in my RCP app to work around various bugs. I am not 100% convinced that GEF/Draw2d should contain those workarounds.

wiresketch avatar Apr 22 '24 15:04 wiresketch

It's always risky to touch very basic classes like the FigureCanvas, which is why I'm a little hesitant to do so. Disabling overlay scrolling seems like a much safer think to do and at least in the short term, this should be the preferable solution.

We used to do double-buffering for a different project. In principle, one could create a prototype from that, but that would require a lot more testing.

ptziegler avatar Apr 23 '24 04:04 ptziegler

I was more curios about the possibility of some kind of double buffering to optimize the paints. Is this something that could have some use?

There is probably no harm in trying this out. Though it has to be an opt-in feature, as to not disrupt other clients. I've created a PoC which applies double buffering for the entire LWS (see https://github.com/eclipse/gef-classic/pull/434).

Is this what you had in mind?

ptziegler avatar Apr 25 '24 19:04 ptziegler

@ptziegler This is close to what I had in mind but not quite. What you've implemented is a classic double-buffering that should not be needed with SWT.DOUBLE_BUFFERING style, and is unlikely to help with performance. I believe that originally this method was meant to reduce flickering on paint.

What I had in mind is reusing the same image for multiple paint calls without redrawing the buffered image each time. It means there needs to be a flag like "bufferedImageIsStale" which would indicate that the buffered image needs to be updated. And even then we would only need to update the part of the buffered image that really changed and needs to be redrawn as indicated by the clipping of the GC.

I've experimented a little bit with this approach and I still need more time to see what's needed for a correct implementation. There are some issues with scrolling optimization that also needs to be propagated to buffered image.

I believe that this kind of optimization might help with performance not only on Linux, but on other platforms too. There are several cases where the paint method is being called multiple times when certain actions/event are performed:

  • Overlay scrollbars animation (the original issue)
  • Dragging something over the content
  • Changing the focused tab in Eclipse (not a performance issue, but it's an example)

I don't know if this idea is actually implementable. Perhaps there are some sync issues (as always with caching) that I am not seeing yet, but this is what I had in mind.

wiresketch avatar Apr 26 '24 08:04 wiresketch

Hm... I see. Perhaps instead of using a "stale" flag, one could make it so that whenever there is a surge of paint events, the actual paint operation is only done once the "last" event has been received.

This would at least be something that works well together with the classic double-buffering approach. Until the last event has been received, the "old" image is painted. The paint event itself creates a "new" image, replacing the "old" one upon completion.

The Throttler does something similar. I wonder if one could re-use parts of it... The only real downside is that the update becomes asynchronous, but with a low enough latency, this probably wouldn't be noticable.

ptziegler avatar Apr 26 '24 12:04 ptziegler

I'm a bit late to join this party. But I wanted to point out that during my performance measurements I noticed that the image copying is not as fast as it may seem. In 4diac IDE we have a thumbnail outline of the diagrams and the thumbnailer uses images with copying to create it and a significant amount of execution time is on copying the images.

I also noted that at least on Windows the repaints are really only on the regions that are given to the lightweight system, while on GTK always the full widget is repainted. You may want to consider this when introducing some buffering.

azoitl avatar Apr 26 '24 16:04 azoitl

The Throttler does something similar. I wonder if one could re-use parts of it... The only real downside is that the update becomes asynchronous, but with a low enough latency, this probably wouldn't be noticable.

I just looked into the Throttler. This would exactly be needed for reducing the impact of the Thumbnailer outline. However I expect that jface is not suitable for draw2d isn't it?

azoitl avatar Apr 26 '24 19:04 azoitl

I just looked into the Throttler. This would exactly be needed for reducing the impact of the Thumbnailer outline. However I expect that jface is not suitable for draw2d isn't it?

A dependency to JFace is not an option. However, the class only depends on SWT so if necessary, we could simply copy it and go from there.

ptziegler avatar Apr 26 '24 19:04 ptziegler

I just looked into the Throttler. This would exactly be needed for reducing the impact of the Thumbnailer outline. However I expect that jface is not suitable for draw2d isn't it?

A dependency to JFace is not an option. However, the class only depends on SWT so if necessary, we could simply copy it and go from there.

In the mean-time I brushed up my Thumbnail know how. It looks I would anyhow need to have a dedicated implementation for the Thumbnail. So no quick win here.

azoitl avatar Apr 26 '24 19:04 azoitl

Yes, copy and paste is not forbidden. It’s EPL.

merks avatar Apr 26 '24 19:04 merks

This issue is stale because it has been open for 90 days with no activity.

github-actions[bot] avatar Jul 26 '24 01:07 github-actions[bot]

This issue is stale because it has been open for 90 days with no activity.

github-actions[bot] avatar Dec 05 '24 02:12 github-actions[bot]

This issue is stale because it has been open for 90 days with no activity.

github-actions[bot] avatar Mar 06 '25 02:03 github-actions[bot]