Here are the Stacktraces of blocked threads:
Name: LWJGL Renderer Thread
State: WAITING on java.util.concurrent.Semaphore$NonfairSync@326a251b
Total blocked: 8 Total waited: 21
Stack trace:
sun.misc.Unsafe.park(Native Method)
java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:997)
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1304)
java.util.concurrent.Semaphore.acquire(Semaphore.java:312)
de.visiongamestudios.gui.JmeFxContainer.handleResize(JmeFxContainer.java:216)
de.visiongamestudios.gui.JmeFxContainer.access$0(JmeFxContainer.java:213)
de.visiongamestudios.gui.JmeFxContainer$2.updateLogicalState(JmeFxContainer.java:175)
com.jme3.scene.Node.updateLogicalState(Node.java:152)
com.jme3.app.SimpleApplication.update(SimpleApplication.java:245)
com.jme3.system.lwjgl.LwjglAbstractDisplay.runLoop(LwjglAbstractDisplay.java:151)
com.jme3.system.lwjgl.LwjglDisplay.runLoop(LwjglDisplay.java:185)
com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:228)
java.lang.Thread.run(Thread.java:744)
Name: QuantumRenderer-0
State: WAITING on java.util.concurrent.Semaphore$NonfairSync@326a251b
Total blocked: 10 Total waited: 9
Stack trace:
sun.misc.Unsafe.park(Native Method)
java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:997)
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1304)
java.util.concurrent.Semaphore.acquire(Semaphore.java:312)
de.visiongamestudios.gui.JmeFxContainer.paintComponent(JmeFxContainer.java:324)
de.visiongamestudios.gui.JmeFxContainer$HostContainer.repaint(JmeFxContainer.java:435)
com.sun.javafx.tk.quantum.EmbeddedScene.uploadPixels(EmbeddedScene.java:142)
com.sun.javafx.tk.quantum.EmbeddedState.uploadPixels(EmbeddedState.java:54)
com.sun.javafx.tk.quantum.UploadingPainter.run(UploadingPainter.java:168)
java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
com.sun.javafx.tk.RenderJob.run(RenderJob.java:58)
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.run(QuantumRenderer.java:129)
java.lang.Thread.run(Thread.java:744)
Name: Thread-5
State: WAITING on java.lang.StringBuilder@5a7adbc2
Total blocked: 152 Total waited: 179
Stack trace:
java.lang.Object.wait(Native Method)
java.lang.Object.wait(Object.java:502)
com.sun.glass.ui.InvokeLaterDispatcher.run(InvokeLaterDispatcher.java:126)
Name: JavaFX Application Thread
State: WAITING on java.util.concurrent.locks.ReentrantLock$NonfairSync@71d7b65d owned by: QuantumRenderer-0
Total blocked: 18 Total waited: 8
Stack trace:
sun.misc.Unsafe.park(Native Method)
java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
com.sun.javafx.tk.quantum.EmbeddedScene.getPixels(EmbeddedScene.java:191)
de.visiongamestudios.gui.JmeFxContainer.paintComponent(JmeFxContainer.java:332)
de.visiongamestudios.gui.JmeFxContainer$HostContainer.repaint(JmeFxContainer.java:435)
de.visiongamestudios.gui.JmeFxContainer$3.run(JmeFxContainer.java:237)
com.sun.javafx.application.PlatformImpl$6$1.run(PlatformImpl.java:301)
com.sun.javafx.application.PlatformImpl$6$1.run(PlatformImpl.java:298)
java.security.AccessController.doPrivileged(Native Method)
com.sun.javafx.application.PlatformImpl$6.run(PlatformImpl.java:298)
com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method)
com.sun.glass.ui.gtk.GtkApplication.access$200(GtkApplication.java:48)
com.sun.glass.ui.gtk.GtkApplication$6$1.run(GtkApplication.java:149)
java.lang.Thread.run(Thread.java:744)
Looks to me like the quantum renderer blocks and the lwjgl thread manage to block each other somehow.
And for the linenumbers the current version of the jfx container I use, with the added cleanup appstate.
[java]
package de.visiongamestudios.gui;
import java.awt.event.KeyEvent;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.BitSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.event.EventHandler;
import javafx.scene.Camera;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.PixelFormat;
import javafx.scene.image.PixelReader;
import javafx.scene.image.WritableImage;
import javafx.scene.input.TransferMode;
import javafx.scene.paint.Color;
import javafx.stage.Window;
import javafx.stage.WindowEvent;
import javax.swing.JFrame;
import org.lwjgl.opengl.Display;
import com.jme3.app.Application;
import com.jme3.app.state.AppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.asset.AssetManager;
import com.jme3.input.RawInputListener;
import com.jme3.input.awt.AwtKeyInput;
import com.jme3.input.event.JoyAxisEvent;
import com.jme3.input.event.JoyButtonEvent;
import com.jme3.input.event.KeyInputEvent;
import com.jme3.input.event.MouseButtonEvent;
import com.jme3.input.event.MouseMotionEvent;
import com.jme3.input.event.TouchEvent;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Node;
import com.jme3.texture.Image;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture2D;
import com.jme3.ui.Picture;
import com.jme3.util.BufferUtils;
import com.sun.javafx.application.PlatformImpl;
import com.sun.javafx.cursor.CursorFrame;
import com.sun.javafx.embed.AbstractEvents;
import com.sun.javafx.embed.EmbeddedSceneDSInterface;
import com.sun.javafx.embed.EmbeddedSceneInterface;
import com.sun.javafx.embed.EmbeddedStageInterface;
import com.sun.javafx.embed.HostDragStartListener;
import com.sun.javafx.embed.HostInterface;
import com.sun.javafx.perf.PerformanceTracker;
import com.sun.javafx.scene.SceneHelper;
import com.sun.javafx.scene.SceneHelper.SceneAccessor;
import com.sun.javafx.stage.EmbeddedWindow;
import com.sun.javafx.tk.Toolkit;
/**
- Need to pass -Dprism.dirtyopts=false on startup
*/
public class JmeFxContainer implements RawInputListener {
EmbeddedStageInterface stagePeer;
EmbeddedSceneInterface scenePeer;
volatile EmbeddedWindow stage;
HostContainer hostContainer;
int pWidth;
int pHeight;
volatile Scene scene;
Picture picture;
Image jmeImage;
Texture2D tex;
ByteBuffer jmeData;
ByteBuffer fxData;
boolean fxDataReady = false;
int oldX = -1;
int oldY = -1;
boolean focus;
Application app;
boolean fullScreenSuppport;
public static JmeFxContainer install(final Application app, final Node guiNode, final boolean fullScreenSupport) {
final JmeFxContainer ctr = new JmeFxContainer(app.getAssetManager(), app, fullScreenSupport);
guiNode.attachChild(ctr.getJmeNode());
app.getInputManager().addRawInputListener(ctr);
if (fullScreenSupport) {
ctr.installSceneAccessorHack();
}
return ctr;
}
JmeFxContainer(final AssetManager assetManager, final Application app, final boolean fullScreenSupport) {
app.getStateManager().attach(new AppState() {
@Override
public void update(final float tpf) {
}
@Override
public void stateDetached(final AppStateManager stateManager) {
}
@Override
public void stateAttached(final AppStateManager stateManager) {
}
@Override
public void setEnabled(final boolean active) {
}
@Override
public void render(final RenderManager rm) {
}
@Override
public void postRender() {
}
@Override
public boolean isInitialized() {
return true;
}
@Override
public boolean isEnabled() {
return false;
}
@Override
public void initialize(final AppStateManager stateManager, final Application app) {
// TODO Auto-generated method stub
}
@Override
public void cleanup() {
Platform.exit();
}
});
this.app = app;
this.fullScreenSuppport = fullScreenSupport;
this.hostContainer = new HostContainer();
this.picture = new Picture("JavaFXContainer", true) {
@Override
public void updateLogicalState(final float tpf) {
if (JmeFxContainer.this.stagePeer != null) {
if (Display.getWidth() != JmeFxContainer.this.pWidth
|| Display.getHeight() != JmeFxContainer.this.pHeight) {
JmeFxContainer.this.handleResize();
}
final int x = Display.getX() + (Display.isFullscreen() ? 0 : 3);
final int y = Display.getY() + (Display.isFullscreen() ? 0 : 25);
if (JmeFxContainer.this.oldX != x || JmeFxContainer.this.oldY != y) {
JmeFxContainer.this.oldX = x;
JmeFxContainer.this.oldY = y;
Platform.runLater(new Runnable() {
@Override
public void run() {
JmeFxContainer.this.stagePeer.setLocation(x, y);
}
});
}
}
super.updateLogicalState(tpf);
}
};
this.picture.move(0, 0, -1);
this.picture.setPosition(0, 0);
this.handleResize();
this.tex = new Texture2D(this.jmeImage);
this.picture.setTexture(assetManager, this.tex, true);
this.initFx();
}
public void reshape(final int width, final int width2) {
this.handleResize();
this.tex = new Texture2D(this.jmeImage);
}
private void handleResize() {
try {
this.imageExchange.acquire();
this.pWidth = Display.getWidth();
this.pHeight = Display.getHeight();
this.picture.setWidth(this.pWidth);
this.picture.setHeight(this.pHeight);
this.jmeData = BufferUtils.createByteBuffer(this.pWidth * this.pHeight * 4);
this.fxData = BufferUtils.createByteBuffer(this.pWidth * this.pHeight * 4);
this.jmeImage = new Image(Format.RGBA8, this.pWidth, this.pHeight, this.jmeData);
if (this.tex != null) {
this.tex.setImage(this.jmeImage);
}
if (this.stagePeer != null) {
Platform.runLater(new Runnable() {
@Override
public void run() {
JmeFxContainer.this.stagePeer.setSize(JmeFxContainer.this.pWidth, JmeFxContainer.this.pHeight);
JmeFxContainer.this.scenePeer.setSize(JmeFxContainer.this.pWidth, JmeFxContainer.this.pHeight);
JmeFxContainer.this.hostContainer.repaint();
}
});
}
} catch (final Exception exc) {
exc.printStackTrace();
} finally {
this.imageExchange.release();
}
}
public Picture getJmeNode() {
return this.picture;
}
private void initFx() {
PlatformImpl.startup(new Runnable() {
@Override
public void run() {
// No need to do anything here
}
});
}
void setFxEnabled(final boolean enabled) {
}
public Scene getScene() {
return this.scene;
}
public void setScene(final Scene newScene) {
if (Toolkit.getToolkit().isFxUserThread()) {
this.setSceneImpl(newScene);
} else {
final CountDownLatch initLatch = new CountDownLatch(1);
Platform.runLater(new Runnable() {
@Override
public void run() {
JmeFxContainer.this.setSceneImpl(newScene);
initLatch.countDown();
}
});
try {
initLatch.await();
} catch (final InterruptedException z) {
z.printStackTrace(System.err);
}
}
}
/*
* Called on JavaFX app thread.
*/
private void setSceneImpl(final Scene newScene) {
if (this.stage != null && newScene == null) {
this.stage.hide();
this.stage = null;
}
this.scene = newScene;
if (this.stage == null && newScene != null) {
this.stage = new EmbeddedWindow(this.hostContainer);
}
if (this.stage != null) {
this.stage.setScene(newScene);
if (!this.stage.isShowing()) {
this.stage.show();
}
}
}
public Window getStage() {
return this.stage;
}
private Semaphore imageExchange = new Semaphore(1);
protected JFrame debugframe;
void paintComponent() {
if (this.scenePeer == null) {
return;
}
try {
this.imageExchange.acquire();
final ByteBuffer data = this.fxData;
data.clear();
final IntBuffer buf = data.asIntBuffer();
final long start = System.currentTimeMillis();
if (!this.scenePeer.getPixels(buf, this.pWidth, this.pHeight)) {
return;
}
if (this.fullScreenSuppport) {
for (final PopupSnapper ps : this.activeSnappers) {
ps.paint(buf, this.pWidth, this.pHeight);
}
}
data.flip();
this.fxDataReady = true;
} catch (final Exception exc) {
exc.printStackTrace();
} finally {
this.imageExchange.release();
}
this.app.enqueue(new Callable<Void>() {
@Override
public Void call() throws Exception {
final boolean updateImage = JmeFxContainer.this.imageExchange.tryAcquire();
// we update only if we can do that in nonblocking mode
// if would need to block, it means that another callable with newer data will be
// enqueued soon, so we can just ignore this repaint
if (updateImage) {
try {
if (JmeFxContainer.this.fxDataReady) {
JmeFxContainer.this.fxDataReady = false;
final ByteBuffer tmp = JmeFxContainer.this.jmeData;
JmeFxContainer.this.jmeData = JmeFxContainer.this.fxData;
JmeFxContainer.this.fxData = tmp;
}
} finally {
JmeFxContainer.this.imageExchange.release();
}
JmeFxContainer.this.jmeImage.setData(JmeFxContainer.this.jmeData);
} else {
// System.out.println("Skipping update due to contention");
}
return null;
}
});
}
private class HostContainer implements HostInterface {
@Override
public void setEmbeddedStage(final EmbeddedStageInterface embeddedStage) {
JmeFxContainer.this.stagePeer = embeddedStage;
if (JmeFxContainer.this.stagePeer == null) {
return;
}
if (JmeFxContainer.this.pWidth > 0 && JmeFxContainer.this.pHeight > 0) {
JmeFxContainer.this.stagePeer.setSize(JmeFxContainer.this.pWidth, JmeFxContainer.this.pHeight);
}
JmeFxContainer.this.stagePeer.setFocused(true, AbstractEvents.FOCUSEVENT_ACTIVATED);
}
@Override
public void setEmbeddedScene(final EmbeddedSceneInterface embeddedScene) {
JmeFxContainer.this.scenePeer = embeddedScene;
if (JmeFxContainer.this.scenePeer == null) {
return;
}
if (JmeFxContainer.this.pWidth > 0 && JmeFxContainer.this.pHeight > 0) {
JmeFxContainer.this.scenePeer.setSize(JmeFxContainer.this.pWidth, JmeFxContainer.this.pHeight);
}
JmeFxContainer.this.scenePeer.setDragStartListener(new HostDragStartListener() {
@Override
public void dragStarted(final EmbeddedSceneDSInterface dragSource, final TransferMode dragAction) {
System.out.println("Dragging");
}
});
}
@Override
public boolean requestFocus() {
System.out.println("Called requestFocus");
return true;
}
@Override
public boolean traverseFocusOut(final boolean forward) {
System.out.println("Called traverseFocusOut " + forward);
return true;
}
@Override
public void setPreferredSize(final int width, final int height) {
}
int repaintCounter = 0;
@Override
public void repaint() {
// System.out.println("Repainting from host " + (++repaintCounter) + " " + System.currentTimeMillis()%10000);
JmeFxContainer.this.paintComponent();
}
@Override
public void setEnabled(final boolean enabled) {
JmeFxContainer.this.setFxEnabled(enabled);
}
@Override
public void setCursor(final CursorFrame cursorFrame) {
// System.out.println("Called setCursor " + cursorFrame);
}
@Override
public boolean grabFocus() {
// System.out.println("Called grabFocus");
return true;
}
@Override
public void ungrabFocus() {
// System.out.println("Called ungrabFocus");
}
}
@Override
public void beginInput() {
}
@Override
public void endInput() {
}
@Override
public void onJoyAxisEvent(final JoyAxisEvent evt) {
}
@Override
public void onJoyButtonEvent(final JoyButtonEvent evt) {
}
@Override
public void onMouseMotionEvent(final MouseMotionEvent evt) {
if (this.scenePeer == null) {
return;
}
final int x = evt.getX();
final int y = this.pHeight - evt.getY();
final boolean covered = this.isCovered(x, y);
if (!covered) {
return;
} else {
evt.setConsumed();
}
// not sure if should be grabbing focus on mouse motion event
// grabFocus();
int type = AbstractEvents.MOUSEEVENT_MOVED;
int button = AbstractEvents.MOUSEEVENT_NONE_BUTTON;
final int wheelRotation = (int) Math.round(evt.getDeltaWheel() / -120.0);
if (wheelRotation != 0) {
type = AbstractEvents.MOUSEEVENT_WHEEL;
button = AbstractEvents.MOUSEEVENT_NONE_BUTTON;
} else if (this.mouseButtonState[0]) {
type = AbstractEvents.MOUSEEVENT_DRAGGED;
button = AbstractEvents.MOUSEEVENT_PRIMARY_BUTTON;
} else if (this.mouseButtonState[1]) {
type = AbstractEvents.MOUSEEVENT_DRAGGED;
button = AbstractEvents.MOUSEEVENT_SECONDARY_BUTTON;
} else if (this.mouseButtonState[2]) {
type = AbstractEvents.MOUSEEVENT_DRAGGED;
button = AbstractEvents.MOUSEEVENT_MIDDLE_BUTTON;
}
this.scenePeer.mouseEvent(type, button, this.mouseButtonState[0], this.mouseButtonState[1],
this.mouseButtonState[2], x, y, Display.getX() + x, Display.getY() + y,
this.keyStateSet.get(KeyEvent.VK_SHIFT), this.keyStateSet.get(KeyEvent.VK_CONTROL),
this.keyStateSet.get(KeyEvent.VK_ALT), this.keyStateSet.get(KeyEvent.VK_META), wheelRotation, false);
}
boolean[] mouseButtonState = new boolean[3];
@Override
public void onMouseButtonEvent(final MouseButtonEvent evt) {
// TODO: Process events in separate thread ?
if (this.scenePeer == null) {
return;
}
final int x = evt.getX();
final int y = this.pHeight - evt.getY();
int button;
switch (evt.getButtonIndex()) {
case 0:
button = AbstractEvents.MOUSEEVENT_PRIMARY_BUTTON;
break;
case 1:
button = AbstractEvents.MOUSEEVENT_SECONDARY_BUTTON;
break;
case 2:
button = AbstractEvents.MOUSEEVENT_MIDDLE_BUTTON;
break;
default:
return;
}
this.mouseButtonState[evt.getButtonIndex()] = evt.isPressed();
// seems that generating mouse release without corresponding mouse pressed is causing problems in Scene.ClickGenerator
final boolean covered = this.isCovered(x, y);
if (!covered) {
this.loseFocus();
return;
} else {
evt.setConsumed();
this.grabFocus();
}
int type;
if (evt.isPressed()) {
type = AbstractEvents.MOUSEEVENT_PRESSED;
} else if (evt.isReleased()) {
type = AbstractEvents.MOUSEEVENT_RELEASED;
// and clicked ??
} else {
return;
}
this.scenePeer.mouseEvent(type, button, this.mouseButtonState[0], this.mouseButtonState[1],
this.mouseButtonState[2], x, y, Display.getX() + x, Display.getY() + y,
this.keyStateSet.get(KeyEvent.VK_SHIFT), this.keyStateSet.get(KeyEvent.VK_CONTROL),
this.keyStateSet.get(KeyEvent.VK_ALT), this.keyStateSet.get(KeyEvent.VK_META), 0,
button == AbstractEvents.MOUSEEVENT_SECONDARY_BUTTON);
}
private boolean isCovered(final int x, final int y) {
if (x < 0 || x >= this.pWidth) {
return false;
}
if (y < 0 || y >= this.pHeight) {
return false;
}
final ByteBuffer data = this.jmeImage.getData(0);
data.limit(data.capacity());
final int alpha = data.get(3 + 4 * (y * this.pWidth + x));
data.limit(0);
return alpha != 0;
}
private void grabFocus() {
if (!this.focus && this.stagePeer != null) {
this.stagePeer.setFocused(true, AbstractEvents.FOCUSEVENT_ACTIVATED);
this.focus = true;
}
}
private void loseFocus() {
if (this.focus && this.stagePeer != null) {
this.stagePeer.setFocused(false, AbstractEvents.FOCUSEVENT_DEACTIVATED);
this.focus = false;
}
}
private char[] keyCharSet = new char[0xFF];
private BitSet keyStateSet = new BitSet(0xFF);
int retrieveKeyState() {
int embedModifiers = 0;
if (this.keyStateSet.get(KeyEvent.VK_SHIFT)) {
embedModifiers |= AbstractEvents.MODIFIER_SHIFT;
}
if (this.keyStateSet.get(KeyEvent.VK_CONTROL)) {
embedModifiers |= AbstractEvents.MODIFIER_CONTROL;
}
if (this.keyStateSet.get(KeyEvent.VK_ALT)) {
embedModifiers |= AbstractEvents.MODIFIER_ALT;
}
if (this.keyStateSet.get(KeyEvent.VK_META)) {
embedModifiers |= AbstractEvents.MODIFIER_META;
}
return embedModifiers;
}
@Override
public void onKeyEvent(final KeyInputEvent evt) {
if (this.scenePeer == null) {
return;
}
final char keyChar = evt.getKeyChar();
int fxKeycode = AwtKeyInput.convertJmeCode(evt.getKeyCode());
final int keyState = this.retrieveKeyState();
if (fxKeycode > this.keyCharSet.length) {
switch (keyChar) {
case '\\':
fxKeycode = java.awt.event.KeyEvent.VK_BACK_SLASH;
break;
default:
return;
}
}
if (this.focus) {
evt.setConsumed();
}
if (evt.isRepeating()) {
final char x = this.keyCharSet[fxKeycode];
if (this.focus) {
this.scenePeer.keyEvent(AbstractEvents.KEYEVENT_TYPED, fxKeycode, new char[] { x }, keyState);
}
} else if (evt.isPressed()) {
this.keyCharSet[fxKeycode] = keyChar;
this.keyStateSet.set(fxKeycode);
if (this.focus) {
this.scenePeer.keyEvent(AbstractEvents.KEYEVENT_PRESSED, fxKeycode, new char[] { keyChar }, keyState);
this.scenePeer.keyEvent(AbstractEvents.KEYEVENT_TYPED, fxKeycode, new char[] { keyChar }, keyState);
}
} else {
final char x = this.keyCharSet[fxKeycode];
this.keyStateSet.clear(fxKeycode);
if (this.focus) {
this.scenePeer.keyEvent(AbstractEvents.KEYEVENT_RELEASED, fxKeycode, new char[] { x }, keyState);
}
}
}
@Override
public void onTouchEvent(final TouchEvent evt) {
}
Map<Window, PopupSnapper> snappers = new IdentityHashMap<>();
List<PopupSnapper> activeSnappers = new CopyOnWriteArrayList<>();
class PopupSnapper {
private Window window;
private Scene scene;
WritableImage img;
boolean ignoreRepaint;
public PopupSnapper(final Window window, final Scene scene) {
this.window = window;
this.scene = scene;
}
public void paint(final IntBuffer buf, final int pWidth, final int pHeight) {
try {
final WritableImage img = this.img;
if (img == null) {
return;
}
synchronized (this) {
final PixelReader pr = img.getPixelReader();
final int w = (int) img.getWidth();
final int h = (int) img.getHeight();
final int[] pixels = new int[w * h];
pr.getPixels(0, 0, w, h, PixelFormat.getIntArgbInstance(), pixels, 0, w);
final int xoff = (int) (this.window.getX() - JmeFxContainer.this.oldX);
final int yoff = (int) (this.window.getY() - JmeFxContainer.this.oldY);
for (int x = 0; x < w; x++) {
for (int y = 0; y < h; y++) {
final int offset = x + xoff + (y + yoff) * pWidth;
final int old = buf.get(offset);
final int merge = JmeFxContainer.merge(old, pixels[x + y * w]);
buf.put(offset, merge);
}
}
}
} catch (final Exception exc) {
exc.printStackTrace();
}
}
public void repaint() {
try {
if (!Color.TRANSPARENT.equals(this.scene.getFill())) {
this.scene.setFill(Color.TRANSPARENT);
}
if (this.img != null) {
if (this.img.getWidth() != this.scene.getWidth() || this.img.getHeight() != this.scene.getHeight()) {
this.img = null;
}
}
synchronized (this) {
this.img = this.scene.snapshot(this.img);
}
JmeFxContainer.this.paintComponent();
} catch (final Exception exc) {
exc.printStackTrace();
}
}
public void start() {
try {
final Field trackerField = Scene.class.getDeclaredField("tracker");
trackerField.setAccessible(true);
trackerField.set(this.scene, new PerformanceTracker() {
@Override
public void frameRendered() {
super.frameRendered();
if (PopupSnapper.this.ignoreRepaint) {
PopupSnapper.this.ignoreRepaint = false;
return;
}
Platform.runLater(new Runnable() {
@Override
public void run() {
PopupSnapper.this.ignoreRepaint = true;
PopupSnapper.this.repaint();
}
});
}
@Override
public void pulse() {
super.pulse();
}
@Override
protected long nanoTime() {
return System.nanoTime();
}
@Override
public void doOutputLog() {
}
@Override
public void doLogEvent(final String s) {
}
});
} catch (final Exception exc) {
exc.printStackTrace();
}
JmeFxContainer.this.activeSnappers.add(this);
}
public void stop() {
JmeFxContainer.this.activeSnappers.remove(this);
}
}
private void installSceneAccessorHack() {
try {
final Field f = SceneHelper.class.getDeclaredField("sceneAccessor");
f.setAccessible(true);
final SceneAccessor orig = (SceneAccessor) f.get(null);
final SceneAccessor sa = new SceneAccessor() {
@Override
public void setPaused(final boolean paused) {
orig.setPaused(paused);
}
@Override
public void parentEffectiveOrientationInvalidated(final Scene scene) {
orig.parentEffectiveOrientationInvalidated(scene);
}
@Override
public Camera getEffectiveCamera(final Scene scene) {
return orig.getEffectiveCamera(scene);
}
@Override
public Scene createPopupScene(final Parent root) {
final Scene scene = orig.createPopupScene(root);
scene.windowProperty().addListener(new ChangeListener<Window>() {
@Override
public void changed(final javafx.beans.value.ObservableValue<? extends Window> observable,
final Window oldValue, final Window window) {
window.addEventHandler(WindowEvent.WINDOW_SHOWN, new EventHandler<WindowEvent>() {
@Override
public void handle(final WindowEvent event) {
if (Display.isFullscreen()) {
final PopupSnapper ps = new PopupSnapper(window, scene);
JmeFxContainer.this.snappers.put(window, ps);
ps.start();
}
}
});
};
});
scene.windowProperty().addListener(new ChangeListener<Window>() {
@Override
public void changed(final javafx.beans.value.ObservableValue<? extends Window> observable,
final Window oldValue, final Window window) {
window.addEventHandler(WindowEvent.WINDOW_HIDDEN, new EventHandler<WindowEvent>() {
@Override
public void handle(final WindowEvent event) {
if (Display.isFullscreen()) {
final PopupSnapper ps = JmeFxContainer.this.snappers.remove(window);
if (ps == null) {
System.out.println("Cannot find snapper for window " + window);
} else {
ps.stop();
}
}
}
});
};
});
return scene;
}
};
f.set(null, sa);
} catch (final Exception exc) {
exc.printStackTrace();
}
}
static int merge(final int bg, final int src) {
// TODO: optimize it
final int sa = src >>> 24;
if (sa == 0) {
return bg;
}
final int ba = bg >>> 24;
final int rb = (src & 0x00ff00ff) * sa + (bg & 0x00ff00ff) * (0xff - sa) & 0xff00ff00;
final int g = (src & 0x0000ff00) * sa + (bg & 0x0000ff00) * (0xff - sa) & 0x00ff0000;
final int a = sa + (ba * (0xff - sa) >> 8);
return a << 24 | (rb | g) >>> 8;
}
}
[/java]