aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--make/scripts/tests.sh6
-rw-r--r--src/nativewindow/classes/com/jogamp/nativewindow/awt/JAWTWindow.java1
-rw-r--r--src/nativewindow/classes/jogamp/nativewindow/SurfaceScaleUtils.java30
-rw-r--r--src/newt/classes/com/jogamp/newt/awt/NewtCanvasAWT.java26
-rw-r--r--src/newt/classes/jogamp/newt/WindowImpl.java38
-rw-r--r--src/newt/classes/jogamp/newt/driver/windows/WindowDriver.java2
-rw-r--r--src/newt/classes/jogamp/newt/driver/x11/WindowDriver.java2
-rw-r--r--src/test/com/jogamp/opengl/test/junit/jogl/acore/TestSharedContextNewtAWTBug523.java34
-rw-r--r--src/test/com/jogamp/opengl/test/junit/jogl/acore/TestSingleGLInJSliderNewtAWT.java683
9 files changed, 757 insertions, 65 deletions
diff --git a/make/scripts/tests.sh b/make/scripts/tests.sh
index c2b9fea19..9c0c05b55 100644
--- a/make/scripts/tests.sh
+++ b/make/scripts/tests.sh
@@ -308,6 +308,7 @@ function jrun() {
#D_ARGS="-Dnewt.debug.Window -Dnewt.debug.Window.KeyEvent"
#D_ARGS="-Dnewt.debug.Window -Dnewt.debug.Window.MouseEvent -Dnewt.debug.Window.KeyEvent"
#D_ARGS="-Dnewt.debug.Window"
+ #D_ARGS="-Dnewt.debug.Window -Dnativewindow.debug.JAWT"
#D_ARGS="-Dnewt.debug.Window.visibility.failure.freeze"
#D_ARGS="-Xprof"
#D_ARGS="-Dnativewindow.debug=all -Djogl.debug=all -Dnewt.debug=all"
@@ -510,6 +511,8 @@ function testawtswt() {
#testnoawt com.jogamp.opengl.test.junit.jogl.glsl.TestRulerNEWT01 $*
#testnoawt com.jogamp.opengl.demos.graph.ui.UISceneDemo20 $*
#testawt com.jogamp.opengl.test.junit.jogl.awt.ManualHiDPIBufferedImage01AWT $*
+#testawt com.jogamp.opengl.test.junit.jogl.acore.TestSharedContextNewtAWTBug523 $*
+testawt com.jogamp.opengl.test.junit.jogl.acore.TestSingleGLInJSliderNewtAWT $*
#
# demos (any TK, more user driven tests)
@@ -631,6 +634,7 @@ function testawtswt() {
#testswt com.jogamp.opengl.test.junit.jogl.acore.TestSharedContextVBOES2SWT3 $*
#testawt com.jogamp.opengl.test.junit.jogl.acore.TestSharedContextWithJTabbedPaneAWT $*
#testawt com.jogamp.opengl.test.junit.jogl.acore.TestSharedExternalContextAWT $*
+#testawt com.jogamp.opengl.test.junit.jogl.acore.TestSingleGLInJSliderNewtAWT $*
#testawt com.jogamp.opengl.test.junit.jogl.acore.TestFBOAutoDrawableDeadlockAWT $*
#testawt com.jogamp.opengl.test.junit.jogl.acore.TestPBufferDeadlockAWT $*
@@ -1038,7 +1042,7 @@ function testawtswt() {
#testnoawt com.jogamp.opengl.demos.graph.ui.FontView01 $*
#testnoawt com.jogamp.opengl.test.junit.graph.TestTextRendererNEWT21 $*
-testnoawt com.jogamp.opengl.demos.av.MovieCube $*
+#testnoawt com.jogamp.opengl.demos.av.MovieCube $*
#testnoawt com.jogamp.opengl.demos.av.MovieSimple $*
#testmobile com.jogamp.opengl.test.junit.jogl.demos.es2.newt.TestGearsES2NEWT $*
diff --git a/src/nativewindow/classes/com/jogamp/nativewindow/awt/JAWTWindow.java b/src/nativewindow/classes/com/jogamp/nativewindow/awt/JAWTWindow.java
index 89ca40096..78cb58b8d 100644
--- a/src/nativewindow/classes/com/jogamp/nativewindow/awt/JAWTWindow.java
+++ b/src/nativewindow/classes/com/jogamp/nativewindow/awt/JAWTWindow.java
@@ -391,6 +391,7 @@ public abstract class JAWTWindow implements NativeWindow, OffscreenLayerSurface,
*/
public final boolean updatePixelScale(final GraphicsConfiguration gc, final boolean clearFlag) {
if( JAWTUtil.getPixelScale(gc, minPixelScale, maxPixelScale) ) {
+ // Enforce maxPixelScale as used by AWT
hasPixelScaleChanged = true;
System.arraycopy(maxPixelScale, 0, hasPixelScale, 0, 2);
if( DEBUG ) {
diff --git a/src/nativewindow/classes/jogamp/nativewindow/SurfaceScaleUtils.java b/src/nativewindow/classes/jogamp/nativewindow/SurfaceScaleUtils.java
index 8f45001f9..152b189d1 100644
--- a/src/nativewindow/classes/jogamp/nativewindow/SurfaceScaleUtils.java
+++ b/src/nativewindow/classes/jogamp/nativewindow/SurfaceScaleUtils.java
@@ -41,9 +41,29 @@ public class SurfaceScaleUtils {
private static final float EPSILON = 1.1920929E-7f; // Float.MIN_VALUE == 1.4e-45f ; double EPSILON 2.220446049250313E-16d
- private static boolean isZero(final float a) {
+ /** Returns true if `abs(a) < EPSILON`, otherwise false. */
+ public static boolean isZero(final float a) {
return Math.abs(a) < EPSILON;
}
+ /** Returns true if `isZero(f2[0]) && isZero(f2[1])`, otherwise false. */
+ public static boolean isZero(final float[] f2) {
+ return isZero(f2[0]) && isZero(f2[1]);
+ }
+
+ /** Returns true if `abs(a-b) < EPSILON`, otherwise false. */
+ public static boolean isEqual(final float a, final float b) {
+ return Math.abs(a-b) < EPSILON;
+ }
+
+ /** Returns true if `isEqual(f2[0], c) && isEqual(f2[1], c)`, otherwise false. */
+ public static boolean isEqual(final float[] f2, final float c) {
+ return isEqual(f2[0], c) && isEqual(f2[1], c);
+ }
+
+ /** Returns true if `isEqual(f2[0], g2[0]) && isEqual(f2[1], g2[1])`, otherwise false. */
+ public static boolean isEqual(final float[] f2, final float[] g2) {
+ return isEqual(f2[0], g2[0]) && isEqual(f2[1], g2[1]);
+ }
/**
* Returns integer rounded product, i.e. {@code (int) ( a * pixelScale + 0.5f )}
@@ -158,15 +178,15 @@ public class SurfaceScaleUtils {
* @return the constrained pixel-scale
*/
public static float clampPixelScale(final float pixelScale, final float minPixelScale, final float maxPixelScale) {
- if( isZero(pixelScale-ScalableSurface.IDENTITY_PIXELSCALE) ) {
+ if( isEqual(pixelScale, ScalableSurface.IDENTITY_PIXELSCALE) ) {
return ScalableSurface.IDENTITY_PIXELSCALE;
- } else if( isZero(pixelScale-ScalableSurface.AUTOMAX_PIXELSCALE) ||
+ } else if( isEqual(pixelScale, ScalableSurface.AUTOMAX_PIXELSCALE) ||
pixelScale > maxPixelScale ||
- isZero(pixelScale-maxPixelScale)
+ isEqual(pixelScale, maxPixelScale)
)
{
return maxPixelScale;
- } else if( pixelScale < minPixelScale || isZero(pixelScale-minPixelScale) )
+ } else if( pixelScale < minPixelScale || isEqual(pixelScale, minPixelScale) )
{
return minPixelScale;
} else {
diff --git a/src/newt/classes/com/jogamp/newt/awt/NewtCanvasAWT.java b/src/newt/classes/com/jogamp/newt/awt/NewtCanvasAWT.java
index 7ccab3336..783bc6857 100644
--- a/src/newt/classes/com/jogamp/newt/awt/NewtCanvasAWT.java
+++ b/src/newt/classes/com/jogamp/newt/awt/NewtCanvasAWT.java
@@ -636,23 +636,16 @@ public class NewtCanvasAWT extends java.awt.Canvas implements NativeWindowHolder
}
}
}
- private final boolean updatePixelScale(final GraphicsConfiguration gc) {
- if( jawtWindow.updatePixelScale(gc, true) ) {
+
+ /** Propagates AWT pixelScale to NEWT */
+ private final boolean updatePixelScale(final GraphicsConfiguration gc, final boolean force) {
+ if( jawtWindow.updatePixelScale(gc, false) || jawtWindow.hasPixelScaleChanged() || force ) {
+ jawtWindow.hasPixelScaleChanged(); // clear
+ final float[] hasPixelScale = jawtWindow.getCurrentSurfaceScale(new float[2]);
final Window cWin = newtChild;
final Window dWin = cWin.getDelegatedWindow();
if( dWin instanceof WindowImpl ) {
- final float[] maxPixelScale = jawtWindow.getMaximumSurfaceScale(new float[2]);
- final float[] minPixelScale = jawtWindow.getMinimumSurfaceScale(new float[2]);
- // FIXME: Bug 1373, 1374: Implement general High-DPI for even non native DPI toolkit aware platforms (Linux, Windows)
- // final float[] curPixelScale = jawtWindow.getCurrentSurfaceScale(new float[2]);
- // ((WindowImpl)dWin).pixelScaleChangeNotify(curPixelScale, minPixelScale, maxPixelScale);
- ((WindowImpl)dWin).pixelScaleChangeNotify(minPixelScale, maxPixelScale, true);
- // ((WindowImpl)dWin).sizeChangedNotify(true /* defer */, getWidth(), getHeight(), true /* force */);
- } else {
- final float[] reqPixelScale = jawtWindow.getRequestedSurfaceScale(new float[2]);
- if( jawtWindow.setSurfaceScale(reqPixelScale) ) {
- // jawtWindow.getRequestedSurfaceScale(reqPixelScale);
- }
+ ((WindowImpl)dWin).setSurfaceScale(hasPixelScale);
}
return true;
}
@@ -761,7 +754,7 @@ public class NewtCanvasAWT extends java.awt.Canvas implements NativeWindowHolder
System.err.println("NewtCanvasAWT.reshape: "+x+"/"+y+" "+width+"x"+height);
}
if( validateComponent(true) ) {
- if( !printActive && updatePixelScale(getGraphicsConfiguration()) ) {
+ if( !printActive && updatePixelScale(getGraphicsConfiguration(), false /* force */) ) {
// NOP
} else {
// newtChild.setSize(width, height);
@@ -1059,8 +1052,7 @@ public class NewtCanvasAWT extends java.awt.Canvas implements NativeWindowHolder
}
newtChild.setVisible(false);
newtChild.setSize(w, h);
- final float[] reqSurfaceScale = newtChild.getRequestedSurfaceScale(new float[2]);
- jawtWindow.setSurfaceScale(reqSurfaceScale);
+ updatePixelScale(getGraphicsConfiguration(), true /* force */); // AWT -> NEWT
newtChild.reparentWindow(jawtWindow, -1, -1, Window.REPARENT_HINT_BECOMES_VISIBLE);
newtChild.addSurfaceUpdatedListener(jawtWindow);
if( jawtWindow.isOffscreenLayerSurfaceEnabled() &&
diff --git a/src/newt/classes/jogamp/newt/WindowImpl.java b/src/newt/classes/jogamp/newt/WindowImpl.java
index c860e4c3e..8632dc5f4 100644
--- a/src/newt/classes/jogamp/newt/WindowImpl.java
+++ b/src/newt/classes/jogamp/newt/WindowImpl.java
@@ -163,6 +163,7 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer
protected final float[] maxPixelScale = new float[] { ScalableSurface.IDENTITY_PIXELSCALE, ScalableSurface.IDENTITY_PIXELSCALE };
protected final float[] hasPixelScale = new float[] { ScalableSurface.IDENTITY_PIXELSCALE, ScalableSurface.IDENTITY_PIXELSCALE };
protected final float[] reqPixelScale = new float[] { ScalableSurface.AUTOMAX_PIXELSCALE, ScalableSurface.AUTOMAX_PIXELSCALE };
+ private boolean hasSetPixelScale = false;
private volatile int[] pixelPos = new int[] { 64, 64 }; // client-area pos w/o insets in pixel units
private volatile int[] pixelSize = new int[] { 128, 128 }; // client-area size w/o insets in pixel units, default: may be overwritten by user
private volatile int[] windowPos = new int[] { 64, 64 }; // client-area pos w/o insets in window units
@@ -2695,15 +2696,20 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer
/**
* {@inheritDoc}
- * <p>
- * FIXME: Bug 1373, 1374: Implement general High-DPI for even non native DPI toolkit aware platforms (Linux, Windows)
- * </p>
*/
@Override
public boolean setSurfaceScale(final float[] pixelScale) {
+ final boolean isAuto = SurfaceScaleUtils.isEqual(pixelScale, ScalableSurface.AUTOMAX_PIXELSCALE);
+ if( DEBUG_IMPLEMENTATION ) {
+ System.err.println("WindowImpl.setPixelScale.0: has["+hasPixelScale[0]+", "+hasPixelScale[1]+"], req["+
+ reqPixelScale[0]+", "+reqPixelScale[1]+"] -> req["+
+ pixelScale[0]+", "+pixelScale[1]+"], isAuto "+isAuto+", realized "+isNativeValid());
+ }
System.arraycopy(pixelScale, 0, reqPixelScale, 0, 2);
+ hasSetPixelScale = !isAuto;
return false;
}
+ protected boolean hasSetPixelScale() { return hasSetPixelScale; }
@Override
public final float[] getRequestedSurfaceScale(final float[] result) {
@@ -2967,7 +2973,7 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer
boolean res = false;
if( DEBUG_IMPLEMENTATION ) {
System.err.println("Window.SoftPixelScale.0a: req "+reqPixelScale[0]+", has "+hasPixelScale[0]+", new "+newPixelScale[0]+" - "+getThreadName());
- // Thread.dumpStack();
+ Thread.dumpStack();
}
synchronized( scaleLock ) {
try {
@@ -5245,30 +5251,6 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer
// Misc
//
- /**
- * Notify to update the pixel-scale values.
- * <p>
- * FIXME: Bug 1373, 1374: Implement general High-DPI for even non native DPI toolkit aware platforms (Linux, Windows)
- * A variation may be be desired like
- * {@code pixelScaleChangeNotify(final float[] curPixelScale, final float[] minPixelScale, final float[] maxPixelScale)}.
- * </p>
- * <p>
- * Maybe create interface {@code ScalableSurface.Upstream} with above method,
- * to allow downstream to notify upstream ScalableSurface implementations like NEWT's {@link Window} to act accordingly.
- * </p>
- * @param minPixelScale
- * @param maxPixelScale
- * @param reset if {@code true} {@link #setSurfaceScale(float[]) reset pixel-scale} w/ {@link #getRequestedSurfaceScale(float[]) requested values}
- * value to reflect the new minimum and maximum values.
- */
- public final void pixelScaleChangeNotify(final float[] minPixelScale, final float[] maxPixelScale, final boolean reset) {
- System.arraycopy(minPixelScale, 0, this.minPixelScale, 0, 2);
- System.arraycopy(maxPixelScale, 0, this.maxPixelScale, 0, 2);
- if( reset ) {
- setSurfaceScale(reqPixelScale);
- }
- }
-
@Override
public final void windowRepaint(final int x, final int y, final int width, final int height) {
windowRepaint(false, x, y, width, height);
diff --git a/src/newt/classes/jogamp/newt/driver/windows/WindowDriver.java b/src/newt/classes/jogamp/newt/driver/windows/WindowDriver.java
index 1605d4126..b95911adf 100644
--- a/src/newt/classes/jogamp/newt/driver/windows/WindowDriver.java
+++ b/src/newt/classes/jogamp/newt/driver/windows/WindowDriver.java
@@ -76,7 +76,7 @@ public class WindowDriver extends WindowImpl {
*/
private boolean updatePixelScaleByMonitor(final long crt_handle, final int[] move_diff, final boolean sendEvent, final boolean defer) {
boolean res = false;
- if( 0 != crt_handle ) {
+ if( !hasSetPixelScale() && 0 != crt_handle ) {
final float newPixelScaleRaw[] = { 0, 0 };
if( GDIUtil.GetMonitorPixelScale(crt_handle, newPixelScaleRaw) ) {
res = applySoftPixelScale(move_diff, sendEvent, defer, newPixelScaleRaw);
diff --git a/src/newt/classes/jogamp/newt/driver/x11/WindowDriver.java b/src/newt/classes/jogamp/newt/driver/x11/WindowDriver.java
index 5fb01c619..e939edeae 100644
--- a/src/newt/classes/jogamp/newt/driver/x11/WindowDriver.java
+++ b/src/newt/classes/jogamp/newt/driver/x11/WindowDriver.java
@@ -105,7 +105,7 @@ public class WindowDriver extends WindowImpl {
*/
private boolean updatePixelScaleByMonitor(final MonitorDevice md, final int[] move_diff, final boolean sendEvent, final boolean defer) {
boolean res = false;
- if( null != md ) {
+ if( !hasSetPixelScale() && null != md ) {
final float newPixelScale[] = { 0, 0 };
md.getPixelScale(newPixelScale);
if( DEBUG_IMPLEMENTATION ) {
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<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 {
+ 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<args.length; i++) {
+ if(args[i].equals("-time")) {
+ if (++i < args.length) {
+ durationPerTest = atoi(args[i]);
+ }
+ }
+ }
+ org.junit.runner.JUnitCore.main(TestSingleGLInJSliderNewtAWT.class.getName());
+ }
+
+}
+