/**
* Copyright 2011 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.acore;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import com.jogamp.opengl.GL;
import com.jogamp.opengl.GL2;
import com.jogamp.opengl.GLAutoDrawable;
import com.jogamp.opengl.GLCapabilities;
import com.jogamp.opengl.GLContext;
import com.jogamp.opengl.GLDrawableFactory;
import com.jogamp.opengl.GLEventListener;
import com.jogamp.opengl.GLOffscreenAutoDrawable;
import com.jogamp.opengl.GLProfile;
import com.jogamp.opengl.awt.GLCanvas;
import com.jogamp.opengl.fixedfunc.GLMatrixFunc;
import com.jogamp.opengl.fixedfunc.GLPointerFunc;
import com.jogamp.opengl.glu.GLU;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.FixMethodOrder;
import org.junit.runners.MethodSorters;
import com.jogamp.common.ExceptionUtils;
import com.jogamp.common.nio.Buffers;
import com.jogamp.newt.awt.NewtCanvasAWT;
import com.jogamp.newt.opengl.GLWindow;
import com.jogamp.opengl.test.junit.util.AWTRobotUtil;
import com.jogamp.opengl.test.junit.util.UITestCase;
import com.jogamp.opengl.test.junit.util.AWTRobotUtil.WindowClosingListener;
import com.jogamp.opengl.util.Animator;
/**
* TestSharedContextNewtAWTBug523
*
* Opens a single JFrame with two OpenGL windows and sliders to adjust the view orientation.
*
* Each window renders a red triangle and a blue triangle.
* The red triangle is rendered using glBegin / glVertex / glEnd.
* The blue triangle is rendered using vertex buffer objects.
*
* VAO's are not used to allow testing on OSX GL2 context!
*
* If static useNewt is true, then those windows are GLWindow objects in a NewtCanvasAWT.
* If static useNewt is false, then those windows are GLCanvas objects.
*
* If shareContext is true, then the two OpenGL windows are initialized with a shared context,
* so that they share the vertex buffer and array objects and display lists.
* If shareContext is false, then the two OpenGL windows each have their own context, and the blue
* triangle fails to render in one of the windows.
*
* The four test cases run through the four combinations of useNewt and shareContext.
*
* Similar test cases are {@link TestSharedContextListNEWT}, {@link TestSharedContextListAWT},
* {@link TestSharedContextVBOES2NEWT1} and {@link TestSharedContextVBOES1NEWT}.
*
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestSharedContextNewtAWTBug523 extends UITestCase {
static long durationPerTest = 1000;
// counters for instances of event listener TwoTriangles
// private static int instanceCounter;
private static int initializationCounter;
// This semaphore is released once each time a GLEventListener destroy method is called.
// The main thread waits twice on this semaphore to ensure both canvases have finished cleaning up.
private static Semaphore disposalCompleteSemaphore = new Semaphore(0);
// Buffer objects can be shared across shared OpenGL context.
// If we run with sharedContext, then the tests will use these shared buffer objects,
// otherwise each event listener allocates its own buffer objects.
private static AtomicInteger sharedVertexBufferObjects = new AtomicInteger(0);
private static AtomicInteger sharedIndexBufferObjects = new AtomicInteger(0);
@BeforeClass
public static void initClass() {
if(!GLProfile.isAvailable(GLProfile.GL2)) {
setTestSupported(false);
}
}
static private GLOffscreenAutoDrawable initShared(final GLCapabilities caps) {
final GLOffscreenAutoDrawable sharedDrawable = GLDrawableFactory.getFactory(caps.getGLProfile()).createOffscreenAutoDrawable(null, caps, null, 64, 64);
Assert.assertNotNull(sharedDrawable);
// init and render one frame, which will setup the Gears display lists
sharedDrawable.display();
final GLContext ctx = sharedDrawable.getContext();
Assert.assertNotNull("Shared drawable's ctx is null", ctx);
Assert.assertTrue("Shared drawable's ctx is not created", ctx.isCreated());
return sharedDrawable;
}
static private void releaseShared(final GLOffscreenAutoDrawable sharedDrawable) {
if(null != sharedDrawable) {
sharedDrawable.destroy();
}
}
// inner class that implements the event listener
static class TwoTriangles implements GLEventListener {
boolean useShared;
int canvasWidth;
int canvasHeight;
private static final float boundsRadius = 2f;
private float viewDistance;
private static float viewDistanceFactor = 1.0f;
private float xAxisRotation;
private float yAxisRotation;
private static final float viewFovDegrees = 15f;
// Buffer objects can be shared across canvas instances, if those canvases are initialized with the same GLContext.
// If we run with sharedBufferObjects true, then the tests will use these shared buffer objects.
// If we run with sharedBufferObjects false, then each event listener allocates its own buffer objects.
private final int [] privateVertexBufferObjects = {0};
private final int [] privateIndexBufferObjects = {0};
public static int createVertexBuffer(final GL2 gl2) {
final FloatBuffer vertexBuffer = Buffers.newDirectFloatBuffer(18);
vertexBuffer.put(new float[]{
1.0f, -0.5f, 0f, // vertex 1
0f, 0f, 1f, // normal 1
1.5f, -0.5f, 0f, // vertex 2
0f, 0f, 1f, // normal 2
1.0f, 0.5f, 0f, // vertex 3
0f, 0f, 1f // normal 3
});
vertexBuffer.position(0);
final int[] vbo = { 0 };
gl2.glGenBuffers(1, vbo, 0);
gl2.glBindBuffer(GL.GL_ARRAY_BUFFER, vbo[0]);
gl2.glBufferData(GL.GL_ARRAY_BUFFER, vertexBuffer.capacity() * Buffers.SIZEOF_FLOAT, vertexBuffer, GL.GL_STATIC_DRAW);
gl2.glBindBuffer(GL.GL_ARRAY_BUFFER, 0);
return vbo[0];
}
public static int createVertexIndexBuffer(final GL2 gl2) {
final IntBuffer indexBuffer = Buffers.newDirectIntBuffer(3);
indexBuffer.put(new int[]{0, 1, 2});
indexBuffer.position(0);
final int[] vbo = { 0 };
gl2.glGenBuffers(1, vbo, 0);
gl2.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, vbo[0]);
gl2.glBufferData(GL.GL_ELEMENT_ARRAY_BUFFER, indexBuffer.capacity() * Buffers.SIZEOF_INT, indexBuffer, GL.GL_STATIC_DRAW);
gl2.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, 0);
return vbo[0];
}
TwoTriangles (final int canvasWidth, final int canvasHeight, final boolean useShared) {
// instanceNum = instanceCounter++;
this.canvasWidth = canvasWidth;
this.canvasHeight = canvasHeight;
this.useShared = useShared;
}
public void setXAxisRotation(final float xRot) {
xAxisRotation = xRot;
}
public void setYAxisRotation(final float yRot) {
yAxisRotation = yRot;
}
public void setViewDistanceFactor(final float factor) {
viewDistanceFactor = factor;
}
public void init(final GLAutoDrawable drawable) {
final GL2 gl2 = drawable.getGL().getGL2();
System.err.println("INIT GL IS: " + gl2.getClass().getName());
// Disable VSync
gl2.setSwapInterval(0);
// Setup the drawing area and shading mode
gl2.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
// the first instance of TwoTriangles initializes the shared buffer objects;
// synchronizing to deal with potential liveness issues if the data is initialized from one thread and used on another
synchronized (this) {
// use either the shared or private vertex buffers, as
int [] vertexBufferObjects;
int [] indexBufferObjects;
//
if (useShared) {
System.err.println("Using shared VBOs on slave 0x"+Integer.toHexString(hashCode()));
privateVertexBufferObjects[0] = sharedVertexBufferObjects.get();
privateIndexBufferObjects[0] = sharedIndexBufferObjects.get();
} else {
System.err.println("Using local VBOs on slave 0x"+Integer.toHexString(hashCode()));
}
vertexBufferObjects = privateVertexBufferObjects;
indexBufferObjects = privateIndexBufferObjects;
// if buffer sharing is enabled, then the first of the two event listeners to be
// initialized will allocate the buffers, and the other will re-use the allocated one
if (vertexBufferObjects[0] == 0) {
System.err.println("Creating vertex VBO on slave 0x"+Integer.toHexString(hashCode()));
vertexBufferObjects[0] = createVertexBuffer(gl2);
if (useShared) {
sharedVertexBufferObjects.set(vertexBufferObjects[0]);
}
}
// A check in the case that buffer sharing is enabled but context sharing is not enabled -- in that
// case, the buffer objects are not shareable, and the blue triangle cannot be rendereds.
// Furthermore, although the calls to bind and draw elements do not cause an error from glGetError
// when this check is removed, true blue triangle is not rendered anyways, and more importantly,
// I found that with my system glDrawElements causes a runtime exception 50% of the time. Executing the binds
// to unshareable buffers sets up glDrawElements for unpredictable crashes -- sometimes it does, sometimes not.
if (gl2.glIsBuffer(vertexBufferObjects[0])) {
gl2.glBindBuffer(GL.GL_ARRAY_BUFFER, vertexBufferObjects[0]);
//
gl2.glEnableClientState(GLPointerFunc.GL_VERTEX_ARRAY);
gl2.glVertexPointer(3, GL.GL_FLOAT, 6 * Buffers.SIZEOF_FLOAT, 0);
//
gl2.glEnableClientState(GLPointerFunc.GL_NORMAL_ARRAY);
gl2.glNormalPointer(GL.GL_FLOAT, 6 * Buffers.SIZEOF_FLOAT, 3 * Buffers.SIZEOF_FLOAT);
} else {
System.err.println("Vertex VBO is not a buffer on slave 0x"+Integer.toHexString(hashCode()));
}
if (indexBufferObjects[0] == 0) {
System.err.println("Creating index VBO on slave 0x"+Integer.toHexString(hashCode()));
indexBufferObjects[0] = createVertexIndexBuffer(gl2);
if (useShared) {
sharedIndexBufferObjects.set(indexBufferObjects[0]);
}
}
// again, a check in the case that buffer sharing is enabled but context sharing is not enabled
if (gl2.glIsBuffer(indexBufferObjects[0])) {
gl2.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, indexBufferObjects[0]);
} else {
System.err.println("Index VBO is not a buffer on slave 0x"+Integer.toHexString(hashCode()));
}
gl2.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, 0);
gl2.glBindBuffer(GL.GL_ARRAY_BUFFER, 0);
gl2.glDisableClientState(GLPointerFunc.GL_VERTEX_ARRAY);
gl2.glDisableClientState(GLPointerFunc.GL_NORMAL_ARRAY);
initializationCounter++;
} // synchronized (this)
viewDistance = setupViewFrustum(gl2, canvasWidth, canvasHeight, boundsRadius, 1.0f, viewFovDegrees);
}
public void dispose(final GLAutoDrawable drawable) {
synchronized (this) {
initializationCounter--;
final GL2 gl2 = drawable.getGL().getGL2();
// release shared resources
if (initializationCounter == 0 || !useShared) {
// use either the shared or private vertex buffers, as
int [] vertexBufferObjects;
int [] indexBufferObjects;
if (useShared) {
privateVertexBufferObjects[0] = sharedVertexBufferObjects.get();
privateIndexBufferObjects[0] = sharedIndexBufferObjects.get();
sharedVertexBufferObjects.set(0);
sharedIndexBufferObjects.set(0);
}
vertexBufferObjects = privateVertexBufferObjects;
indexBufferObjects = privateIndexBufferObjects;
gl2.glDeleteBuffers(1, vertexBufferObjects, 0);
logAnyErrorCodes(this, gl2, "dispose.2");
gl2.glDeleteBuffers(1, indexBufferObjects, 0);
logAnyErrorCodes(this, gl2, "dispose.3");
vertexBufferObjects[0] = 0;
indexBufferObjects[0] = 0;
}
// release the main thread once for each disposal
disposalCompleteSemaphore.release();
} // synchronized (this)
}
public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) {
}
public void display(final GLAutoDrawable drawable) {
// wait until all instances are initialized before attempting to draw using the
// vertex array object, because the buffers are allocated in init and when the
// buffers are shared, we need to ensure that they are allocated before trying to use them
synchronized (this) {
if (initializationCounter != 2) {
return;
}
}
final GL2 gl2 = drawable.getGL().getGL2();
final GLU glu = new GLU();
logAnyErrorCodes(this, gl2, "display.0");
// Clear the drawing area
gl2.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
gl2.glViewport(0, 0, canvasWidth, canvasHeight);
gl2.glMatrixMode(GLMatrixFunc.GL_PROJECTION);
gl2.glLoadIdentity();
glu.gluPerspective(viewFovDegrees, (float)canvasWidth/(float)canvasHeight,
viewDistance*viewDistanceFactor-boundsRadius, viewDistance*viewDistanceFactor+boundsRadius);
// Reset the current matrix to the "identity"
gl2.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
gl2.glLoadIdentity();
// draw the scene
gl2.glPushAttrib(GL2.GL_ALL_ATTRIB_BITS);
gl2.glPushMatrix();
glu.gluLookAt(0, 0, 0 + viewDistance*viewDistanceFactor, 0, 0, 0, 0, 1, 0);
gl2.glRotatef(xAxisRotation, 1, 0, 0);
gl2.glRotatef(yAxisRotation, 0, 1, 0);
gl2.glDisable(GL.GL_CULL_FACE);
gl2.glEnable(GL.GL_DEPTH_TEST);
logAnyErrorCodes(this, gl2, "display.1");
// draw the triangles
drawTwoTriangles(gl2);
gl2.glPopMatrix();
gl2.glPopAttrib();
// Flush all drawing operations to the graphics card
gl2.glFlush();
logAnyErrorCodes(this, gl2, "display.X");
}
public void drawTwoTriangles(final GL2 gl2) {
// draw a red triangle the old fashioned way
gl2.glColor3f(1f, 0f, 0f);
gl2.glBegin(GL.GL_TRIANGLES);
gl2.glVertex3d(-1.5, -0.5, 0);
gl2.glNormal3d(0, 0, 1);
gl2.glVertex3d(-0.5, -0.5, 0);
gl2.glNormal3d(0, 0, 1);
gl2.glVertex3d(-0.75, 0.5, 0);
gl2.glNormal3d(0, 0, 1);
gl2.glEnd();
logAnyErrorCodes(this, gl2, "drawTwoTriangles.1");
// draw the blue triangle using a vertex array object, sharing the vertex and index buffer objects across
// contexts; if context sharing is not initialized, then one window will simply have to live without a blue triangle
//
// synchronizing to deal with potential liveness issues if the data is initialized from one
// thread and used on another
boolean vboBound = false;
// use either the shared or private vertex buffers, as
int [] vertexBufferObjects;
int [] indexBufferObjects;
synchronized (this) {
if (useShared) {
privateVertexBufferObjects[0] = sharedVertexBufferObjects.get();
privateIndexBufferObjects[0] = sharedIndexBufferObjects.get();
}
vertexBufferObjects = privateVertexBufferObjects;
indexBufferObjects = privateIndexBufferObjects;
} // synchronized (this)
// A check in the case that buffer sharing is enabled but context sharing is not enabled -- in that
// case, the buffer objects are not shareable, and the blue triangle cannot be rendereds.
// Furthermore, although the calls to bind and draw elements do not cause an error from glGetError
// when this check is removed, true blue triangle is not rendered anyways, and more importantly,
// I found that with my system glDrawElements causes a runtime exception 50% of the time. Executing the binds
// to unshareable buffers sets up glDrawElements for unpredictable crashes -- sometimes it does, sometimes not.
final boolean isVBO1 = gl2.glIsBuffer(indexBufferObjects[0]);
final boolean isVBO2 = gl2.glIsBuffer(vertexBufferObjects[0]);
final boolean useVBO = isVBO1 && isVBO2;
if ( useVBO ) {
gl2.glBindBuffer(GL.GL_ARRAY_BUFFER, vertexBufferObjects[0]);
gl2.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, indexBufferObjects[0]);
gl2.glEnableClientState(GLPointerFunc.GL_VERTEX_ARRAY);
// gl2.glVertexPointer(3, GL2.GL_FLOAT, 6 * GLBuffers.SIZEOF_FLOAT, 0);
gl2.glEnableClientState(GLPointerFunc.GL_NORMAL_ARRAY);
// gl2.glNormalPointer(GL2.GL_FLOAT, 6 * GLBuffers.SIZEOF_FLOAT, 3 * GLBuffers.SIZEOF_FLOAT);
vboBound = true;
}
// System.err.println("XXX VBO bound "+vboBound+"[ vbo1 "+isVBO1+", vbo2 "+isVBO2+"]");
logAnyErrorCodes(this, gl2, "drawTwoTriangles.2");
if (vboBound) {
gl2.glColor3f(0f, 0f, 1f);
gl2.glDrawElements(GL.GL_TRIANGLES, 3, GL.GL_UNSIGNED_INT, 0);
gl2.glBindBuffer(GL.GL_ARRAY_BUFFER, 0);
gl2.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, 0);
gl2.glDisableClientState(GLPointerFunc.GL_VERTEX_ARRAY);
gl2.glDisableClientState(GLPointerFunc.GL_NORMAL_ARRAY);
}
logAnyErrorCodes(this, gl2, "drawTwoTriangles.3");
}
public void displayChanged(final GLAutoDrawable drawable, final boolean modeChanged, final boolean deviceChanged) {
}
} // inner class TwoTriangles
private static final Set<String> errorSet = new HashSet<String>();
public static void logAnyErrorCodes(final Object obj, final GL gl, final String prefix) {
final int glError = gl.glGetError();
if(glError != GL.GL_NO_ERROR) {
final String errStr = "GL-Error: "+prefix + " on obj 0x"+Integer.toHexString(obj.hashCode())+", OpenGL error: 0x" + Integer.toHexString(glError);
if( errorSet.add(errStr) ) {
System.err.println(errStr);
ExceptionUtils.dumpStack(System.err);
}
}
final int status = gl.glCheckFramebufferStatus(GL.GL_FRAMEBUFFER);
if (status != GL.GL_FRAMEBUFFER_COMPLETE) {
final String errStr = "GL-Error: "+prefix + " on obj 0x"+Integer.toHexString(obj.hashCode())+", glCheckFramebufferStatus: 0x" + Integer.toHexString(status);
if( errorSet.add(errStr) ) {
System.err.println(errStr);
ExceptionUtils.dumpStack(System.err);
}
}
}
/**
* Sets the OpenGL projection matrix and front and back clipping planes for
* a viewport and returns the distance the camera should be placed from
* the center of the scene's bounding sphere such that the geometry is
* centered in the view frustum.
*
* @param gl2 current OpenGL context
* @param width width of GLDrawable
* @param height height of GLDrawable
* @param boundsRadius radius of a minimal bounding sphere of objects to be
* rendered in the viewport
* @param zoomFactor affects how far away the camera is placed from the scene; changing the
* zoom from 1.0 to 0.5 would make the scene appear half the size
* @param viewFovDegrees the desired field of vision for the viewport,
* higher is more fish-eye
* @return the distance the camera should be from the center of the scenes
* bounding sphere
*/
public static float setupViewFrustum(final GL2 gl2, final int width, final int height, final float boundsRadius, final float zoomFactor, final float viewFovDegrees) {
assert boundsRadius > 0.0f;
assert zoomFactor > 0.0f;
assert viewFovDegrees > 0.0f;
final GLU glu = new GLU();
final float aspectRatio = (float) width / (float) height;
final float boundRadiusAdjusted = boundsRadius / zoomFactor;
final float lowestFov = aspectRatio > 1.0f ? viewFovDegrees : aspectRatio * viewFovDegrees;
final float viewDist = (float) (boundRadiusAdjusted / Math.sin( (lowestFov / 2.0) * (Math.PI / 180.0) ));
gl2.glMatrixMode(GLMatrixFunc.GL_PROJECTION);
gl2.glLoadIdentity();
glu.gluPerspective(viewFovDegrees, aspectRatio, 0.1*viewDist, viewDist + boundRadiusAdjusted);
return viewDist;
}
@Test
public void test01UseAWTNotShared() throws InterruptedException, InvocationTargetException {
testContextSharingCreateVisibleDestroy(false, false);
}
@Test
public void test02UseAWTSharedContext() throws InterruptedException, InvocationTargetException {
testContextSharingCreateVisibleDestroy(false, true);
}
@Test
public void test10UseNEWTNotShared() throws InterruptedException, InvocationTargetException {
testContextSharingCreateVisibleDestroy(true, false);
}
@Test
public void test11UseNEWTSharedContext() throws InterruptedException, InvocationTargetException {
testContextSharingCreateVisibleDestroy(true, true);
}
/**
* Assemble the user interface and start the animator.
* It waits until the window is closed an then attempts orderly shutdown and resource deallocation.
*/
public void testContextSharingCreateVisibleDestroy(final boolean useNewt, final boolean shareContext) throws InterruptedException, InvocationTargetException {
final JFrame frame = new JFrame("Simple JOGL App for testing context sharing");
final WindowClosingListener awtClosingListener = AWTRobotUtil.addClosingListener(frame);
//
// GLDrawableFactory factory = GLDrawableFactory.getFactory(GLProfile.get(GLProfile.GL2));
// GLContext sharedContext = factory.getOrCreateSharedContext(factory.getDefaultDevice());
//
final GLCapabilities glCapabilities = new GLCapabilities(GLProfile.get(GLProfile.GL2));
glCapabilities.setSampleBuffers(true);
glCapabilities.setNumSamples(4);
final GLOffscreenAutoDrawable sharedDrawable;
if(shareContext) {
sharedDrawable = initShared(glCapabilities);
} else {
sharedDrawable = null;
}
final TwoTriangles eventListener1 = new TwoTriangles(640, 480, shareContext);
final TwoTriangles eventListener2 = new TwoTriangles(320, 480, shareContext);
final Component openGLComponent1;
final Component openGLComponent2;
final GLAutoDrawable openGLAutoDrawable1;
final GLAutoDrawable openGLAutoDrawable2;
if (useNewt) {
final GLWindow glWindow1 = GLWindow.create(glCapabilities);
if(shareContext) {
glWindow1.setSharedAutoDrawable(sharedDrawable);
}
final NewtCanvasAWT newtCanvasAWT1 = new NewtCanvasAWT(glWindow1);
newtCanvasAWT1.setPreferredSize(new Dimension(eventListener1.canvasWidth, eventListener1.canvasHeight));
glWindow1.addGLEventListener(eventListener1);
//
final GLWindow glWindow2 = GLWindow.create(glCapabilities);
if(shareContext) {
glWindow2.setSharedAutoDrawable(sharedDrawable);
}
final NewtCanvasAWT newtCanvasAWT2 = new NewtCanvasAWT(glWindow2);
newtCanvasAWT2.setPreferredSize(new Dimension(eventListener2.canvasWidth, eventListener2.canvasHeight));
glWindow2.addGLEventListener(eventListener2);
openGLComponent1 = newtCanvasAWT1;
openGLComponent2 = newtCanvasAWT2;
openGLAutoDrawable1 = glWindow1;
openGLAutoDrawable2 = glWindow2;
} else {
// Implementation using two GLCanvas instances; for GLCanvas context sharing to work, you must pass it in
// through the constructor; if you set it after it has no effect -- it does no harm if you initialized the ctor
// with the shared context, but if you didn't, it also doesn't trigger sharing
final GLCanvas canvas1;
final GLCanvas canvas2;
if (shareContext) {
canvas1 = new GLCanvas(glCapabilities);
canvas1.setSharedAutoDrawable(sharedDrawable);
canvas2 = new GLCanvas(glCapabilities);
canvas2.setSharedAutoDrawable(sharedDrawable);
} else {
canvas1 = new GLCanvas(glCapabilities);
canvas2 = new GLCanvas(glCapabilities);
}
canvas1.setSize(eventListener1.canvasWidth, eventListener1.canvasHeight);
canvas1.addGLEventListener(eventListener1);
//
canvas2.setSize(eventListener2.canvasWidth, eventListener2.canvasHeight);
canvas2.addGLEventListener(eventListener2);
openGLComponent1 = canvas1;
openGLComponent2 = canvas2;
openGLAutoDrawable1 = canvas1;
openGLAutoDrawable2 = canvas2;
}
// Create slider for x rotation.
// The vertically oriented slider rotates around the x axis
final JSlider xAxisRotationSlider = new JSlider(SwingConstants.VERTICAL, -180, 180, 1);
xAxisRotationSlider.setPaintTicks(false);
xAxisRotationSlider.setPaintLabels(false);
xAxisRotationSlider.setSnapToTicks(false);
xAxisRotationSlider.addChangeListener(new ChangeListener() {
public void stateChanged(final ChangeEvent e) {
eventListener1.setXAxisRotation(xAxisRotationSlider.getValue());
eventListener2.setXAxisRotation(xAxisRotationSlider.getValue());
}
});
final JLabel xAxisRotationLabel = new JLabel("X-Axis Rotation");
// Create slider for y rotation.
// The horizontally oriented slider rotates around the y axis
final JSlider yAxisRotationSlider = new JSlider(SwingConstants.HORIZONTAL, -180, 180, 1);
yAxisRotationSlider.setPaintTicks(false);
yAxisRotationSlider.setPaintLabels(false);
yAxisRotationSlider.setSnapToTicks(false);
yAxisRotationSlider.addChangeListener(new ChangeListener() {
public void stateChanged(final ChangeEvent e) {
eventListener1.setYAxisRotation(yAxisRotationSlider.getValue());
eventListener2.setYAxisRotation(yAxisRotationSlider.getValue());
}
});
final JLabel yAxisRotationLabel = new JLabel("Y-Axis Rotation");
// Create slider for view distance factor.
// We want a range of 0.0 to 10.0 with 0.1 increments (so we can scale down using 0.0 to 1.0).
// So, set JSlider to 0 to 100 and divide by 10.0 in stateChanged
final JSlider viewDistanceFactorSlider = new JSlider(SwingConstants.HORIZONTAL, 0, 100, 10);
viewDistanceFactorSlider.setPaintTicks(false);
viewDistanceFactorSlider.setPaintLabels(false);
viewDistanceFactorSlider.setSnapToTicks(false);
viewDistanceFactorSlider.addChangeListener(new ChangeListener() {
public void stateChanged(final ChangeEvent e) {
eventListener1.setViewDistanceFactor(viewDistanceFactorSlider.getValue() / 10.0f);
eventListener2.setViewDistanceFactor(viewDistanceFactorSlider.getValue() / 10.0f);
}
});
final JLabel viewDistanceFactorLabel = new JLabel("View Distance Factor");
// group the view distance and label into a vertical panel
final JPanel viewDistancePanel = new JPanel();
viewDistancePanel.setLayout(new BoxLayout(viewDistancePanel, BoxLayout.PAGE_AXIS));
viewDistancePanel.add(Box.createVerticalGlue());
viewDistancePanel.add(viewDistanceFactorSlider);
viewDistancePanel.add(viewDistanceFactorLabel);
viewDistancePanel.add(Box.createVerticalGlue());
// group both OpenGL canvases / windows into a horizontal panel
final JPanel openGLPanel = new JPanel();
openGLPanel.setLayout(new BoxLayout(openGLPanel, BoxLayout.LINE_AXIS));
openGLPanel.add(openGLComponent1);
openGLPanel.add(Box.createHorizontalStrut(5));
openGLPanel.add(openGLComponent2);
// group the open GL panel and the y-axis rotation slider into a vertical panel.
final JPanel canvasAndYAxisPanel = new JPanel();
canvasAndYAxisPanel.setLayout(new BoxLayout(canvasAndYAxisPanel, BoxLayout.PAGE_AXIS));
canvasAndYAxisPanel.add(openGLPanel);
canvasAndYAxisPanel.add(Box.createVerticalGlue());
canvasAndYAxisPanel.add(yAxisRotationSlider);
canvasAndYAxisPanel.add(yAxisRotationLabel);
// group the X-axis rotation slider and label into a horizontal panel.
final JPanel xAxisPanel = new JPanel();
xAxisPanel.setLayout(new BoxLayout(xAxisPanel, BoxLayout.LINE_AXIS));
xAxisPanel.add(xAxisRotationSlider);
xAxisPanel.add(xAxisRotationLabel);
final JPanel mainPanel = (JPanel) frame.getContentPane();
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.LINE_AXIS));
mainPanel.add(viewDistancePanel);
mainPanel.add(Box.createHorizontalGlue());
mainPanel.add(canvasAndYAxisPanel);
mainPanel.add(Box.createHorizontalGlue());
mainPanel.add(xAxisPanel);
final Animator animator = new Animator(Thread.currentThread().getThreadGroup());
animator.setUpdateFPSFrames(1, null);
animator.add(openGLAutoDrawable1);
animator.add(openGLAutoDrawable2);
final Semaphore windowOpenSemaphore = new Semaphore(0);
final Semaphore closingSemaphore = new Semaphore(0);
// signal the main thread when the frame is closed
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(final WindowEvent e) {
closingSemaphore.release();
}
});
// make the window visible using the EDT
SwingUtilities.invokeLater( new Runnable() {
public void run() {
frame.pack();
frame.setVisible(true);
windowOpenSemaphore.release();
}
});
// wait for the window to be visible and start the animation
try {
final boolean windowOpened = windowOpenSemaphore.tryAcquire(5000, TimeUnit.MILLISECONDS);
Assert.assertEquals(true, windowOpened);
} catch (final InterruptedException e) {
System.err.println("Closing wait interrupted: " + e.getMessage());
}
animator.start();
// sleep for test duration, then request the window to close, wait for the window to close,s and stop the animation
try {
while(animator.isAnimating() && animator.getTotalFPSDuration() < durationPerTest) {
Thread.sleep(100);
}
AWTRobotUtil.closeWindow(frame, true, awtClosingListener);
final boolean windowClosed = closingSemaphore.tryAcquire(5000, TimeUnit.MILLISECONDS);
Assert.assertEquals(true, windowClosed);
} catch (final InterruptedException e) {
System.err.println("Closing wait interrupted: " + e.getMessage());
}
animator.stop();
// ask the EDT to dispose of the frame;
// if using newt, explicitly dispose of the canvases because otherwise it seems our destroy methods are not called
SwingUtilities.invokeLater( new Runnable() {
public void run() {
frame.setVisible(false);
frame.dispose();
if (useNewt) {
((NewtCanvasAWT)openGLComponent1).destroy();
((NewtCanvasAWT)openGLComponent2).destroy();
}
closingSemaphore.release();
}
});
// wait for orderly destruction; it seems that if we share a GLContext across newt windows, bad things happen;
// I must be doing something wrong but I haven't figured it out yet
try {
final boolean windowsDisposed = closingSemaphore.tryAcquire(5000, TimeUnit.MILLISECONDS);
Assert.assertEquals(true, windowsDisposed);
} catch (final InterruptedException e) {
System.err.println("Closing wait interrupted: " + e.getMessage());
}
// ensure that the two OpenGL canvas' destroy methods completed successfully and released resources before we move on
int disposalSuccesses = 0;
try {
boolean acquired;
acquired = disposalCompleteSemaphore.tryAcquire(5000, TimeUnit.MILLISECONDS);
if (acquired){
disposalSuccesses++;
}
acquired = disposalCompleteSemaphore.tryAcquire(5000, TimeUnit.MILLISECONDS);
if (acquired){
disposalSuccesses++;
}
} catch (final InterruptedException e) {
System.err.println("Clean exit interrupted: " + e.getMessage());
}
Assert.assertEquals(true, disposalSuccesses == 2);
releaseShared(sharedDrawable);
}
static int atoi(final String a) {
int i=0;
try {
i = Integer.parseInt(a);
} catch (final Exception ex) { ex.printStackTrace(); }
return i;
}
public static void main(final String[] args) throws IOException {
for(int i=0; i<args.length; i++) {
if(args[i].equals("-time")) {
if (++i < args.length) {
durationPerTest = atoi(args[i]);
}
}
}
org.junit.runner.JUnitCore.main(TestSharedContextNewtAWTBug523.class.getName());
}
}