/**
* Copyright 2012 JogAmp Community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of JogAmp Community.
*/
package com.jogamp.opengl.test.junit.jogl.swt;
import java.awt.AWTException;
import java.awt.Robot;
import java.lang.reflect.InvocationTargetException;
import org.eclipse.swt.SWT ;
import org.eclipse.swt.layout.FillLayout ;
import org.eclipse.swt.widgets.Composite ;
import org.eclipse.swt.widgets.Display ;
import org.eclipse.swt.widgets.Shell ;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Test;
import org.junit.FixMethodOrder;
import org.junit.runners.MethodSorters;
import com.jogamp.opengl.GL ;
import com.jogamp.opengl.GL2 ;
import com.jogamp.opengl.GLAutoDrawable ;
import com.jogamp.opengl.GLCapabilities ;
import com.jogamp.opengl.GLEventListener ;
import com.jogamp.opengl.GLProfile;
import com.jogamp.opengl.fixedfunc.GLMatrixFunc;
import com.jogamp.common.util.InterruptSource;
import com.jogamp.common.util.InterruptedRuntimeException;
import com.jogamp.nativewindow.swt.SWTAccessor;
import com.jogamp.newt.NewtFactory;
import com.jogamp.newt.event.KeyAdapter;
import com.jogamp.newt.event.KeyEvent;
import com.jogamp.newt.opengl.GLWindow ;
import com.jogamp.newt.swt.NewtCanvasSWT ;
import com.jogamp.opengl.test.junit.util.AWTRobotUtil;
import com.jogamp.opengl.test.junit.util.MiscUtils;
import com.jogamp.opengl.test.junit.util.UITestCase;
////////////////////////////////////////////////////////////////////////////////
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestNewtCanvasSWTBug628ResizeDeadlockAWT extends UITestCase {
static int duration = 500;
static class BigFlashingX implements GLEventListener
{
float r = 0f, g = 0f, b = 0f;
public void init( final GLAutoDrawable drawable )
{
final GL2 gl = drawable.getGL().getGL2() ;
gl.glClearColor( 0.0f, 0.0f, 0.0f, 1.0f ) ;
gl.glEnable( GL.GL_LINE_SMOOTH ) ;
gl.glEnable( GL.GL_BLEND ) ;
gl.glBlendFunc( GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA ) ;
}
public void reshape( final GLAutoDrawable drawable, final int x, final int y, final int width, final int height )
{
// System.err.println( ">>>>>>>> reshape " + x + ", " + y + ", " + width + ", " +height ) ;
final GL2 gl = drawable.getGL().getGL2() ;
gl.glViewport( 0, 0, width, height ) ;
gl.glMatrixMode( GLMatrixFunc.GL_PROJECTION ) ;
gl.glLoadIdentity() ;
gl.glOrtho( -1.0, 1.0, -1.0, 1.0, -1.0, 1.0 ) ;
gl.glMatrixMode( GLMatrixFunc.GL_MODELVIEW ) ;
gl.glLoadIdentity() ;
}
public void display( final GLAutoDrawable drawable )
{
// System.err.println( ">>>> display" ) ;
final GL2 gl = drawable.getGL().getGL2() ;
// Sven: I could have been seeing things, but it seemed that if this
// glClear is in here twice it seems aggravates the problem. Not
// sure why other than it just takes longer, but this is pretty
// fast operation.
gl.glClear( GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT ) ;
gl.glClear( GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT ) ;
gl.glColor4f( r, g, b, 1.0f ) ;
gl.glBegin( GL.GL_LINES ) ;
{
gl.glVertex2f( -1.0f, 1.0f ) ;
gl.glVertex2f( 1.0f, -1.0f ) ;
gl.glVertex2f( -1.0f, -1.0f ) ;
gl.glVertex2f( 1.0f, 1.0f ) ;
}
gl.glEnd() ;
if(r<1f) {
r+=0.1f;
} else if(g<1f) {
g+=0.1f;
} else if(b<1f) {
b+=0.1f;
} else {
r = 0f;
g = 0f;
b = 0f;
}
}
public void dispose( final GLAutoDrawable drawable )
{
}
}
////////////////////////////////////////////////////////////////////////////////
static class ResizeThread extends InterruptSource.Thread {
volatile boolean shallStop = false;
private final Shell _shell ;
private int _n ;
public ResizeThread( final Shell shell )
{
_shell = shell ;
}
final Runnable resizeAction = new Runnable() {
public void run()
{
System.err.println("[R-i shallStop "+shallStop+", disposed "+_shell.isDisposed()+"]");
if( shallStop || _shell.isDisposed() ) {
return;
}
try {
if( _n % 2 == 0 ) {
_shell.setSize( 200, 200 ) ;
} else {
_shell.setSize( 400, 450 ) ;
}
} catch (final Exception e0) {
e0.printStackTrace();
Assert.assertTrue("Deadlock @ setSize: "+e0, false);
}
++_n ;
} };
public void run()
{
// The problem was originally observed by grabbing the lower right
// corner of the window and moving the mouse around rapidly e.g. in
// a circle. Eventually the UI will hang with something similar to
// the backtrace noted in the bug report.
//
// This loop simulates rapid resizing by the user by toggling
// the shell back-and-forth between two sizes.
System.err.println("[R-0 shallStop "+shallStop+", disposed "+_shell.isDisposed()+"]");
final Display display = _shell.getDisplay();
while( !shallStop && !_shell.isDisposed() )
{
try
{
System.err.println("[R-n shallStop "+shallStop+", disposed "+_shell.isDisposed()+"]");
display.asyncExec( resizeAction );
display.wake();
java.lang.Thread.sleep( 50L ) ;
} catch( final InterruptedException e ) {
throw new InterruptedRuntimeException(e);
}
}
System.err.println("*R-Exit* shallStop "+shallStop+", disposed "+_shell.isDisposed());
}
}
////////////////////////////////////////////////////////////////////////////////
static class KeyfireThread extends InterruptSource.Thread
{
volatile boolean shallStop = false;
Display _display;
Robot _robot;
int _n = 0;
public KeyfireThread(final Robot robot, final Display display)
{
super();
_robot = robot;
_display = display;
}
public void run()
{
System.err.println("[K-0]");
while( !shallStop )
{
try {
System.err.println("[K-"+_n+"]");
AWTRobotUtil.waitForIdle(_robot);
AWTRobotUtil.newtKeyPress(_n, _robot, true, KeyEvent.VK_0, 10);
AWTRobotUtil.newtKeyPress(_n, _robot, false, KeyEvent.VK_0, 0);
java.lang.Thread.sleep( 40L ) ;
_n++;
if(!_display.isDisposed()) {
_display.wake();
}
} catch( final InterruptedException e ) {
break ;
}
}
System.err.println("*K-Exit*");
}
}
////////////////////////////////////////////////////////////////////////////////
private volatile boolean shallStop = false;
static class SWT_DSC {
volatile Display display;
volatile Shell shell;
volatile Composite composite;
volatile com.jogamp.newt.Display swtNewtDisplay = null;
public void init() {
SWTAccessor.invoke(true, new Runnable() {
public void run() {
display = new Display();
Assert.assertNotNull( display );
}});
display.syncExec(new Runnable() {
public void run() {
shell = new Shell( display );
Assert.assertNotNull( shell );
shell.setLayout( new FillLayout() );
composite = new Composite( shell, SWT.NO_BACKGROUND );
composite.setLayout( new FillLayout() );
Assert.assertNotNull( composite );
}});
swtNewtDisplay = NewtFactory.createDisplay(null, false); // no-reuse
}
public void dispose() {
Assert.assertNotNull( display );
Assert.assertNotNull( shell );
Assert.assertNotNull( composite );
try {
display.syncExec(new Runnable() {
public void run() {
composite.dispose();
shell.dispose();
}});
SWTAccessor.invoke(true, new Runnable() {
public void run() {
display.dispose();
}});
}
catch( final Throwable throwable ) {
throwable.printStackTrace();
Assume.assumeNoException( throwable );
}
swtNewtDisplay = null;
display = null;
shell = null;
composite = null;
}
class WaitAction implements Runnable {
private final long sleepMS;
WaitAction(final long sleepMS) {
this.sleepMS = sleepMS;
}
public void run() {
if( !display.readAndDispatch() ) {
// blocks on linux .. display.sleep();
try {
Thread.sleep(sleepMS);
} catch (final InterruptedException e) { }
}
}
}
final WaitAction awtRobotWaitAction = new WaitAction(AWTRobotUtil.TIME_SLICE);
}
@Test
public void test() throws InterruptedException, AWTException, InvocationTargetException {
final Robot robot = new Robot();
final SWT_DSC dsc = new SWT_DSC();
dsc.init();
final GLWindow glWindow;
{
final GLProfile gl2Profile = GLProfile.get( GLProfile.GL2 ) ;
final GLCapabilities caps = new GLCapabilities( gl2Profile ) ;
final com.jogamp.newt.Screen screen = NewtFactory.createScreen(dsc.swtNewtDisplay, 0);
glWindow = GLWindow.create( screen, caps ) ;
glWindow.addGLEventListener( new BigFlashingX() ) ;
glWindow.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(final com.jogamp.newt.event.KeyEvent e) {
if( !e.isPrintableKey() || e.isAutoRepeat() ) {
return;
}
System.err.print(".");
glWindow.display();
}
});
NewtCanvasSWT.create( dsc.composite, 0, glWindow ) ;
}
dsc.display.syncExec( new Runnable() {
public void run() {
dsc.shell.setText( "NewtCanvasSWT Resize Bug Demo" ) ;
dsc.shell.setSize( 400, 450 ) ;
dsc.shell.open() ;
} } );
Assert.assertTrue("GLWindow didn't become visible natively!", AWTRobotUtil.waitForRealized(glWindow, dsc.awtRobotWaitAction, true));
AWTRobotUtil.requestFocus(robot, glWindow, false);
AWTRobotUtil.setMouseToClientLocation(robot, glWindow, 50, 50);
shallStop = false;
final ResizeThread resizer;
{
resizer = new ResizeThread( dsc.shell ) ;
resizer.start() ;
}
final KeyfireThread keyfire;
{
keyfire = new KeyfireThread( robot, dsc.display ) ;
keyfire.start() ;
}
{
final Thread t = new InterruptSource.Thread(null, new Runnable() {
@Override
public void run() {
try {
Thread.sleep(duration);
} catch (final InterruptedException e) {}
resizer.shallStop = true;
keyfire.shallStop = true;
try
{
resizer.join();
} catch( final InterruptedException e ) { }
try
{
keyfire.join();
} catch( final InterruptedException e ) { }
shallStop = true;
if( null != dsc.display && !dsc.display.isDisposed() ) {
dsc.display.wake();
}
} } );
t.setDaemon(true);
t.start();
}
try {
while( !shallStop && !dsc.display.isDisposed() ) {
dsc.display.syncExec( new Runnable() {
public void run() {
if( !dsc.display.isDisposed() && !dsc.display.readAndDispatch() && !shallStop ) {
// blocks on linux .. dsc.display.sleep();
try {
Thread.sleep(10);
} catch (final InterruptedException ie) { ie.printStackTrace(); }
}
} } );
}
} catch (final Exception e0) {
e0.printStackTrace();
Assert.assertTrue("Deadlock @ dispatch: "+e0, false);
}
// canvas is disposed implicit, due to it's disposed listener !
dsc.dispose();
}
public static void main( final String[] args ) {
for(int i=0; i<args.length; i++) {
if(args[i].equals("-time")) {
duration = MiscUtils.atoi(args[++i], duration);
}
}
System.out.println("durationPerTest: "+duration);
org.junit.runner.JUnitCore.main(TestNewtCanvasSWTBug628ResizeDeadlockAWT.class.getName());
}
}