From 9d1e7c9adca97780a5b45b135c5693cffee218fc Mon Sep 17 00:00:00 2001 From: Sven Gothel Date: Sun, 14 May 2023 05:41:22 +0200 Subject: HiDPI AWT/NEWT: Propagate AWT enforced pixelScale via setSurfaceScale() blocking native change by monitor-pixelScale (Windows, X11) --- .../jogl/acore/TestSharedContextNewtAWTBug523.java | 34 +- .../jogl/acore/TestSingleGLInJSliderNewtAWT.java | 683 +++++++++++++++++++++ 2 files changed, 705 insertions(+), 12 deletions(-) create mode 100644 src/test/com/jogamp/opengl/test/junit/jogl/acore/TestSingleGLInJSliderNewtAWT.java (limited to 'src/test/com/jogamp') diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/acore/TestSharedContextNewtAWTBug523.java b/src/test/com/jogamp/opengl/test/junit/jogl/acore/TestSharedContextNewtAWTBug523.java index 7a1d48cf0..397b3696c 100644 --- a/src/test/com/jogamp/opengl/test/junit/jogl/acore/TestSharedContextNewtAWTBug523.java +++ b/src/test/com/jogamp/opengl/test/junit/jogl/acore/TestSharedContextNewtAWTBug523.java @@ -1,5 +1,5 @@ /** - * Copyright 2011 JogAmp Community. All rights reserved. + * Copyright 2011-2023 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: @@ -86,16 +86,16 @@ import com.jogamp.opengl.util.Animator; /** * TestSharedContextNewtAWTBug523 * - * Opens a single JFrame with two OpenGL windows and sliders to adjust the view orientation. + * Opens a single JFrame with two OpenGL widgets and sliders to adjust the view orientation. * - * Each window renders a red triangle and a blue triangle. + * Each OpenGL widget 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 static useNewt is true, then those OpenGL widgets are GLWindow objects in a NewtCanvasAWT. + * If static useNewt is false, then those OpenGL widgets 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. @@ -222,6 +222,7 @@ public class TestSharedContextNewtAWTBug523 extends UITestCase { } + @Override public void init(final GLAutoDrawable drawable) { final GL2 gl2 = drawable.getGL().getGL2(); @@ -300,12 +301,9 @@ public class TestSharedContextNewtAWTBug523 extends UITestCase { initializationCounter++; } // synchronized (this) - - - viewDistance = setupViewFrustum(gl2, canvasWidth, canvasHeight, boundsRadius, 1.0f, viewFovDegrees); - } + @Override public void dispose(final GLAutoDrawable drawable) { synchronized (this) { @@ -340,9 +338,16 @@ public class TestSharedContextNewtAWTBug523 extends UITestCase { } // synchronized (this) } + @Override public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) { + System.err.println("reshape: "+canvasWidth+"x"+canvasHeight+" -> "+width+"x"+height+", [drawable pixel "+drawable.getSurfaceWidth()+"x"+drawable.getSurfaceHeight()+"]"); + canvasWidth = width; + canvasHeight = height; + final GL2 gl2 = drawable.getGL().getGL2(); + viewDistance = setupViewFrustum(gl2, canvasWidth, canvasHeight, boundsRadius, 1.0f, viewFovDegrees); } + @Override public void display(final GLAutoDrawable drawable) { // wait until all instances are initialized before attempting to draw using the @@ -553,7 +558,7 @@ public class TestSharedContextNewtAWTBug523 extends UITestCase { * 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 JFrame frame = new JFrame("JSlider with "+(shareContext?"Shared":"NonShared")+" "+(useNewt?"NEWT":"AWT")+"-OpenGL Widget"); final TestUtil.WindowClosingListener awtClosingListener = AWTRobotUtil.addClosingListener(frame); // @@ -571,8 +576,8 @@ public class TestSharedContextNewtAWTBug523 extends UITestCase { sharedDrawable = null; } - final TwoTriangles eventListener1 = new TwoTriangles(640, 480, shareContext); - final TwoTriangles eventListener2 = new TwoTriangles(320, 480, shareContext); + final TwoTriangles eventListener1 = new TwoTriangles(480, 480, shareContext); + final TwoTriangles eventListener2 = new TwoTriangles(480, 480, shareContext); final Component openGLComponent1; final Component openGLComponent2; @@ -637,6 +642,7 @@ public class TestSharedContextNewtAWTBug523 extends UITestCase { xAxisRotationSlider.setSnapToTicks(false); xAxisRotationSlider.addChangeListener(new ChangeListener() { + @Override public void stateChanged(final ChangeEvent e) { eventListener1.setXAxisRotation(xAxisRotationSlider.getValue()); eventListener2.setXAxisRotation(xAxisRotationSlider.getValue()); @@ -652,6 +658,7 @@ public class TestSharedContextNewtAWTBug523 extends UITestCase { yAxisRotationSlider.setSnapToTicks(false); yAxisRotationSlider.addChangeListener(new ChangeListener() { + @Override public void stateChanged(final ChangeEvent e) { eventListener1.setYAxisRotation(yAxisRotationSlider.getValue()); eventListener2.setYAxisRotation(yAxisRotationSlider.getValue()); @@ -668,6 +675,7 @@ public class TestSharedContextNewtAWTBug523 extends UITestCase { viewDistanceFactorSlider.setSnapToTicks(false); viewDistanceFactorSlider.addChangeListener(new ChangeListener() { + @Override public void stateChanged(final ChangeEvent e) { eventListener1.setViewDistanceFactor(viewDistanceFactorSlider.getValue() / 10.0f); eventListener2.setViewDistanceFactor(viewDistanceFactorSlider.getValue() / 10.0f); @@ -731,6 +739,7 @@ public class TestSharedContextNewtAWTBug523 extends UITestCase { // make the window visible using the EDT SwingUtilities.invokeLater( new Runnable() { + @Override public void run() { frame.pack(); frame.setVisible(true); @@ -763,6 +772,7 @@ public class TestSharedContextNewtAWTBug523 extends UITestCase { // 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() { + @Override public void run() { frame.setVisible(false); frame.dispose(); diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/acore/TestSingleGLInJSliderNewtAWT.java b/src/test/com/jogamp/opengl/test/junit/jogl/acore/TestSingleGLInJSliderNewtAWT.java new file mode 100644 index 000000000..63edbb8e4 --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/jogl/acore/TestSingleGLInJSliderNewtAWT.java @@ -0,0 +1,683 @@ +/** + * Copyright 2011-2023 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 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.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.TestUtil; +import com.jogamp.opengl.test.junit.util.UITestCase; +import com.jogamp.opengl.util.Animator; + + +/** + * TestSingleGLInJSliderNewtAWT + * + * Opens a single JFrame with one OpenGL widget and sliders to adjust the view orientation. + * + * The OpenGL widget renders a red triangle and a blue triangle. + * + * If static useNewt is true, then those OpenGL widgets are GLWindow objects in a NewtCanvasAWT. + * If static useNewt is false, then those OpenGL widgets are GLCanvas objects. + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class TestSingleGLInJSliderNewtAWT extends UITestCase { + + static long durationPerTest = 1000; + + // 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); + + @BeforeClass + public static void initClass() { + if(!GLProfile.isAvailable(GLProfile.GL2)) { + setTestSupported(false); + } + } + + // inner class that implements the event listener + static class TwoTriangles implements GLEventListener { + + 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) { + // instanceNum = instanceCounter++; + this.canvasWidth = canvasWidth; + this.canvasHeight = canvasHeight; + } + + public void setXAxisRotation(final float xRot) { + xAxisRotation = xRot; + } + + public void setYAxisRotation(final float yRot) { + yAxisRotation = yRot; + } + + public void setViewDistanceFactor(final float factor) { + viewDistanceFactor = factor; + } + + + @Override + 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 + System.err.println("Using local VBOs on slave 0x"+Integer.toHexString(hashCode())); + final int [] vertexBufferObjects = privateVertexBufferObjects; + final int [] indexBufferObjects = privateIndexBufferObjects; + + System.err.println("Creating vertex VBO on slave 0x"+Integer.toHexString(hashCode())); + vertexBufferObjects[0] = createVertexBuffer(gl2); + + // 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())); + } + + System.err.println("Creating index VBO on slave 0x"+Integer.toHexString(hashCode())); + indexBufferObjects[0] = createVertexIndexBuffer(gl2); + + // 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); + } // synchronized (this) + } + + @Override + public void dispose(final GLAutoDrawable drawable) { + + synchronized (this) { + final GL2 gl2 = drawable.getGL().getGL2(); + + // release shared resources + { + // use either the shared or private vertex buffers, as + int [] vertexBufferObjects; + int [] indexBufferObjects; + 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) + } + + @Override + public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) { + System.err.println("reshape: "+canvasWidth+"x"+canvasHeight+" -> "+width+"x"+height+", [drawable pixel "+drawable.getSurfaceWidth()+"x"+drawable.getSurfaceHeight()+"]"); + canvasWidth = width; + canvasHeight = height; + final GL2 gl2 = drawable.getGL().getGL2(); + viewDistance = setupViewFrustum(gl2, canvasWidth, canvasHeight, boundsRadius, 1.0f, viewFovDegrees); + } + + @Override + public void display(final GLAutoDrawable drawable) { + 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) { + 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 errorSet = new HashSet(); + + 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 { + testCreateVisibleDestroy(false); + } + + @Test + public void test10UseNEWTNotShared() throws InterruptedException, InvocationTargetException { + testCreateVisibleDestroy(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 testCreateVisibleDestroy(final boolean useNewt) throws InterruptedException, InvocationTargetException { + final JFrame frame = new JFrame("JSlider with "+(useNewt?"NEWT":"AWT")+"-OpenGL Widget"); + final TestUtil.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 TwoTriangles eventListener1 = new TwoTriangles(480, 480); + + final Component openGLComponent1; + final GLAutoDrawable openGLAutoDrawable1; + + if (useNewt) { + final GLWindow glWindow1 = GLWindow.create(glCapabilities); + final NewtCanvasAWT newtCanvasAWT1 = new NewtCanvasAWT(glWindow1); + newtCanvasAWT1.setPreferredSize(new Dimension(eventListener1.canvasWidth, eventListener1.canvasHeight)); + glWindow1.addGLEventListener(eventListener1); + // + openGLComponent1 = newtCanvasAWT1; + openGLAutoDrawable1 = glWindow1; + } 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; + + canvas1 = new GLCanvas(glCapabilities); + canvas1.setSize(eventListener1.canvasWidth, eventListener1.canvasHeight); + canvas1.addGLEventListener(eventListener1); + + openGLComponent1 = canvas1; + openGLAutoDrawable1 = canvas1; + } + + // 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() { + + @Override + public void stateChanged(final ChangeEvent e) { + eventListener1.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() { + + @Override + public void stateChanged(final ChangeEvent e) { + eventListener1.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() { + + @Override + public void stateChanged(final ChangeEvent e) { + eventListener1.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)); + + // 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); + + 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() { + @Override + 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, null); + 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() { + @Override + public void run() { + frame.setVisible(false); + frame.dispose(); + if (useNewt) { + ((NewtCanvasAWT)openGLComponent1).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++; + } + } catch (final InterruptedException e) { + System.err.println("Clean exit interrupted: " + e.getMessage()); + } + + Assert.assertEquals(true, disposalSuccesses == 1); + } + + 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