From 34687193484b2404d83eebf5d008b71d54e52286 Mon Sep 17 00:00:00 2001 From: Sven Gothel Date: Fri, 18 Jan 2013 03:38:35 +0100 Subject: Fix Bug 669: Recursive GLContext makeCurrent()/release() Culprit: GLContext's makeCurrent() didn't clear the boolean flag 'unlockContextAndSurface' in case the context is already current (-> recursion). Above case was detected within a code block tailed by a finally block, which acted on mentioned flag, i.e. called lock.unlock() and hence decremented the lock count even though the method return w/ successful state. Fixed. Added debug code: GLContext.release() debug code (DEBUG | TRACE_SWITCH), recording stack trace of last release() call, which is dumped in case no current was current. Added 2 unit tests: - Simple recursive GLContext makeCurrent()/release() from within GLEventListener's display(). Test also validates lock count and lock ownership. - GLAutoDrawable display() of another GLAutoDrawable from within GLEventListener's display(..). --- src/jogl/classes/javax/media/opengl/GLContext.java | 11 ++ src/jogl/classes/jogamp/opengl/GLContextImpl.java | 122 +++++++++++++------ .../jogamp/opengl/x11/glx/X11GLXContext.java | 2 +- .../acore/TestBug669RecursiveGLContext01NEWT.java | 135 +++++++++++++++++++++ .../acore/TestBug669RecursiveGLContext02NEWT.java | 132 ++++++++++++++++++++ 5 files changed, 364 insertions(+), 38 deletions(-) create mode 100644 src/test/com/jogamp/opengl/test/junit/jogl/acore/TestBug669RecursiveGLContext01NEWT.java create mode 100644 src/test/com/jogamp/opengl/test/junit/jogl/acore/TestBug669RecursiveGLContext02NEWT.java (limited to 'src') diff --git a/src/jogl/classes/javax/media/opengl/GLContext.java b/src/jogl/classes/javax/media/opengl/GLContext.java index 8cc29f1f2..455f2d70d 100644 --- a/src/jogl/classes/javax/media/opengl/GLContext.java +++ b/src/jogl/classes/javax/media/opengl/GLContext.java @@ -103,6 +103,7 @@ public abstract class GLContext { public static final boolean DEBUG = Debug.debug("GLContext"); public static final boolean TRACE_SWITCH = Debug.isPropertyDefined("jogl.debug.GLContext.TraceSwitch", true); + public static final boolean DEBUG_TRACE_SWITCH = DEBUG || TRACE_SWITCH; /** Reflects property jogl.debug.DebugGL. If true, the debug pipeline is enabled at context creation. */ public static final boolean DEBUG_GL = Debug.isPropertyDefined("jogl.debug.DebugGL", true); @@ -414,6 +415,16 @@ public abstract class GLContext { } } + /** Returns a String representation of the {@link #makeCurrent()} result. */ + public static final String makeCurrentResultToString(int res) { + switch(res) { + case CONTEXT_NOT_CURRENT: return "CONTEXT_NOT_CURRENT"; + case CONTEXT_CURRENT: return "CONTEXT_CURRENT"; + case CONTEXT_CURRENT_NEW: return "CONTEXT_NOT_CURRENT"; + default: return "INVALID_VALUE"; + } + } + /** * Sets the thread-local variable returned by {@link #getCurrent} * and has no other side-effects. For use by third parties adding diff --git a/src/jogl/classes/jogamp/opengl/GLContextImpl.java b/src/jogl/classes/jogamp/opengl/GLContextImpl.java index d960883d5..f2c2cfada 100644 --- a/src/jogl/classes/jogamp/opengl/GLContextImpl.java +++ b/src/jogl/classes/jogamp/opengl/GLContextImpl.java @@ -49,6 +49,7 @@ import com.jogamp.common.os.DynamicLookupHelper; import com.jogamp.common.os.Platform; import com.jogamp.common.util.ReflectionUtil; import com.jogamp.common.util.VersionNumber; +import com.jogamp.common.util.locks.RecursiveLock; import com.jogamp.gluegen.runtime.FunctionAddressResolver; import com.jogamp.gluegen.runtime.ProcAddressTable; import com.jogamp.gluegen.runtime.opengl.GLNameResolver; @@ -270,14 +271,25 @@ public abstract class GLContextImpl extends GLContext { @Override public void release() throws GLException { release(false); - } + } private void release(boolean inDestruction) throws GLException { if(TRACE_SWITCH) { - System.err.println(getThreadName() +": GLContext.ContextSwitch: obj " + toHexString(hashCode()) + ", ctx "+toHexString(contextHandle)+", surf "+toHexString(drawable.getHandle())+" - release() - force: "+inDestruction+", "+lock); + System.err.println(getThreadName() +": GLContext.ContextSwitch[release.0]: obj " + toHexString(hashCode()) + ", ctx "+toHexString(contextHandle)+", surf "+toHexString(drawable.getHandle())+", inDestruction: "+inDestruction+", "+lock); } if ( !lock.isOwner(Thread.currentThread()) ) { - throw new GLException("Context not current on current thread "+Thread.currentThread().getName()+": "+this); + final String msg = getThreadName() +": Context not current on current thread, obj " + toHexString(hashCode())+", ctx "+toHexString(contextHandle)+", surf "+toHexString(drawable.getHandle())+", inDestruction: "+inDestruction+", "+lock; + if( DEBUG_TRACE_SWITCH ) { + System.err.println(msg); + if( null != lastCtxReleaseStack) { + System.err.print("Last release call: "); + lastCtxReleaseStack.printStackTrace(); + } else { + System.err.println("Last release call: NONE"); + } + } + throw new GLException(msg); } + Throwable drawableContextMadeCurrentException = null; final boolean actualRelease = ( inDestruction || lock.getHoldCount() == 1 ) && 0 != contextHandle; try { @@ -298,8 +310,13 @@ public abstract class GLContextImpl extends GLContext { } drawable.unlockSurface(); lock.unlock(); - if(TRACE_SWITCH) { - System.err.println(getThreadName() +": GLContext.ContextSwitch: obj " + toHexString(hashCode()) + ", ctx "+toHexString(contextHandle)+", surf "+toHexString(drawable.getHandle())+" - "+(actualRelease?"switch":"keep ")+" - CONTEXT_RELEASE - "+lock); + if( DEBUG_TRACE_SWITCH ) { + final String msg = getThreadName() +": GLContext.ContextSwitch[release.X]: obj " + toHexString(hashCode()) + ", ctx "+toHexString(contextHandle)+", surf "+toHexString(drawable.getHandle())+" - "+(actualRelease?"switch":"keep ")+" - "+lock; + lastCtxReleaseStack = new Throwable(msg); + if(TRACE_SWITCH) { + System.err.println(msg); + // Thread.dumpStack(); + } } } if(null != drawableContextMadeCurrentException) { @@ -307,11 +324,12 @@ public abstract class GLContextImpl extends GLContext { } } + private Throwable lastCtxReleaseStack = null; protected abstract void releaseImpl() throws GLException; @Override public final void destroy() { - if (DEBUG || TRACE_SWITCH) { + if (DEBUG_TRACE_SWITCH) { System.err.println(getThreadName() + ": GLContextImpl.destroy.0: obj " + toHexString(hashCode()) + ", ctx " + toHexString(contextHandle) + ", surf "+toHexString(drawable.getHandle())+", isShared "+GLContextShareSet.isShared(this)+" - "+lock); } @@ -328,7 +346,7 @@ public abstract class GLContextImpl extends GLContext { lock.lock(); // holdCount++ -> 1 - n (1: not locked, 2-n: destroy while rendering) if ( lock.getHoldCount() > 2 ) { final String msg = getThreadName() + ": GLContextImpl.destroy: obj " + toHexString(hashCode()) + ", ctx " + toHexString(contextHandle); - if (DEBUG || TRACE_SWITCH) { + if (DEBUG_TRACE_SWITCH) { System.err.println(msg+" - Lock was hold more than once - makeCurrent/release imbalance: "+lock); Thread.dumpStack(); } @@ -445,14 +463,21 @@ public abstract class GLContextImpl extends GLContext { */ @Override public int makeCurrent() throws GLException { - boolean unlockContextAndDrawable = true; - int res = CONTEXT_NOT_CURRENT; + if(TRACE_SWITCH) { + System.err.println(getThreadName() +": GLContext.ContextSwitch[makeCurrent.0]: obj " + toHexString(hashCode()) + ", ctx "+toHexString(contextHandle)+", surf "+toHexString(drawable.getHandle())+" - "+lock); + } // Note: the surface is locked within [makeCurrent .. swap .. release] - int lockRes = drawable.lockSurface(); + final int lockRes = drawable.lockSurface(); if (NativeSurface.LOCK_SURFACE_NOT_READY >= lockRes) { + if(DEBUG_TRACE_SWITCH) { + System.err.println(getThreadName() +": GLContext.ContextSwitch[makeCurrent.X1]: obj " + toHexString(hashCode()) + ", ctx "+toHexString(contextHandle)+", surf "+toHexString(drawable.getHandle())+" - Surface Not Ready - CONTEXT_NOT_CURRENT - "+lock); + } return CONTEXT_NOT_CURRENT; } + + boolean unlockContextAndSurface = true; // Must be cleared if successful, otherwise finally block will release context and surface! + int res = CONTEXT_NOT_CURRENT; try { if (0 == drawable.getHandle()) { throw new GLException("drawable has invalid handle: "+drawable); @@ -472,8 +497,9 @@ public abstract class GLContextImpl extends GLContext { // Assume we don't need to make this context current again // For Mac OS X, however, we need to update the context to track resizes drawableUpdatedNotify(); + unlockContextAndSurface = false; // success if(TRACE_SWITCH) { - System.err.println(getThreadName() +": GLContext.ContextSwitch: obj " + toHexString(hashCode()) + ", ctx "+toHexString(contextHandle)+", surf "+toHexString(drawable.getHandle())+" - keep - CONTEXT_CURRENT - "+lock); + System.err.println(getThreadName() +": GLContext.ContextSwitch[makeCurrent.X2]: obj " + toHexString(hashCode()) + ", ctx "+toHexString(contextHandle)+", surf "+toHexString(drawable.getHandle())+" - keep - CONTEXT_CURRENT - "+lock); } return CONTEXT_CURRENT; } else { @@ -481,7 +507,7 @@ public abstract class GLContextImpl extends GLContext { } } res = makeCurrentWithinLock(lockRes); - unlockContextAndDrawable = CONTEXT_NOT_CURRENT == res; + unlockContextAndSurface = CONTEXT_NOT_CURRENT == res; // success ? /** * FIXME: refactor dependence on Java 2D / JOGL bridge @@ -491,28 +517,27 @@ public abstract class GLContextImpl extends GLContext { } */ } catch (RuntimeException e) { - unlockContextAndDrawable = true; + unlockContextAndSurface = true; throw e; } finally { - if (unlockContextAndDrawable) { + if (unlockContextAndSurface) { + if(DEBUG_TRACE_SWITCH) { + System.err.println(getThreadName() +": GLContext.ContextSwitch[makeCurrent.1]: Context lock.unlock() due to error, res "+makeCurrentResultToString(res)+", "+lock); + } lock.unlock(); } } } /* if ( drawable.isRealized() ) */ } catch (RuntimeException e) { - unlockContextAndDrawable = true; + unlockContextAndSurface = true; throw e; } finally { - if (unlockContextAndDrawable) { + if (unlockContextAndSurface) { drawable.unlockSurface(); } } - if (res == CONTEXT_NOT_CURRENT) { - if(DEBUG || TRACE_SWITCH) { - System.err.println(getThreadName() +": GLContext.ContextSwitch: obj " + toHexString(hashCode()) + ", ctx "+toHexString(contextHandle)+", surf "+toHexString(drawable.getHandle())+", drawable.isRealized() "+drawable.isRealized()+" - switch - CONTEXT_NOT_CURRENT - "+lock); - } - } else { + if (res != CONTEXT_NOT_CURRENT) { setCurrent(this); if(res == CONTEXT_CURRENT_NEW) { // check if the drawable's and the GL's GLProfile are equal @@ -531,15 +556,9 @@ public abstract class GLContextImpl extends GLContext { gl = gl.getContext().setGL( GLPipelineFactory.create("javax.media.opengl.Trace", null, gl, new Object[] { System.err } ) ); } - contextRealized(true); - - if(DEBUG || TRACE_SWITCH) { - System.err.println(getThreadName() +": GLContext.ContextSwitch: obj " + toHexString(hashCode()) + ", ctx "+toHexString(contextHandle)+", surf "+toHexString(drawable.getHandle())+" - switch - CONTEXT_CURRENT_NEW - "+lock); - } - } else if(TRACE_SWITCH) { - System.err.println(getThreadName() +": GLContext.ContextSwitch: obj " + toHexString(hashCode()) + ", ctx "+toHexString(contextHandle)+", surf "+toHexString(drawable.getHandle())+" - switch - CONTEXT_CURRENT - "+lock); + contextRealized(true); } - + contextMadeCurrent(true); /* FIXME: refactor dependence on Java 2D / JOGL bridge @@ -551,9 +570,12 @@ public abstract class GLContextImpl extends GLContext { } */ } + if(TRACE_SWITCH) { + System.err.println(getThreadName() +": GLContext.ContextSwitch[makeCurrent.X3]: obj " + toHexString(hashCode()) + ", ctx "+toHexString(contextHandle)+", surf "+toHexString(drawable.getHandle())+" - switch - "+makeCurrentResultToString(res)+" - "+lock); + } return res; } - + private final int makeCurrentWithinLock(int surfaceLockRes) throws GLException { if (!isCreated()) { if(DEBUG_GL) { @@ -584,7 +606,7 @@ public abstract class GLContextImpl extends GLContext { shareWith.getDrawableImpl().unlockSurface(); } } - if (DEBUG || TRACE_SWITCH) { + if (DEBUG_TRACE_SWITCH) { if(created) { System.err.println(getThreadName() + ": Create GL context OK: obj " + toHexString(hashCode()) + ", ctx " + toHexString(contextHandle) + ", surf "+toHexString(drawable.getHandle())+" for " + getClass().getName()+" - "+getGLVersion()); // Thread.dumpStack(); @@ -1641,7 +1663,7 @@ public abstract class GLContextImpl extends GLContext { return device.getUniqueID() + "-" + toHexString(composeBits(major, minor, ctxProfileBits)); } - protected String getContextFQN() { + protected final String getContextFQN() { return contextFQN; } @@ -1696,19 +1718,19 @@ public abstract class GLContextImpl extends GLContext { //---------------------------------------------------------------------- // Helpers for buffer object optimizations - public void setBufferSizeTracker(GLBufferSizeTracker bufferSizeTracker) { + public final void setBufferSizeTracker(GLBufferSizeTracker bufferSizeTracker) { this.bufferSizeTracker = bufferSizeTracker; } - public GLBufferSizeTracker getBufferSizeTracker() { + public final GLBufferSizeTracker getBufferSizeTracker() { return bufferSizeTracker; } - public GLBufferStateTracker getBufferStateTracker() { + public final GLBufferStateTracker getBufferStateTracker() { return bufferStateTracker; } - public GLStateTracker getGLStateTracker() { + public final GLStateTracker getGLStateTracker() { return glStateTracker; } @@ -1717,10 +1739,36 @@ public abstract class GLContextImpl extends GLContext { // current on the OpenGL worker thread // - public boolean hasWaiters() { + /** + * Returns true if the given thread is owner, otherwise false. + *

+ * Method exists merely for code validation of {@link #isCurrent()}. + *

+ */ + public final boolean isOwner(Thread thread) { + return lock.isOwner(thread); + } + + /** + * Returns true if there are other threads waiting for this GLContext to {@link #makeCurrent()}, otherwise false. + *

+ * Since method does not perform any synchronization, accurate result are returned if lock is hold - only. + *

+ */ + public final boolean hasWaiters() { return lock.getQueueLength()>0; } + /** + * Returns the number of hold locks. See {@link RecursiveLock#getHoldCount()} for semantics. + *

+ * Since method does not perform any synchronization, accurate result are returned if lock is hold - only. + *

+ */ + public final int getLockCount() { + return lock.getHoldCount(); + } + //--------------------------------------------------------------------------- // Special FBO hook // diff --git a/src/jogl/classes/jogamp/opengl/x11/glx/X11GLXContext.java b/src/jogl/classes/jogamp/opengl/x11/glx/X11GLXContext.java index 5b0d32353..c2b66801e 100644 --- a/src/jogl/classes/jogamp/opengl/x11/glx/X11GLXContext.java +++ b/src/jogl/classes/jogamp/opengl/x11/glx/X11GLXContext.java @@ -166,7 +166,7 @@ public abstract class X11GLXContext extends GLContextImpl { throw new InternalError("Given readDrawable but no driver support"); } } catch (RuntimeException re) { - if(DEBUG || TRACE_SWITCH) { + if(DEBUG_TRACE_SWITCH) { System.err.println(getThreadName()+": Warning: X11GLXContext.glXMakeContextCurrent failed: "+re+", with "+ "dpy "+toHexString(dpy)+ ", write "+toHexString(writeDrawable)+ diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/acore/TestBug669RecursiveGLContext01NEWT.java b/src/test/com/jogamp/opengl/test/junit/jogl/acore/TestBug669RecursiveGLContext01NEWT.java new file mode 100644 index 000000000..7b8529191 --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/jogl/acore/TestBug669RecursiveGLContext01NEWT.java @@ -0,0 +1,135 @@ +/** + * Copyright 2013 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 javax.media.opengl.GLAutoDrawable; +import javax.media.opengl.GLCapabilities; +import javax.media.opengl.GLContext; +import javax.media.opengl.GLEventListener; +import javax.media.opengl.GLProfile; + +import jogamp.opengl.GLContextImpl; + +import org.junit.Assert; +import org.junit.Test; + +import com.jogamp.newt.opengl.GLWindow; +import com.jogamp.opengl.test.junit.jogl.demos.es2.GearsES2; +import com.jogamp.opengl.test.junit.util.UITestCase; +import com.jogamp.opengl.util.Animator; + +/** + * Tests simple recursive GLContext behavior. + * + *

+ * Issues {@link GLContext#makeCurrent()} and {@link GLContext#release()} + * from within {@link GLEventListener#display(GLAutoDrawable)}. + *

+ * + * + */ +public class TestBug669RecursiveGLContext01NEWT extends UITestCase { + + @Test(timeout=5000) + public void test01_Plain() { + test01Impl(false); + } + + @Test(timeout=5000) + public void test01_Anim() { + test01Impl(true); + } + + private void test01Impl(boolean anim) { + final String profile = GLProfile.GL2ES2; + if(!GLProfile.isAvailable(profile)) { System.err.println(profile+" n/a"); return; } + + final GLProfile pro = GLProfile.get(profile); + final GLCapabilities caps = new GLCapabilities(pro); + final GLWindow window = GLWindow.create(caps); + + final Animator animator = new Animator(); + if(anim) { + animator.add(window); + } + animator.start(); + + window.setSize(640, 480); + window.addGLEventListener(new GLEventListener() { + private void makeCurrentRecursive(GLContextImpl context, int lockCount) { + Assert.assertEquals(true, context.isOwner(Thread.currentThread())); + Assert.assertEquals(lockCount, context.getLockCount()); + Assert.assertEquals(true, context.isCurrent()); + + Assert.assertEquals(GLContext.CONTEXT_CURRENT, context.makeCurrent()); // recursive: lock +1 + + Assert.assertEquals(true, context.isOwner(Thread.currentThread())); + Assert.assertEquals(lockCount+1, context.getLockCount()); + Assert.assertEquals(true, context.isCurrent()); + } + private void releaseRecursive(GLContextImpl context, int lockCount) { + Assert.assertEquals(true, context.isOwner(Thread.currentThread())); + Assert.assertEquals(lockCount, context.getLockCount()); + Assert.assertEquals(true, context.isCurrent()); // still current + + context.release(); // recursive: lock -1 + + Assert.assertEquals(true, context.isOwner(Thread.currentThread())); + Assert.assertEquals(lockCount-1, context.getLockCount()); + Assert.assertEquals(true, context.isCurrent()); // still current + } + + public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) { } + + public void init(final GLAutoDrawable drawable) { } + + public void dispose(final GLAutoDrawable drawable) { } + + public void display(final GLAutoDrawable drawable) { + final GLContextImpl context = (GLContextImpl)drawable.getContext(); + makeCurrentRecursive(context, 1); + releaseRecursive(context, 2); + } + }); + window.addGLEventListener(new GearsES2()); + + try { + window.setVisible(true); + window.display(); + } finally { + animator.stop(); + window.destroy(); + } + } + + public static void main(String args[]) { + org.junit.runner.JUnitCore.main(TestBug669RecursiveGLContext01NEWT.class.getName()); + } + +} + diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/acore/TestBug669RecursiveGLContext02NEWT.java b/src/test/com/jogamp/opengl/test/junit/jogl/acore/TestBug669RecursiveGLContext02NEWT.java new file mode 100644 index 000000000..104355a18 --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/jogl/acore/TestBug669RecursiveGLContext02NEWT.java @@ -0,0 +1,132 @@ +/** + * Copyright 2013 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 javax.media.opengl.GLAutoDrawable; +import javax.media.opengl.GLCapabilities; +import javax.media.opengl.GLEventListener; +import javax.media.opengl.GLProfile; + +import org.junit.Assert; +import org.junit.Test; + +import com.jogamp.newt.opengl.GLWindow; +import com.jogamp.opengl.test.junit.jogl.demos.es2.GearsES2; +import com.jogamp.opengl.test.junit.jogl.demos.es2.RedSquareES2; +import com.jogamp.opengl.test.junit.util.UITestCase; +import com.jogamp.opengl.util.Animator; + +/** + * Tests recursive GLContext behavior. + * + *

+ * Issues {@link GLAutoDrawable#display()} of another {@link GLAutoDrawable} + * from within {@link GLEventListener#display(GLAutoDrawable)}. + *

+ * + * + */ +public class TestBug669RecursiveGLContext02NEWT extends UITestCase { + + @Test(timeout=5000) + public void test01_Plain() { + test01Impl(false); + } + + @Test(timeout=5000) + public void test01_Anim() { + test01Impl(true); + } + + private void test01Impl(boolean anim) { + final String profile = GLProfile.GL2ES2; + if(!GLProfile.isAvailable(profile)) { System.err.println(profile+" n/a"); return; } + + final GLProfile pro = GLProfile.get(profile); + final GLCapabilities caps = new GLCapabilities(pro); + + final GLWindow window2 = GLWindow.create(caps); // display() triggered by window's GLEventListener! + window2.setPosition(0, 0); + window2.setSize(200, 200); + window2.addGLEventListener(new RedSquareES2()); + + final GLWindow window1 = GLWindow.create(caps); + + final Animator animator1 = new Animator(); + final Animator animator2 = new Animator(); + if(anim) { + animator1.add(window1); + animator2.add(window2); + } + animator1.start(); + animator2.start(); + + window1.setPosition(250, 0); + window1.setSize(200, 200); + window1.addGLEventListener(new GLEventListener() { + public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) { } + + public void init(final GLAutoDrawable drawable) { } + + public void dispose(final GLAutoDrawable drawable) { } + + public void display(final GLAutoDrawable drawable) { + window2.display(); + } + }); + window1.addGLEventListener(new GearsES2()); + + try { + window2.setVisible(true); + window1.setVisible(true); + window1.display(); + window2.display(); + if(anim) { + try { + Thread.sleep(500); + } catch(InterruptedException ie) {} + } + } finally { + animator1.stop(); + + final int win1Frames = window1.getTotalFPSFrames(); + final int win2Frames = window2.getTotalFPSFrames(); + System.err.println("Window1: frames "+win1Frames); + System.err.println("Window2: frames "+win2Frames); + Assert.assertTrue("Win2 frames not double the amount of Win1 frames", 2*win2Frames >= win1Frames); + window1.destroy(); + window2.destroy(); + } + } + + public static void main(String args[]) { + org.junit.runner.JUnitCore.main(TestBug669RecursiveGLContext02NEWT.class.getName()); + } + +} + -- cgit v1.2.3