* AwtKeyInput now synchronized properly between OGL and EDT threads

* AwtMouseInput will no longer generate events with no mouse movement
 * Fixed issue where settings specified on AwtPanelsContext were not copied properly to the offscreen context
 * Significant performance improvements in AwtPanel system
  - Use of proper locking to prevent deadlocks
  - Optimized BufferStrategy by using a single, accelerated front buffer
  - Removed pbuffer.makeCurrent() which was not necessary and used many CPU cycles

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@8337 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
sha..rd 13 years ago
parent dcb71def31
commit fa92c00a20
  1. 47
      engine/src/desktop/com/jme3/input/awt/AwtKeyInput.java
  2. 63
      engine/src/desktop/com/jme3/input/awt/AwtMouseInput.java
  3. 120
      engine/src/desktop/com/jme3/system/awt/AwtPanel.java
  4. 3
      engine/src/desktop/com/jme3/system/awt/AwtPanelsContext.java
  5. 5
      engine/src/desktop/com/jme3/util/Screenshots.java
  6. 1
      engine/src/test/jme3test/awt/TestAwtPanels.java

@ -38,8 +38,7 @@ import com.jme3.input.event.KeyInputEvent;
import java.awt.Component; import java.awt.Component;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import java.awt.event.KeyListener; import java.awt.event.KeyListener;
import java.util.LinkedList; import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;
/** /**
@ -52,8 +51,9 @@ import java.util.logging.Logger;
public class AwtKeyInput implements KeyInput, KeyListener { public class AwtKeyInput implements KeyInput, KeyListener {
private static final Logger logger = Logger.getLogger(AwtKeyInput.class.getName()); private static final Logger logger = Logger.getLogger(AwtKeyInput.class.getName());
private final ArrayList<KeyInputEvent> eventQueue = new ArrayList<KeyInputEvent>();
private RawInputListener listener; private RawInputListener listener;
private List<KeyInputEvent> eventQueue = new LinkedList<KeyInputEvent>();
private Component component; private Component component;
public AwtKeyInput(){ public AwtKeyInput(){
@ -66,12 +66,14 @@ public class AwtKeyInput implements KeyInput, KeyListener {
} }
public void setInputSource(Component comp){ public void setInputSource(Component comp){
if (component != null){ synchronized (eventQueue){
component.removeKeyListener(this); if (component != null){
eventQueue.clear(); component.removeKeyListener(this);
eventQueue.clear();
}
component = comp;
component.addKeyListener(this);
} }
component = comp;
component.addKeyListener(this);
} }
public long getInputTimeNanos() { public long getInputTimeNanos() {
@ -83,11 +85,13 @@ public class AwtKeyInput implements KeyInput, KeyListener {
} }
public void update() { public void update() {
// flush events to listener synchronized (eventQueue){
for (int i = 0; i < eventQueue.size(); i++){ // flush events to listener
listener.onKeyEvent(eventQueue.get(i)); for (int i = 0; i < eventQueue.size(); i++){
listener.onKeyEvent(eventQueue.get(i));
}
eventQueue.clear();
} }
eventQueue.clear();
} }
public boolean isInitialized() { public boolean isInitialized() {
@ -100,25 +104,30 @@ public class AwtKeyInput implements KeyInput, KeyListener {
public void keyTyped(KeyEvent evt) { public void keyTyped(KeyEvent evt) {
// key code is zero for typed events // key code is zero for typed events
int code = 0; // int code = 0;
//int code = convertAwtKey(evt.getKeyCode()); // KeyInputEvent keyEvent = new KeyInputEvent(code, evt.getKeyChar(), false, true);
KeyInputEvent keyEvent = new KeyInputEvent(code, evt.getKeyChar(), false, true); // keyEvent.setTime(evt.getWhen());
keyEvent.setTime(evt.getWhen()); // synchronized (eventQueue){
eventQueue.add(keyEvent); // eventQueue.add(keyEvent);
// }
} }
public void keyPressed(KeyEvent evt) { public void keyPressed(KeyEvent evt) {
int code = convertAwtKey(evt.getKeyCode()); int code = convertAwtKey(evt.getKeyCode());
KeyInputEvent keyEvent = new KeyInputEvent(code, evt.getKeyChar(), true, false); KeyInputEvent keyEvent = new KeyInputEvent(code, evt.getKeyChar(), true, false);
keyEvent.setTime(evt.getWhen()); keyEvent.setTime(evt.getWhen());
eventQueue.add(keyEvent); synchronized (eventQueue){
eventQueue.add(keyEvent);
}
} }
public void keyReleased(KeyEvent evt) { public void keyReleased(KeyEvent evt) {
int code = convertAwtKey(evt.getKeyCode()); int code = convertAwtKey(evt.getKeyCode());
KeyInputEvent keyEvent = new KeyInputEvent(code, evt.getKeyChar(), false, false); KeyInputEvent keyEvent = new KeyInputEvent(code, evt.getKeyChar(), false, false);
keyEvent.setTime(evt.getWhen()); keyEvent.setTime(evt.getWhen());
eventQueue.add(keyEvent); synchronized (eventQueue){
eventQueue.add(keyEvent);
}
} }
/** /**

@ -88,6 +88,7 @@ public class AwtMouseInput implements MouseInput, MouseListener, MouseWheelListe
private Point centerLocationOnScreen; private Point centerLocationOnScreen;
private Point lastKnownLocation; private Point lastKnownLocation;
private boolean isRecentering; private boolean isRecentering;
private boolean cursorMoved;
private int eventsSinceRecenter; private int eventsSinceRecenter;
public AwtMouseInput() { public AwtMouseInput() {
@ -161,29 +162,33 @@ public class AwtMouseInput implements MouseInput, MouseListener, MouseWheelListe
} }
public void update() { public void update() {
int newX = location.x; if (cursorMoved){
int newY = location.y; int newX = location.x;
int newWheel = wheelPos; int newY = location.y;
int newWheel = wheelPos;
// invert DY
MouseMotionEvent evt = new MouseMotionEvent(newX, newY, // invert DY
newX - lastEventX, MouseMotionEvent evt = new MouseMotionEvent(newX, newY,
lastEventY - newY, newX - lastEventX,
wheelPos, lastEventWheel - wheelPos); lastEventY - newY,
listener.onMouseMotionEvent(evt); wheelPos, lastEventWheel - wheelPos);
listener.onMouseMotionEvent(evt);
lastEventX = newX;
lastEventY = newY;
lastEventWheel = newWheel;
cursorMoved = false;
}
int size = eventQueue.size(); int size = eventQueue.size();
for (int i = 0; i < size; i++){ for (int i = 0; i < size; i++){
listener.onMouseButtonEvent(eventQueue.get(i)); listener.onMouseButtonEvent(eventQueue.get(i));
} }
eventQueue.clear(); eventQueue.clear();
lastEventX = newX;
lastEventY = newY;
lastEventWheel = newWheel;
} }
private final Cursor getTransparentCursor() { private Cursor getTransparentCursor() {
if (transparentCursor == null){ if (transparentCursor == null){
BufferedImage cursorImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); BufferedImage cursorImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
cursorImage.setRGB(0, 0, 0); cursorImage.setRGB(0, 0, 0);
@ -191,10 +196,7 @@ public class AwtMouseInput implements MouseInput, MouseListener, MouseWheelListe
} }
return transparentCursor; return transparentCursor;
} }
//
// public boolean isCursorVisible() {
// return( isCursorVisible );
// }
// public void setHardwareCursor(URL file, int xHotspot, int yHotspot) { // public void setHardwareCursor(URL file, int xHotspot, int yHotspot) {
// //Create the image from the provided url // //Create the image from the provided url
// java.awt.Image cursorImage = new ImageIcon( file ).getImage( ); // java.awt.Image cursorImage = new ImageIcon( file ).getImage( );
@ -239,6 +241,7 @@ public class AwtMouseInput implements MouseInput, MouseListener, MouseWheelListe
public void mouseWheelMoved(MouseWheelEvent arg0) { public void mouseWheelMoved(MouseWheelEvent arg0) {
int dwheel = arg0.getUnitsToScroll(); int dwheel = arg0.getUnitsToScroll();
wheelPos -= dwheel; wheelPos -= dwheel;
cursorMoved = true;
} }
public void mouseDragged(MouseEvent arg0) { public void mouseDragged(MouseEvent arg0) {
@ -267,16 +270,8 @@ public class AwtMouseInput implements MouseInput, MouseListener, MouseWheelListe
} }
lastKnownLocation.x = arg0.getX(); lastKnownLocation.x = arg0.getX();
lastKnownLocation.y = arg0.getY(); lastKnownLocation.y = arg0.getY();
// MHenze (cylab) Fix Issue 35: all accesses to the swingEvents list have to be synchronized for StandardGame to work...
// TODO MHenze (cylab): Cleanup. this members seem obsolete by the newly introduced above. cursorMoved = true;
// absPoint.setLocation(location);
// if (lastPoint.x == Integer.MIN_VALUE) {
// lastPoint.setLocation(absPoint.x, absPoint.y);
// }
// lastPoint.setLocation(arg0.getPoint());
// currentDeltaPoint.x = absPoint.x - lastPoint.x;
// currentDeltaPoint.y = -(absPoint.y - lastPoint.y);
// lastPoint.setLocation(location);
} }
} }
@ -285,14 +280,10 @@ public class AwtMouseInput implements MouseInput, MouseListener, MouseWheelListe
if (robot != null) { if (robot != null) {
eventsSinceRecenter = 0; eventsSinceRecenter = 0;
isRecentering = true; isRecentering = true;
// SwingUtilities.invokeLater(new Runnable() {
// public void run() {
centerLocation.setLocation(component.getWidth()/2, component.getHeight()/2); centerLocation.setLocation(component.getWidth()/2, component.getHeight()/2);
centerLocationOnScreen.setLocation(centerLocation); centerLocationOnScreen.setLocation(centerLocation);
SwingUtilities.convertPointToScreen(centerLocationOnScreen, component); SwingUtilities.convertPointToScreen(centerLocationOnScreen, component);
robot.mouseMove(centerLocationOnScreen.x, centerLocationOnScreen.y); robot.mouseMove(centerLocationOnScreen.x, centerLocationOnScreen.y);
// }
// });
} }
} }
@ -301,13 +292,13 @@ public class AwtMouseInput implements MouseInput, MouseListener, MouseWheelListe
switch (arg0.getButton()) { switch (arg0.getButton()) {
default: default:
case MouseEvent.BUTTON1: //left case MouseEvent.BUTTON1: //left
index = 0; index = MouseInput.BUTTON_LEFT;
break; break;
case MouseEvent.BUTTON2: //middle case MouseEvent.BUTTON2: //middle
index = 2; index = MouseInput.BUTTON_MIDDLE;
break; break;
case MouseEvent.BUTTON3: //right case MouseEvent.BUTTON3: //right
index = 1; index = MouseInput.BUTTON_RIGHT;
break; break;
} }
return index; return index;

@ -8,9 +8,12 @@ import com.jme3.texture.FrameBuffer;
import com.jme3.texture.Image.Format; import com.jme3.texture.Image.Format;
import com.jme3.util.BufferUtils; import com.jme3.util.BufferUtils;
import com.jme3.util.Screenshots; import com.jme3.util.Screenshots;
import java.awt.AWTException;
import java.awt.BufferCapabilities;
import java.awt.Canvas; import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.ImageCapabilities;
import java.awt.event.ComponentAdapter; import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent; import java.awt.event.ComponentEvent;
import java.awt.geom.AffineTransform; import java.awt.geom.AffineTransform;
@ -18,10 +21,9 @@ import java.awt.image.AffineTransformOp;
import java.awt.image.BufferStrategy; import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
public class AwtPanel extends Canvas implements SceneProcessor { public class AwtPanel extends Canvas implements SceneProcessor {
@ -29,6 +31,7 @@ public class AwtPanel extends Canvas implements SceneProcessor {
private BufferedImage img; private BufferedImage img;
private FrameBuffer fb; private FrameBuffer fb;
private ByteBuffer byteBuf; private ByteBuffer byteBuf;
private IntBuffer intBuf;
private boolean activeUpdates = true; private boolean activeUpdates = true;
private RenderManager rm; private RenderManager rm;
private ArrayList<ViewPort> viewPorts = new ArrayList<ViewPort>(); private ArrayList<ViewPort> viewPorts = new ArrayList<ViewPort>();
@ -36,15 +39,13 @@ public class AwtPanel extends Canvas implements SceneProcessor {
// Visibility/drawing vars // Visibility/drawing vars
private BufferStrategy strategy; private BufferStrategy strategy;
private AffineTransformOp transformOp; private AffineTransformOp transformOp;
private CyclicBarrier visibleBarrier = new CyclicBarrier(2);
private AtomicBoolean visible = new AtomicBoolean(false); private AtomicBoolean visible = new AtomicBoolean(false);
private boolean glVisible = false;
// Reshape vars // Reshape vars
private int newWidth = 0; private int newWidth = 1;
private int newHeight = 0; private int newHeight = 1;
private CyclicBarrier reshapeBarrier = new CyclicBarrier(2);
private AtomicBoolean reshapeNeeded = new AtomicBoolean(false); private AtomicBoolean reshapeNeeded = new AtomicBoolean(false);
private final Object lock = new Object();
public AwtPanel(boolean activeUpdates){ public AwtPanel(boolean activeUpdates){
this.activeUpdates = activeUpdates; this.activeUpdates = activeUpdates;
@ -53,16 +54,15 @@ public class AwtPanel extends Canvas implements SceneProcessor {
addComponentListener(new ComponentAdapter(){ addComponentListener(new ComponentAdapter(){
@Override @Override
public void componentResized(ComponentEvent e) { public void componentResized(ComponentEvent e) {
newWidth = Math.max(getWidth(), 1); synchronized (lock){
newHeight = Math.max(getHeight(), 1); int newWidth2 = Math.max(getWidth(), 1);
reshapeNeeded.set(true); int newHeight2 = Math.max(getHeight(), 1);
if (newWidth != newWidth2 || newHeight != newHeight2){
try { newWidth = newWidth2;
reshapeBarrier.await(); newHeight = newHeight2;
} catch (InterruptedException ex) { reshapeNeeded.set(true);
ex.printStackTrace(); System.out.println("EDT: componentResized " + newWidth + ", " + newHeight);
} catch (BrokenBarrierException ex) { }
ex.printStackTrace();
} }
} }
}); });
@ -72,15 +72,9 @@ public class AwtPanel extends Canvas implements SceneProcessor {
public void addNotify(){ public void addNotify(){
super.addNotify(); super.addNotify();
try { synchronized (lock){
createBufferStrategy(2);
strategy = getBufferStrategy();
visible.set(true); visible.set(true);
visibleBarrier.await(); System.out.println("EDT: addNotify");
} catch (InterruptedException ex) {
ex.printStackTrace();
} catch (BrokenBarrierException ex) {
ex.printStackTrace();
} }
requestFocusInWindow(); requestFocusInWindow();
@ -88,45 +82,44 @@ public class AwtPanel extends Canvas implements SceneProcessor {
@Override @Override
public void removeNotify(){ public void removeNotify(){
try { synchronized (lock){
visible.set(false); visible.set(false);
visibleBarrier.await(); // strategy.dispose();
strategy = null; // strategy = null;
} catch (InterruptedException ex) { System.out.println("EDT: removeNotify");
ex.printStackTrace();
} catch (BrokenBarrierException ex) {
ex.printStackTrace();
} }
super.removeNotify(); super.removeNotify();
} }
private void checkVisibility(){ public void drawFrameInThread(){
if (visible.get() != glVisible){ if (!visible.get()){
if (strategy != null){
strategy.dispose();
strategy = null;
}
return;
}
if (strategy == null || strategy.contentsLost()){
if (strategy != null){
strategy.dispose();
}
try { try {
glVisible = visible.get(); createBufferStrategy(1, new BufferCapabilities(new ImageCapabilities(true), new ImageCapabilities(true), BufferCapabilities.FlipContents.UNDEFINED));
visibleBarrier.await(); } catch (AWTException ex) {
} catch (InterruptedException ex) {
ex.printStackTrace();
} catch (BrokenBarrierException ex) {
ex.printStackTrace(); ex.printStackTrace();
} }
strategy = getBufferStrategy();
System.out.println("OGL: BufferStrategy lost!");
} }
}
public void drawFrame(){ Graphics2D g2d;
checkVisibility();
if (!glVisible)
return;
if (strategy.contentsLost()){ synchronized (lock){
strategy.dispose(); g2d = (Graphics2D) strategy.getDrawGraphics();
createBufferStrategy(2);
strategy = getBufferStrategy();
System.out.println("BufferStrategy lost!");
} }
Graphics2D g2d = (Graphics2D) strategy.getDrawGraphics();
g2d.drawImage(img, transformOp, 0, 0); g2d.drawImage(img, transformOp, 0, 0);
g2d.dispose(); g2d.dispose();
strategy.show(); strategy.show();
@ -137,7 +130,9 @@ public class AwtPanel extends Canvas implements SceneProcessor {
} }
public void setActiveUpdates(boolean activeUpdates) { public void setActiveUpdates(boolean activeUpdates) {
this.activeUpdates = activeUpdates; synchronized (lock){
this.activeUpdates = activeUpdates;
}
} }
public void attachTo(ViewPort ... vps){ public void attachTo(ViewPort ... vps){
@ -162,11 +157,15 @@ public class AwtPanel extends Canvas implements SceneProcessor {
private void reshapeInThread(int width, int height) { private void reshapeInThread(int width, int height) {
byteBuf = BufferUtils.ensureLargeEnough(byteBuf, width * height * 4); byteBuf = BufferUtils.ensureLargeEnough(byteBuf, width * height * 4);
intBuf = byteBuf.asIntBuffer();
fb = new FrameBuffer(width, height, 1); fb = new FrameBuffer(width, height, 1);
fb.setDepthBuffer(Format.Depth); fb.setDepthBuffer(Format.Depth);
fb.setColorBuffer(Format.RGB8); fb.setColorBuffer(Format.RGB8);
img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// synchronized (lock){
// img = (BufferedImage) getGraphicsConfiguration().createCompatibleImage(width, height);
// }
AffineTransform tx = AffineTransform.getScaleInstance(1, -1); AffineTransform tx = AffineTransform.getScaleInstance(1, -1);
tx.translate(0, -img.getHeight()); tx.translate(0, -img.getHeight());
transformOp = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR); transformOp = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
@ -188,26 +187,19 @@ public class AwtPanel extends Canvas implements SceneProcessor {
} }
public void postFrame(FrameBuffer out) { public void postFrame(FrameBuffer out) {
if (out != fb) if (out != fb){
throw new IllegalStateException("Why did you change the output framebuffer?"); throw new IllegalStateException("Why did you change the output framebuffer?");
}
if (reshapeNeeded.getAndSet(false)){ if (reshapeNeeded.getAndSet(false)){
reshapeInThread(newWidth, newHeight); reshapeInThread(newWidth, newHeight);
try {
reshapeBarrier.await();
} catch (InterruptedException ex) {
ex.printStackTrace();
} catch (BrokenBarrierException ex) {
ex.printStackTrace();
}
}else if (activeUpdates){ }else if (activeUpdates){
byteBuf.clear(); byteBuf.clear();
rm.getRenderer().readFrameBuffer(fb, byteBuf); rm.getRenderer().readFrameBuffer(fb, byteBuf);
Screenshots.convertScreenShot2(byteBuf, img); Screenshots.convertScreenShot2(intBuf, img);
drawFrame(); drawFrameInThread();
}else{
checkVisibility();
} }
} }
public void reshape(ViewPort vp, int w, int h) { public void reshape(ViewPort vp, int w, int h) {

@ -138,7 +138,8 @@ public class AwtPanelsContext implements JmeContext {
} }
public void setSettings(AppSettings settings) { public void setSettings(AppSettings settings) {
settings.copyFrom(settings); this.settings.copyFrom(settings);
this.settings.setRenderer(AppSettings.LWJGL_OPENGL2);
if (actualContext != null){ if (actualContext != null){
actualContext.setSettings(settings); actualContext.setSettings(settings);
} }

@ -5,17 +5,18 @@ import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt; import java.awt.image.DataBufferInt;
import java.awt.image.WritableRaster; import java.awt.image.WritableRaster;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.IntBuffer;
public final class Screenshots { public final class Screenshots {
public static void convertScreenShot2(ByteBuffer bgraBuf, BufferedImage out){ public static void convertScreenShot2(IntBuffer bgraBuf, BufferedImage out){
WritableRaster wr = out.getRaster(); WritableRaster wr = out.getRaster();
DataBufferInt db = (DataBufferInt) wr.getDataBuffer(); DataBufferInt db = (DataBufferInt) wr.getDataBuffer();
int[] cpuArray = db.getData(); int[] cpuArray = db.getData();
bgraBuf.clear(); bgraBuf.clear();
bgraBuf.asIntBuffer().get(cpuArray); bgraBuf.get(cpuArray);
// int width = wr.getWidth(); // int width = wr.getWidth();
// int height = wr.getHeight(); // int height = wr.getHeight();

@ -49,6 +49,7 @@ public class TestAwtPanels extends SimpleApplication {
app.setShowSettings(false); app.setShowSettings(false);
AppSettings settings = new AppSettings(true); AppSettings settings = new AppSettings(true);
settings.setCustomRenderer(AwtPanelsContext.class); settings.setCustomRenderer(AwtPanelsContext.class);
settings.setFrameRate(60);
app.setSettings(settings); app.setSettings(settings);
app.start(); app.start();

Loading…
Cancel
Save