diff options
author | Michael Weber <[email protected]> | 2011-11-04 17:30:26 -0700 |
---|---|---|
committer | Michael Weber <[email protected]> | 2011-11-04 17:30:26 -0700 |
commit | e7388512b69979d00e5a213a1b166a1b1726ae0c (patch) | |
tree | a2ac2149e7ffdecb864cfade4d7c5252b2024e34 | |
parent | 48bc1b090b57eb8ec3cb2f9ff33b69c9d99704d7 (diff) |
New test case that demonstates bug 523, hang on application exit if
GLContext objects are shared across GLWindow instances. This bug does
not manifest in the latest source.
-rw-r--r-- | src/test/com/jogamp/opengl/test/junit/jogl/newt/TestSharedContextNewtAWTBug523.java | 791 |
1 files changed, 791 insertions, 0 deletions
diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/newt/TestSharedContextNewtAWTBug523.java b/src/test/com/jogamp/opengl/test/junit/jogl/newt/TestSharedContextNewtAWTBug523.java new file mode 100644 index 000000000..11ca35a35 --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/jogl/newt/TestSharedContextNewtAWTBug523.java @@ -0,0 +1,791 @@ +/** + * 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.newt; + +import java.lang.reflect.*; + +import org.junit.Assert; +import org.junit.Test; + +import com.jogamp.common.nio.Buffers; +import com.jogamp.opengl.util.Animator; +import com.jogamp.opengl.util.GLBuffers; +import com.jogamp.newt.awt.NewtCanvasAWT; +import com.jogamp.newt.opengl.GLWindow; + +import com.jogamp.opengl.test.junit.util.*; + +import java.awt.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.IOException; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.util.Arrays; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import javax.media.opengl.*; +import javax.media.opengl.awt.GLCanvas; +import javax.media.opengl.glu.GLU; +import javax.swing.*; +import javax.swing.event.ChangeListener; +import javax.swing.event.ChangeEvent; + + +/** + * TestSharedContextNewtAWT.java + * + * 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 bound to a vertex array object. + * + * 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 can share buffer 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. + * + * Additionally, there is the option useSharedBufferObjects. If it is true, then only a single + * vertex buffer object and index buffer object will be allocated, and each vertex array object + * will share the underlying buffer objects, provided that context sharing is also enabled. + * + */ +public class TestSharedContextNewtAWTBug523 extends UITestCase { + + static boolean useSharedBufferObjects = true; + 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 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 static int [] sharedVertexBufferObjects = {0}; + private static int [] sharedIndexBufferObjects = {0}; + private static FloatBuffer sharedVertexBuffer; + private static IntBuffer sharedIndexBuffer; + + static { + GLProfile.initSingleton(true); + } + + // inner class that implements the event listener + static class TwoTriangles implements GLEventListener { + + // these two booleans control the four configurations of this demo app: + // - whether to use GLWindow / NewtCanvasAWT or GLCanvas + // - whether to share GLContext across the OpenGL windows + private final boolean useNewt; + private final boolean shareContext; + + private final int instanceNum; + int canvasWidth; + int canvasHeight; + private float boundsRadius = 2f; + private float viewDistance; + private float viewDistanceFactor = 1.0f; + private float xAxisRotation; + private float yAxisRotation; + private float viewFovDegrees = 15f; + + // vertex array objects cannot be shared across open gl canvases; + // + // However, display lists can be shared across GLCanvas instances (if those canvases are initialized with the same GLContext), + // including a display list created in one context that uses a VAO. + private int [] vertexArrayObjects = {0}; + + // 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 int [] privateVertexBufferObjects = {0}; + private int [] privateIndexBufferObjects = {0}; + private FloatBuffer privateVertexBuffer; + private IntBuffer privateIndexBuffer; + + TwoTriangles (int canvasWidth, int canvasHeight, boolean useNewt, boolean shareContext) { + instanceNum = instanceCounter++; + this.canvasWidth = canvasWidth; + this.canvasHeight = canvasHeight; + this.useNewt = useNewt; + this.shareContext = shareContext; + } + + public void setXAxisRotation(float xRot) { + xAxisRotation = xRot; + } + + public void setYAxisRotation(float yRot) { + yAxisRotation = yRot; + } + + public void setViewDistanceFactor(float factor) { + viewDistanceFactor = factor; + } + + + public void init(GLAutoDrawable drawable) { + + long threadId = Thread.currentThread().getId(); + + if (shareContext && drawable instanceof GLWindow) { + GLWindow glWindow = (GLWindow)drawable; + + GLDrawableFactory factory = GLDrawableFactory.getFactory(GLProfile.get(GLProfile.GL2)); + GLContext sharedContext = factory.getOrCreateSharedContext(factory.getDefaultDevice()); + + GLContext createSharedContext = glWindow.createContext(sharedContext); + drawable.setContext(createSharedContext); + + int result = createSharedContext.makeCurrent(); + if (result != GLContext.CONTEXT_CURRENT && result != GLContext.CONTEXT_CURRENT_NEW) { + return; + } + } + + 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 intialized from one thread and used on another + synchronized (this) { + gl2.glGenVertexArrays(1, vertexArrayObjects, 0); + + gl2.glBindVertexArray(vertexArrayObjects[0]); + + // use either the shared or private vertex buffers, as + int [] vertexBufferObjects; + int [] indexBufferObjects; + FloatBuffer vertexBuffer; + IntBuffer indexBuffer; + // + if (useSharedBufferObjects) { + vertexBufferObjects = sharedVertexBufferObjects; + indexBufferObjects = sharedIndexBufferObjects; + vertexBuffer = sharedVertexBuffer; + indexBuffer = sharedIndexBuffer; + } else { + vertexBufferObjects = privateVertexBufferObjects; + indexBufferObjects = privateIndexBufferObjects; + vertexBuffer = privateVertexBuffer; + indexBuffer = privateIndexBuffer; + } + + // 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) { + vertexBuffer = GLBuffers.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); + + gl2.glGenBuffers(1, vertexBufferObjects, 0); + gl2.glBindBuffer(GL2.GL_ARRAY_BUFFER, vertexBufferObjects[0]); + gl2.glBufferData(GL2.GL_ARRAY_BUFFER, vertexBuffer.capacity() * Buffers.SIZEOF_FLOAT, vertexBuffer, GL2.GL_STATIC_DRAW); + } + + // 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(GL2.GL_ARRAY_BUFFER, vertexBufferObjects[0]); + // + gl2.glEnableClientState(GL2.GL_VERTEX_ARRAY); + gl2.glVertexPointer(3, GL2.GL_FLOAT, 6 * GLBuffers.SIZEOF_FLOAT, 0); + // + gl2.glEnableClientState(GL2.GL_NORMAL_ARRAY); + gl2.glNormalPointer(GL2.GL_FLOAT, 6 * GLBuffers.SIZEOF_FLOAT, 3 * GLBuffers.SIZEOF_FLOAT); + } + + if (indexBufferObjects[0] == 0) { + indexBuffer = GLBuffers.newDirectIntBuffer(3); + indexBuffer.put(new int[]{0, 1, 2}); + indexBuffer.position(0); + + gl2.glGenBuffers(1, indexBufferObjects, 0); + gl2.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, indexBufferObjects[0]); + gl2.glBufferData(GL2.GL_ELEMENT_ARRAY_BUFFER, indexBuffer.capacity() * Buffers.SIZEOF_INT, indexBuffer, GL2.GL_STATIC_DRAW); + } + + // again, a check in the case that buffer sharing is enabled but context sharing is not enabled + if (gl2.glIsBuffer(indexBufferObjects[0])) { + gl2.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, indexBufferObjects[0]); + } + + gl2.glBindVertexArray(0); + gl2.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, 0); + gl2.glBindBuffer(GL2.GL_ARRAY_BUFFER, 0); + gl2.glDisableClientState(GL2.GL_VERTEX_ARRAY); + gl2.glDisableClientState(GL2.GL_NORMAL_ARRAY); + + initializationCounter++; + } // synchronized (this) + + + viewDistance = setupViewFrustum(gl2, canvasWidth, canvasHeight, boundsRadius, 1.0f, viewFovDegrees); + + } + + public void dispose(GLAutoDrawable drawable) { + + long threadId = Thread.currentThread().getId(); + + synchronized (this) { + initializationCounter--; + + GL2 gl2 = drawable.getGL().getGL2(); + + gl2.glDeleteVertexArrays(1, vertexArrayObjects, 0); + vertexArrayObjects[0] = 0; + logAnyErrorCodes(gl2, "display"); + + // release shared resources + if (initializationCounter == 0 || !useSharedBufferObjects) { + // use either the shared or private vertex buffers, as + int [] vertexBufferObjects; + int [] indexBufferObjects; + if (useSharedBufferObjects) { + vertexBufferObjects = sharedVertexBufferObjects; + indexBufferObjects = sharedIndexBufferObjects; + } else { + vertexBufferObjects = privateVertexBufferObjects; + indexBufferObjects = privateIndexBufferObjects; + } + + gl2.glDeleteBuffers(1, vertexBufferObjects, 0); + gl2.glDeleteBuffers(1, indexBufferObjects, 0); + vertexBufferObjects[0] = 0; + indexBufferObjects[0] = 0; + logAnyErrorCodes(gl2, "display"); + } + + // release the main thread once for each disposal + disposalCompleteSemaphore.release(); + } // synchronized (this) + } + + public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) { + } + + public void display(GLAutoDrawable drawable) { + + long threadId = Thread.currentThread().getId(); + + // 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 thems + synchronized (this) { + if (initializationCounter != 2) { + return; + } + } + + boolean isEDT = SwingUtilities.isEventDispatchThread(); + + GL2 gl2 = drawable.getGL().getGL2(); + GLU glu = new GLU(); + + // Clear the drawing area + gl2.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT); + + gl2.glViewport(0, 0, canvasWidth, canvasHeight); + gl2.glMatrixMode(GL2.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(GL2.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(GL2.GL_CULL_FACE); + gl2.glEnable(GL2.GL_DEPTH_TEST); + + // draw the triangles + drawTwoTriangles(gl2); + + gl2.glPopMatrix(); + gl2.glPopAttrib(); + + // Flush all drawing operations to the graphics card + gl2.glFlush(); + + logAnyErrorCodes(gl2, "display"); + } + + public void drawTwoTriangles(GL2 gl2) { + + // draw a red triangle the old fashioned way + gl2.glColor3f(1f, 0f, 0f); + gl2.glBegin(GL2.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(); + + // 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 intialized from one + // thread and used on another + boolean vaoBound = false; + // use either the shared or private vertex buffers, as + int [] vertexBufferObjects; + int [] indexBufferObjects; + synchronized (this) { + if (useSharedBufferObjects) { + vertexBufferObjects = sharedVertexBufferObjects; + indexBufferObjects = sharedIndexBufferObjects; + } else { + 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. + if (gl2.glIsVertexArray(vertexArrayObjects[0]) && + gl2.glIsBuffer(indexBufferObjects[0]) && gl2.glIsBuffer(vertexBufferObjects[0])) { + gl2.glBindVertexArray(vertexArrayObjects[0]); + gl2.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, indexBufferObjects[0]); + vaoBound = true; + } + + logAnyErrorCodes(gl2, "drawTwoTriangles"); + + if (vaoBound) { + gl2.glColor3f(0f, 0f, 1f); + gl2.glDrawElements(GL2.GL_TRIANGLES, 3, GL2.GL_UNSIGNED_INT, 0); + gl2.glBindVertexArray(0); + gl2.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, 0); + } + } + + public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) { + } + + } // inner class TwoTriangles + + public static void logAnyErrorCodes(GL2 gl2, String prefix) { + int glError = gl2.glGetError(); + while (glError != GL2.GL_NO_ERROR) { + String msg = prefix + ", OpenGL error: " + glError; + System.err.println(msg); + glError = gl2.glGetError(); + } + int status = gl2.glCheckFramebufferStatus(GL2.GL_FRAMEBUFFER); + if (status != GL2.GL_FRAMEBUFFER_COMPLETE) { + String msg = prefix + ", glCheckFramebufferStatus: " + status; + } + } + + /** + * 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(GL2 gl2, int width, int height, float boundsRadius, float zoomFactor, float viewFovDegrees) { + assert boundsRadius > 0.0f; + assert zoomFactor > 0.0f; + assert viewFovDegrees > 0.0f; + + 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(GL2.GL_PROJECTION); + gl2.glLoadIdentity(); + glu.gluPerspective(viewFovDegrees, aspectRatio, 0.1*viewDist, viewDist + boundRadiusAdjusted); + + return viewDist; + } + + @Test + public void testContextSharingCreateVisibleDestroy1() throws InterruptedException, InvocationTargetException { + testContextSharingCreateVisibleDestroy(false, false); + } + + @Test + public void testContextSharingCreateVisibleDestroy2() throws InterruptedException, InvocationTargetException { + testContextSharingCreateVisibleDestroy(false, true); + } + + @Test + public void testContextSharingCreateVisibleDestroy3() throws InterruptedException, InvocationTargetException { + testContextSharingCreateVisibleDestroy(true, false); + } + + @Test + public void testContextSharingCreateVisibleDestroy4() 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"); + // + GLDrawableFactory factory = GLDrawableFactory.getFactory(GLProfile.get(GLProfile.GL2)); + GLContext sharedContext = factory.getOrCreateSharedContext(factory.getDefaultDevice()); + // + GLCapabilities glCapabilities = new GLCapabilities(GLProfile.get(GLProfile.GL2)); + glCapabilities.setSampleBuffers(true); + glCapabilities.setNumSamples(4); + + final TwoTriangles eventListener1 = new TwoTriangles(640, 480, useNewt, shareContext); + final TwoTriangles eventListener2 = new TwoTriangles(320, 480, useNewt, shareContext); + + final Component openGLComponent1; + final Component openGLComponent2; + final GLAutoDrawable openGLAutoDrawable1; + final GLAutoDrawable openGLAutoDrawable2; + + if (useNewt) { + GLWindow glWindow1 = GLWindow.create(glCapabilities); + NewtCanvasAWT newtCanvasAWT1 = new NewtCanvasAWT(glWindow1); + newtCanvasAWT1.setPreferredSize(new Dimension(eventListener1.canvasWidth, eventListener1.canvasHeight)); + glWindow1.addGLEventListener(eventListener1); + // + GLWindow glWindow2 = GLWindow.create(glCapabilities); + NewtCanvasAWT newtCanvasAWT2 = new NewtCanvasAWT(glWindow2); + newtCanvasAWT2.setPreferredSize(new Dimension(eventListener2.canvasWidth, eventListener2.canvasHeight)); + glWindow2.addGLEventListener(eventListener2); + + // For GLWindow, the shared context is initialized in the GLEventListener.init method, because attempting + // to do it now triggers an exception or a + // + //if (shareContext) { + // glWindow1.setContext(sharedContext); + // glWindow2.setContext(sharedContext); + //} + + 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, sharedContext); + canvas2 = new GLCanvas(glCapabilities, sharedContext); + } 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(JSlider.VERTICAL, -180, 180, 1); + xAxisRotationSlider.setPaintTicks(false); + xAxisRotationSlider.setPaintLabels(false); + xAxisRotationSlider.setSnapToTicks(false); + xAxisRotationSlider.addChangeListener(new ChangeListener() { + + public void stateChanged(ChangeEvent e) { + eventListener1.setXAxisRotation(xAxisRotationSlider.getValue()); + eventListener2.setXAxisRotation(xAxisRotationSlider.getValue()); + } + }); + 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(JSlider.HORIZONTAL, -180, 180, 1); + yAxisRotationSlider.setPaintTicks(false); + yAxisRotationSlider.setPaintLabels(false); + yAxisRotationSlider.setSnapToTicks(false); + yAxisRotationSlider.addChangeListener(new ChangeListener() { + + public void stateChanged(ChangeEvent e) { + eventListener1.setYAxisRotation(yAxisRotationSlider.getValue()); + eventListener2.setYAxisRotation(yAxisRotationSlider.getValue()); + } + }); + 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(JSlider.HORIZONTAL, 0, 100, 10); + viewDistanceFactorSlider.setPaintTicks(false); + viewDistanceFactorSlider.setPaintLabels(false); + viewDistanceFactorSlider.setSnapToTicks(false); + viewDistanceFactorSlider.addChangeListener(new ChangeListener() { + + public void stateChanged(ChangeEvent e) { + eventListener1.setViewDistanceFactor((float) viewDistanceFactorSlider.getValue() / 10.0f); + eventListener2.setViewDistanceFactor((float) viewDistanceFactorSlider.getValue() / 10.0f); + } + }); + JLabel viewDistanceFactorLabel = new JLabel("View Distance Factor"); + + // group the view distance and label into a vertical panel + 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 + 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. + 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. + JPanel xAxisPanel = new JPanel(); + xAxisPanel.setLayout(new BoxLayout(xAxisPanel, BoxLayout.LINE_AXIS)); + xAxisPanel.add(xAxisRotationSlider); + xAxisPanel.add(xAxisRotationLabel); + + 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(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 { + boolean windowOpened = windowOpenSemaphore.tryAcquire(5000, TimeUnit.MILLISECONDS); + Assert.assertEquals(true, windowOpened); + } catch (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); + boolean windowClosed = closingSemaphore.tryAcquire(5000, TimeUnit.MILLISECONDS); + Assert.assertEquals(true, windowClosed); + } catch (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 { + boolean windowsDisposed = closingSemaphore.tryAcquire(5000, TimeUnit.MILLISECONDS); + Assert.assertEquals(true, windowsDisposed); + } catch (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 (InterruptedException e) { + System.err.println("Clean exit interrupted: " + e.getMessage()); + } + + Assert.assertEquals(true, disposalSuccesses == 2); + } + + static int atoi(String a) { + int i=0; + try { + i = Integer.parseInt(a); + } catch (Exception ex) { ex.printStackTrace(); } + return i; + } + + public static void main(String[] args) throws IOException { + + long threadId = Thread.currentThread().getId(); + + for(int i=0; i<args.length; i++) { + if(args[i].equals("-time")) { + if (++i < args.length) { + durationPerTest = atoi(args[i]); + } + } else if(args[i].equals("-useSharedBufferObjects")) { + if (++i < args.length && args[i].equals("true")) { + useSharedBufferObjects = true; + } + } + } + + String testname = TestSharedContextNewtAWTBug523.class.getName(); + org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner.main(new String[] { + testname, + "filtertrace=true", + "haltOnError=false", + "haltOnFailure=false", + "showoutput=true", + "outputtoformatters=true", + "logfailedtests=true", + "logtestlistenerevents=true", + "formatter=org.apache.tools.ant.taskdefs.optional.junit.PlainJUnitResultFormatter", + "formatter=org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter,TEST-"+testname+".xml" } ); + } + +} + |