Skip to main content


Eclipse Community Forums
Forum Search:

Search      Help    Register    Login    Home
Home » Eclipse Projects » GEF » FXCanvasEx Retina display problem(Dragging of elements is linearly slower with increasing editors area)
icon4.gif  FXCanvasEx Retina display problem [message #1752479] Tue, 24 January 2017 10:47 Go to next message
Miroslav Stefanovic is currently offline Miroslav StefanovicFriend
Messages: 4
Registered: January 2017
Junior Member
First of all,
I would like to say big thanks to GEF team on effort so far.

I have developed so far an editor with simple JavaFX blocks inside where I can change block position using drag and drop feature. I'm using Eclipse RCP and AbstractFXEditor as part for integration.


When I have only one block (simple JavaFX rectangle) and when I want to move that block on a different position I'm noticing delay. Problem occurs on Mac only. In case of Retina display it's more obvious it takes up to 5 seconds if editor is in full screen mode (resolution is 2880 x 1800) to redraw a simple block after mouse release. See gif attachment.

System setup:
- MacBook Pro (Retina, 15-inch, Mid 2015)
- 2.5 GHz Intel Core i7, 16Gb 1600 MHz DDR3
- Graphics AMD Radeon R9 M370X 2048MB
- OS X El Capitan
- Elipse Mars for RCP but I'm using GEF4 for NEON release version (org.eclipse.gef4.*._1.1.0.201609060946)

I did some investigation and I have concluded:
* if editor area is small (300 x 300), there is no latency on Mac
* by increasing editor area to (700 x 700) latency becomes noticeable
* if I continue with increasing, latency become more and more obvious. It escalates on Mac wit Retina display where lag can take up to 5-6 seconds
* I have tried to configure MvcFxUiModule for Eclipse UI integration to use plain javafx.embed.swt.FXCanvas instead of org.eclipse.gef4.fx.swt.canvas.FXCanvasEx which solves the problem with latency. I see you have added SWT2FXEventConverter and cursorChangeListener to solve 2 issues but maybe some optimization could be performed to solve problem on mac

I'm looking forward to appropriate solution for this otherwise I will have to subclass javafx.embed.swt.FXCanvas but I'm note sure what will be the consequences in the rest of the framework.

Thanks,
Miroslav
Re: FXCanvasEx Retina display problem [message #1752493 is a reply to message #1752479] Tue, 24 January 2017 13:38 Go to previous messageGo to next message
Matthias Wienand is currently offline Matthias WienandFriend
Messages: 163
Registered: March 2015
Senior Member
Hi Miroslav,

thank you very much for reporting the issue in such detail. We noticed a delay with large viewports some time ago, but did not narrow down the reason for the delay, yet. Now that we know where to look (FXCanvasEx), I created a Bugzilla ticket [1] and will make a few tests to identify the performance killer (probably SWT redraw/update).

The inconveniences resolved by FXCanvasEx in contrast to FXCanvas, are forwarding of mouse wheel scroll and gesture events from SWT to JavaFX, consuming of already processed SWT events before forwarding them to JavaFX, correctly changing the mouse cursor, and ensuring that the canvas is redrawn even though many events are being dispatched. If you do not need to process gesture events etc., you can safely use FXCanvas until the issue is resolved.

[1] https://bugs.eclipse.org/bugs/show_bug.cgi?id=510946

Best regards,
Matthias
Re: FXCanvasEx Retina display problem [message #1752506 is a reply to message #1752493] Tue, 24 January 2017 14:48 Go to previous messageGo to next message
Miroslav Stefanovic is currently offline Miroslav StefanovicFriend
Messages: 4
Registered: January 2017
Junior Member
Ok. Thanks for quick response.

Regards,
Miroslav
Re: FXCanvasEx Retina display problem [message #1752518 is a reply to message #1752493] Tue, 24 January 2017 16:18 Go to previous messageGo to next message
Matthias Wienand is currently offline Matthias WienandFriend
Messages: 163
Registered: March 2015
Senior Member
Hi Miroslav,

the delay is not as significant or noticeable on my machine as it is on your machine, given the comparison gif. However, I experimented with calling SWT redraw/update from within the event dispatcher and found that calling redraw() without calling update() yields the best performance while still ensuring that the canvas stays responsive (as it is reasonably often redrawn). I asked Alexander to try the change out on his machine (which is a Mac), and we found that it improves performance significantly on a retina display. The fix will be available with GEF 5.0.0 M5. For the time being, you can create a replacement class (FXCanvasExEx) to replace the relevant code, as follows:
public class FXCanvasExEx extends FXCanvasEx {

	private final class RedrawingEventDispatcher implements EventDispatcher {

		private static final int REDRAW_INTERVAL_MILLIS = 40; // i.e. 25 fps
		private EventDispatcher delegate;
		private long lastRedrawMillis = System.currentTimeMillis();

		protected RedrawingEventDispatcher(EventDispatcher delegate) {
			this.delegate = delegate;
		}

		@Override
		public Event dispatchEvent(final Event event, final EventDispatchChain tail) {
			// dispatch the most recent event
			Event returnedEvent = delegate.dispatchEvent(event, tail);
			// update UI
			long millisNow = System.currentTimeMillis();
			if (millisNow - lastRedrawMillis > REDRAW_INTERVAL_MILLIS) {
				redraw();
				lastRedrawMillis = millisNow;
			}
			// return dispatched event
			return returnedEvent;
		}

		protected EventDispatcher dispose() {
			EventDispatcher d = delegate;
			delegate = null;
			return d;
		}
	}

	private ChangeListener<Cursor> cursorChangeListener = ReflectionUtils.getPrivateFieldValue(this,
			"cursorChangeListener");

	public FXCanvasExEx(Composite parent, int style) {
		super(parent, style);
	}

	@Override
	public void setScene(Scene newScene) {
		Scene oldScene = getScene();
		if (oldScene != null) {
			// restore original event dispatcher
			EventDispatcher eventDispatcher = oldScene.getEventDispatcher();
			if (eventDispatcher instanceof RedrawingEventDispatcher) {
				oldScene.setEventDispatcher(((RedrawingEventDispatcher) eventDispatcher).dispose());
				oldScene.cursorProperty().removeListener(cursorChangeListener);
			}
		}
		super.setScene(newScene);
		if (newScene != null) {
			// wrap event dispatcher
			newScene.setEventDispatcher(new RedrawingEventDispatcher(newScene.getEventDispatcher()));
			newScene.cursorProperty().addListener(cursorChangeListener);
		}
	}
}

Best regards,
Matthias
Re: FXCanvasEx Retina display problem [message #1752575 is a reply to message #1752518] Wed, 25 January 2017 09:26 Go to previous messageGo to next message
Miroslav Stefanovic is currently offline Miroslav StefanovicFriend
Messages: 4
Registered: January 2017
Junior Member
Hi Matthias,

thanks for trying to help. I have tried code spinet above but something is wrong.

private ChangeListener<Cursor> cursorChangeListener = ReflectionUtils.getPrivateFieldValue(this, "cursorChangeListener");


'cursorChangeListener' object that you are trying to get using reflection is NULL. Later it's propagated as NPE in method
@Override
public void setScene(Scene newScene) {
	Scene oldScene = getScene();
	if (oldScene != null) {
		// restore original event dispatcher
		EventDispatcher eventDispatcher = oldScene.getEventDispatcher();
		if (eventDispatcher instanceof RedrawingEventDispatcher) {
			oldScene.setEventDispatcher(((RedrawingEventDispatcher) eventDispatcher).dispose());
			oldScene.cursorProperty().removeListener(cursorChangeListener);
		}
	}
	super.setScene(newScene);
	if (newScene != null) {
		// wrap event dispatcher
		newScene.setEventDispatcher(new RedrawingEventDispatcher(newScene.getEventDispatcher()));
		newScene.cursorProperty().addListener(cursorChangeListener);
	}
}

[Updated on: Wed, 25 January 2017 09:28]

Report message to a moderator

Re: FXCanvasEx Retina display problem [message #1752580 is a reply to message #1752575] Wed, 25 January 2017 10:12 Go to previous message
Miroslav Stefanovic is currently offline Miroslav StefanovicFriend
Messages: 4
Registered: January 2017
Junior Member
I tried to apply changes directly in new class (without extending FXCanvasEx) and I got what I wanted Smile

Thanks a lot guys.

import java.lang.reflect.Method;

import org.eclipse.gef4.common.reflect.ReflectionUtils;
import org.eclipse.gef4.fx.swt.canvas.FXCanvasEx;
import org.eclipse.gef4.fx.swt.gestures.SWT2FXEventConverter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;

import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.embed.swt.FXCanvas;
import javafx.embed.swt.SWTFXUtils;
import javafx.event.Event;
import javafx.event.EventDispatchChain;
import javafx.event.EventDispatcher;
import javafx.scene.Cursor;
import javafx.scene.ImageCursor;
import javafx.scene.Scene;
import javafx.stage.Window;

public class FXCanvasExEx extends FXCanvas
{
	private final class RedrawingEventDispatcher implements EventDispatcher
	{
		private static final int REDRAW_INTERVAL_MILLIS = 40; // i.e. 25 fps
		private EventDispatcher delegate;
		private long lastRedrawMillis = System.currentTimeMillis();

		protected RedrawingEventDispatcher(final EventDispatcher delegate)
		{
			this.delegate = delegate;
		}

		@Override
		public Event dispatchEvent(final Event event, final EventDispatchChain tail)
		{
			// dispatch the most recent event
			final Event returnedEvent = delegate.dispatchEvent(event, tail);
			// update UI
			final long millisNow = System.currentTimeMillis();
			if (millisNow - lastRedrawMillis > REDRAW_INTERVAL_MILLIS)
			{
				redraw();
				lastRedrawMillis = millisNow;
			}
			// return dispatched event
			return returnedEvent;
		}

		protected EventDispatcher dispose()
		{
			final EventDispatcher d = delegate;
			delegate = null;
			return d;
		}
	}

	private ChangeListener<Cursor> cursorChangeListener = new ChangeListener<Cursor>()
	{
		@Override
		public void changed(final ObservableValue<? extends Cursor> observable, final Cursor oldCursor, final Cursor newCursor)
		{
			// XXX: SWTCursors does support image cursors yet
			// (https://bugs.openjdk.java.net/browse/JDK-8088147); we compensate
			// this here (using JDK-internal API)
			if (newCursor instanceof ImageCursor)
			{
				// custom cursor, convert image
				final ImageData imageData = SWTFXUtils.fromFXImage(((ImageCursor) newCursor).getImage(), null);
				final double hotspotX = ((ImageCursor) newCursor).getHotspotX();
				final double hotspotY = ((ImageCursor) newCursor).getHotspotY();
				final org.eclipse.swt.graphics.Cursor swtCursor = new org.eclipse.swt.graphics.Cursor(getDisplay(), imageData, (int) hotspotX, (int) hotspotY);
				// FIXME [JDK-internal]: Set platform cursor on CursorFrame so
				// that it can be retrieved by FXCanvas' HostContainer (which
				// ultimately sets the cursor on the FXCanvas); unfortunately,
				// this is not possible using public API.
				try
				{
					final Method currentCursorFrameAccessor = Cursor.class.getDeclaredMethod("getCurrentFrame", new Class[] {});
					currentCursorFrameAccessor.setAccessible(true);
					final Object currentCursorFrame = currentCursorFrameAccessor.invoke(newCursor, new Object[] {});
					// there is a spelling-mistake in the internal API
					// (setPlatformCursor -> setPlatforCursor)
					final Method platformCursorProvider = currentCursorFrame.getClass().getMethod("setPlatforCursor", new Class[] { Class.class, Object.class });
					platformCursorProvider.setAccessible(true);
					platformCursorProvider.invoke(currentCursorFrame, org.eclipse.swt.graphics.Cursor.class, swtCursor);
				}
				catch (final Exception e)
				{
					System.err.println("Failed to set platform cursor on the current cursor frame.");
					e.printStackTrace();
				}
			}
		}
	};

	private SWT2FXEventConverter gestureConverter = null;
	private TraverseListener traverseListener = null;
	private DisposeListener disposeListener;

	/**
	 * Creates a new {@link FXCanvasEx} for the given parent and with the given
	 * style.
	 *
	 * @param parent
	 *            The {@link Composite} to use as parent.
	 * @param style
	 *            A combination of SWT styles to be applied. Note that the
	 *            {@link FXCanvas} constructor will set the
	 *            {@link SWT#NO_BACKGROUND} style before passing it to the
	 *            {@link Canvas} constructor.
	 */
	public FXCanvasExEx(final Composite parent, final int style)
	{
		super(parent, style);

		// XXX: As FXCanvas uses a dispose listener, we have to use the same
		// mechanism here
		disposeListener = new DisposeListener()
		{
			@Override
			public void widgetDisposed(final DisposeEvent de)
			{
				// XXX: unset the scene, so event dispatcher and cursor listener
				// are properly removed;
				// XXX: The super class will also unset the stage as a result of
				// unsetting the scene. The stagePeer will be unset through
				// the host container when the stage is set invisible (which
				// already happens through the dispose listener of the super
				// class). The embedded scene (scenePeer) will be unset through
				// the host container when unsetting the scene above;
				setScene(null);
				cursorChangeListener = null;

				removeDisposeListener(disposeListener);
				disposeListener = null;

				removeTraverseListener(traverseListener);
				traverseListener = null;

				gestureConverter.dispose();
				gestureConverter = null;
			}
		};
		addDisposeListener(disposeListener);

		// create traverse
		traverseListener = new TraverseListener()
		{
			@Override
			public void keyTraversed(final TraverseEvent e)
			{
				if ((e.detail == SWT.TRAVERSE_TAB_NEXT || e.detail == SWT.TRAVERSE_TAB_PREVIOUS) && (e.stateMask & SWT.CTRL) != 0)
				{
					e.doit = true;
				}
			}
		};
		addTraverseListener(traverseListener);

		gestureConverter = new SWT2FXEventConverter(this);
	}

	@Override
	public void dispose()
	{
		// TODO: remove (logic has been put into dispose listener)
		super.dispose();
	}

	/**
	 * Returns the stage {@link Window} hold by this {@link FXCanvas}.
	 *
	 * @return The stage {@link Window}.
	 */
	public Window getStage()
	{
		return ReflectionUtils.getPrivateFieldValue(this, "stage");
	}

	@Override
	public void setScene(final Scene newScene)
	{
		final Scene oldScene = getScene();
		if (oldScene != null)
		{
			// restore original event dispatcher
			final EventDispatcher eventDispatcher = oldScene.getEventDispatcher();
			if (eventDispatcher instanceof RedrawingEventDispatcher)
			{
				oldScene.setEventDispatcher(((RedrawingEventDispatcher) eventDispatcher).dispose());
				oldScene.cursorProperty().removeListener(cursorChangeListener);
			}
		}
		super.setScene(newScene);
		if (newScene != null)
		{
			// wrap event dispatcher
			newScene.setEventDispatcher(new RedrawingEventDispatcher(newScene.getEventDispatcher()));
			newScene.cursorProperty().addListener(cursorChangeListener);
		}
	}
}


Regards,
Miroslav
Previous Topic:SpaceTreeLayoutAlgorithm
Next Topic:Context buttons
Goto Forum:
  


Current Time: Sat Sep 22 17:27:15 GMT 2018

Powered by FUDForum. Page generated in 0.01723 seconds
.:: Contact :: Home ::.

Powered by: FUDforum 3.0.2.
Copyright ©2001-2010 FUDforum Bulletin Board Software

Back to the top