Skip to content
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

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

Open
wiresketch opened this issue Apr 18, 2024 · 13 comments
Open

Comments

@wiresketch
Copy link
Contributor

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.

@ptziegler
Copy link
Contributor

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
Copy link
Contributor

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 added a commit to ptziegler/gef-classic that referenced this issue Apr 20, 2024
This allows the user to specify whether new instances of FigureCanvas
should use overlay scrolling on not. This is disabled by default, due to
performance issues on Linux/GTK3.

As a result, the scrollbar is shown as a separate entity and no longer
part of the client area. This setting only affects operating systems
supporting overlay scrolling. This method requires SWT 3.126 or newer.

eclipse#430
@ptziegler
Copy link
Contributor

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 added a commit to ptziegler/gef-classic that referenced this issue Apr 20, 2024
This allows the user to specify whether new instances of FigureCanvas
should use overlay scrolling on not. This is disabled by default, due to
performance issues on Linux/GTK3.

As a result, the scrollbar is shown as a separate entity and no longer
part of the client area. This setting only affects operating systems
supporting overlay scrolling. This method requires SWT 3.126 or newer.

eclipse#430
ptziegler added a commit to ptziegler/gef-classic that referenced this issue Apr 21, 2024
This allows the user to specify whether new instances of FigureCanvas
should use overlay scrolling on not. This is disabled by default, due to
performance issues on Linux/GTK3.

As a result, the scrollbar is shown as a separate entity and no longer
part of the client area. This setting only affects operating systems
supporting overlay scrolling. This method requires SWT 3.126 or newer.

eclipse#430
ptziegler added a commit to ptziegler/gef-classic that referenced this issue Apr 21, 2024
This allows the user to specify whether new instances of FigureCanvas
should use overlay scrolling on not. This is disabled by default, due to
performance issues on Linux/GTK3.

As a result, the scrollbar is shown as a separate entity and no longer
part of the client area. This setting only affects operating systems
supporting overlay scrolling. This method requires SWT 3.126 or newer.

eclipse#430
@wiresketch
Copy link
Contributor Author

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.

@ptziegler
Copy link
Contributor

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 added a commit to ptziegler/gef-classic that referenced this issue Apr 25, 2024
By using the `org.eclipse.draw2d.LightweightSystem.useDoubleBuffering`,
clients can enable a special functionality, that first paints all UI
updates to an internal buffer, before it is painted to the screen.

The hope of behavior is to increase the perceived performance of an
application by detaching rendering from the display.

eclipse#430
@ptziegler
Copy link
Contributor

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 #434).

Is this what you had in mind?

ptziegler added a commit to ptziegler/gef-classic that referenced this issue Apr 25, 2024
By using the `org.eclipse.draw2d.LightweightSystem.useDoubleBuffering`,
clients can enable a special functionality, that first paints all UI
updates to an internal buffer, before it is painted to the screen.

The hope of behavior is to increase the perceived performance of an
application by detaching rendering from the display.

eclipse#430
ptziegler added a commit to ptziegler/gef-classic that referenced this issue Apr 26, 2024
By using the `org.eclipse.draw2d.LightweightSystem.useDoubleBuffering`,
clients can enable a special functionality, that first paints all UI
updates to an internal buffer, before it is painted to the screen.

The hope of behavior is to increase the perceived performance of an
application by detaching rendering from the display.

eclipse#430
@wiresketch
Copy link
Contributor Author

@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.

@ptziegler
Copy link
Contributor

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.

@azoitl
Copy link
Contributor

azoitl commented Apr 26, 2024

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
Copy link
Contributor

azoitl commented Apr 26, 2024

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?

@ptziegler
Copy link
Contributor

ptziegler commented Apr 26, 2024

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.

@azoitl
Copy link
Contributor

azoitl commented Apr 26, 2024

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.

@merks
Copy link
Contributor

merks commented Apr 26, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants