diff options
3 files changed, 214 insertions, 44 deletions
diff --git a/src/test/com/jogamp/opengl/test/android/MovieCubeActivity0.java b/src/test/com/jogamp/opengl/test/android/MovieCubeActivity0.java index ec8fa1000..1de095161 100644 --- a/src/test/com/jogamp/opengl/test/android/MovieCubeActivity0.java +++ b/src/test/com/jogamp/opengl/test/android/MovieCubeActivity0.java @@ -3,14 +3,14 @@ * * 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 @@ -20,7 +20,7 @@ * 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. @@ -57,7 +57,7 @@ import android.util.Log; public class MovieCubeActivity0 extends NewtBaseActivity { static String TAG = "MovieCubeActivity0"; - + MouseAdapter showKeyboardMouseListener = new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { @@ -66,18 +66,18 @@ public class MovieCubeActivity0 extends NewtBaseActivity { } } }; - + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - String[] streamLocs = new String[] { + + String[] streamLocs = new String[] { System.getProperty("jnlp.media0_url2"), System.getProperty("jnlp.media0_url1"), - System.getProperty("jnlp.media0_url0") }; + System.getProperty("jnlp.media0_url0") }; final URI streamLoc = getURI(streamLocs, 0, false); if(null == streamLoc) { throw new RuntimeException("no media reachable: "+Arrays.asList(streamLocs)); } - + // also initializes JOGL final GLCapabilities capsMain = new GLCapabilities(GLProfile.getGL2ES2()); capsMain.setBackgroundOpaque(false); @@ -86,53 +86,56 @@ public class MovieCubeActivity0 extends NewtBaseActivity { final com.jogamp.newt.Display dpy = NewtFactory.createDisplay(null); final com.jogamp.newt.Screen scrn = NewtFactory.createScreen(dpy, 0); scrn.addReference(); - + try { final Animator anim = new Animator(); - - // Main + + // Main final GLWindow glWindowMain = GLWindow.create(scrn, capsMain); glWindowMain.setFullscreen(true); setContentView(getWindow(), glWindowMain); anim.add(glWindowMain); glWindowMain.setVisible(true); glWindowMain.addMouseListener(showKeyboardMouseListener); - - final MovieCube demoMain = new MovieCube(-2.3f, 0f, 0f); + + final MovieCube demoMain = new MovieCube(MovieCube.zoom_def, 0f, 0f); final GLMediaPlayer mPlayer = demoMain.getGLMediaPlayer(); mPlayer.addEventListener(new GLMediaEventListener() { @Override public void newFrameAvailable(GLMediaPlayer ts, TextureFrame newFrame, long when) { } - + @Override public void attributesChanged(final GLMediaPlayer mp, int event_mask, long when) { System.err.println("MovieCubeActivity0 AttributesChanges: events_mask 0x"+Integer.toHexString(event_mask)+", when "+when); System.err.println("MovieCubeActivity0 State: "+mp); if( 0 != ( GLMediaEventListener.EVENT_CHANGE_INIT & event_mask ) ) { glWindowMain.addGLEventListener(demoMain); - anim.setUpdateFPSFrames(60, System.err); + anim.setUpdateFPSFrames(60, null); + anim.resetFPSCounter(); + } + if( 0 != ( GLMediaEventListener.EVENT_CHANGE_PLAY & event_mask ) ) { anim.resetFPSCounter(); } if( 0 != ( ( GLMediaEventListener.EVENT_CHANGE_ERR | GLMediaEventListener.EVENT_CHANGE_EOS ) & event_mask ) ) { final StreamException se = mPlayer.getStreamException(); if( null != se ) { - se.printStackTrace(); + se.printStackTrace(); } getActivity().finish(); } - } - }); + } + }); demoMain.initStream(streamLoc, GLMediaPlayer.STREAM_ID_AUTO, GLMediaPlayer.STREAM_ID_AUTO, 0); } catch (IOException e) { e.printStackTrace(); } - + scrn.removeReference(); Log.d(TAG, "onCreate - X"); } - + static URI getURI(String path[], int off, boolean checkAvail) { URI uri = null; for(int i=off; null==uri && i<path.length; i++) { diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieCube.java b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieCube.java index c1ccf7c39..daa09c3b1 100644 --- a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieCube.java +++ b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieCube.java @@ -34,6 +34,7 @@ import java.net.URI; import java.net.URISyntaxException; import javax.media.opengl.GL2ES2; +import javax.media.opengl.GLAnimatorControl; import javax.media.opengl.GLAutoDrawable; import javax.media.opengl.GLCapabilities; import javax.media.opengl.GLEventListener; @@ -41,6 +42,8 @@ import javax.media.opengl.GLException; import javax.media.opengl.GLProfile; import com.jogamp.common.util.IOUtil; +import com.jogamp.graph.curve.Region; +import com.jogamp.graph.font.Font; import com.jogamp.newt.Window; import com.jogamp.newt.event.KeyAdapter; import com.jogamp.newt.event.KeyEvent; @@ -49,6 +52,7 @@ import com.jogamp.newt.event.WindowAdapter; import com.jogamp.newt.event.WindowEvent; import com.jogamp.newt.opengl.GLWindow; import com.jogamp.opengl.JoglVersion; +import com.jogamp.opengl.test.junit.graph.TextRendererGLELBase; import com.jogamp.opengl.test.junit.jogl.demos.es2.TextureSequenceCubeES2; import com.jogamp.opengl.test.junit.util.MiscUtils; import com.jogamp.opengl.test.junit.util.UITestCase; @@ -63,11 +67,13 @@ import com.jogamp.opengl.util.texture.TextureSequence.TextureFrame; * Simple cube movie player w/ aspect ration true projection on a cube. */ public class MovieCube implements GLEventListener { + public static final float zoom_def = -2.77f; private static boolean waitForKey = false; private final float zoom0, rotx, roty; private TextureSequenceCubeES2 cube=null; private GLMediaPlayer mPlayer=null; private int swapInterval = 1; + private int swapIntervalSet = -1; private long lastPerfPos = 0; private volatile boolean resetGLState = false; @@ -92,7 +98,7 @@ public class MovieCube implements GLEventListener { * </p> */ public MovieCube() throws IOException, URISyntaxException { - this(-2.3f, 0f, 0f); + this(zoom_def, 0f, 0f); mPlayer.addEventListener(new GLMediaEventListener() { @Override @@ -147,6 +153,86 @@ public class MovieCube implements GLEventListener { resetGLState = true; } + private final class InfoTextRendererGLELBase extends TextRendererGLELBase { + static final float z_diff = 0.001f; + final float underlineSize; + + InfoTextRendererGLELBase() { + // FIXME: Graph TextRenderer does not AA well w/o MSAA and FBO + super(Region.VBAA_RENDERING_BIT); + texSizeScale = 2; + + fontSize = 1; + pixelScale = 1.0f / ( fontSize * 20f ); + + // underlineSize: 'underline' amount of pixel below 0/0 (Note: lineGap is negative) + final Font.Metrics metrics = font.getMetrics(); + final float lineGap = metrics.getLineGap(fontSize); + final float descent = metrics.getDescent(fontSize); + underlineSize = descent - lineGap; + // System.err.println("XXX: fLG "+lineGap+", fDesc "+descent+", underlineSize "+underlineSize); + + staticRGBAColor[0] = 0.0f; + staticRGBAColor[1] = 0.0f; + staticRGBAColor[2] = 0.0f; + staticRGBAColor[3] = 1.0f; + } + + @Override + public void init(GLAutoDrawable drawable) { + // non-exclusive mode! + this.usrPMVMatrix = cube.pmvMatrix; + super.init(drawable); + } + + @Override + public void display(GLAutoDrawable drawable) { + final GLAnimatorControl anim = drawable.getAnimator(); + final float lfps = null != anim ? anim.getLastFPS() : 0f; + final float tfps = null != anim ? anim.getTotalFPS() : 0f; + final boolean hasVideo = GLMediaPlayer.STREAM_ID_NONE != mPlayer.getVID(); + final float pts = ( hasVideo ? mPlayer.getVideoPTS() : mPlayer.getAudioPTS() ) / 1000f; + + // Note: MODELVIEW is from [ -1 .. 1 ] + + // dy: position right above video rectangle (bottom text line) + final float aspect = (float)mPlayer.getWidth() / (float)mPlayer.getHeight(); + final float aspect_h = 1f/aspect; + final float dy = 1f-aspect_h; + + // yoff1: position right above video rectangle (bottom text line) + // less than underlineSize, so 'underline' pixels are above video. + final float yoff1 = dy-(pixelScale*underlineSize); + + // yoff2: position right below video rectangle (bottom text line) + final float yoff2 = 2f-dy; + + /** + System.err.println("XXX: a "+aspect+", aspect_h "+aspect_h+", dy "+dy+ + "; underlineSize "+underlineSize+" "+(pixelScale*underlineSize)+ + "; yoff "+yoff1+", yoff2 "+yoff2); */ + + // FIXME: Graph TextRenderer does not scale well, i.e. text update per 1/10s cause too much recompute of regions! + // final String text1 = String.format("%03.1f/%03.1f s, %s (%01.2fx, vol %01.2f), a %01.2f, fps %02.1f -> %02.1f / %02.1f, v-sync %d", + final String text1 = String.format("%03.0f/%03.0f s, %s (%01.2fx, vol %01.2f), a %01.2f, fps %02.1f -> %02.1f / %02.1f, v-sync %d", + pts, mPlayer.getDuration() / 1000f, + mPlayer.getState().toString().toLowerCase(), mPlayer.getPlaySpeed(), mPlayer.getAudioVolume(), + aspect, mPlayer.getFramerate(), lfps, tfps, swapIntervalSet); + final String text2 = String.format("audio: id %d, kbps %d, codec %s", + mPlayer.getAID(), mPlayer.getAudioBitrate()/1000, mPlayer.getAudioCodec()); + final String text3 = String.format("video: id %d, kbps %d, codec %s", + mPlayer.getVID(), mPlayer.getVideoBitrate()/1000, mPlayer.getVideoCodec()); + final String text4 = mPlayer.getURI().getRawPath(); + if( displayOSD && null != renderer ) { + renderString(drawable, text1, 1 /* col */, -1 /* row */, -1+z_diff, yoff1, 1f+z_diff); + renderString(drawable, text2, 1 /* col */, 0 /* row */, -1+z_diff, yoff2, 1f+z_diff); + renderString(drawable, text3, 1 /* col */, 1 /* row */, -1+z_diff, yoff2, 1f+z_diff); + renderString(drawable, text4, 1 /* col */, 2 /* row */, -1+z_diff, yoff2, 1f+z_diff); + } + } }; + private final InfoTextRendererGLELBase textRendererGLEL = new InfoTextRendererGLELBase(); + private boolean displayOSD = true; + private final KeyListener keyAction = new KeyAdapter() { public void keyReleased(KeyEvent e) { if( e.isAutoRepeat() ) { @@ -155,7 +241,15 @@ public class MovieCube implements GLEventListener { System.err.println("MC "+e); final int pts0 = GLMediaPlayer.STREAM_ID_NONE != mPlayer.getVID() ? mPlayer.getVideoPTS() : mPlayer.getAudioPTS(); int pts1 = 0; - switch(e.getKeyCode()) { + switch(e.getKeySymbol()) { + case KeyEvent.VK_V: { + switch(swapIntervalSet) { + case 0: swapInterval = 1; break; + default: swapInterval = 0; break; + } + break; + } + case KeyEvent.VK_O: displayOSD = !displayOSD; break; case KeyEvent.VK_RIGHT: pts1 = pts0 + 1000; break; case KeyEvent.VK_UP: pts1 = pts0 + 10000; break; case KeyEvent.VK_PAGE_UP: pts1 = pts0 + 30000; break; @@ -260,14 +354,11 @@ public class MovieCube implements GLEventListener { added = true; } else { added = false; } System.err.println("MC.init: kl-added "+added+", "+drawable.getClass().getName()); + drawable.addGLEventListener(textRendererGLEL); } @Override public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) { - final GL2ES2 gl = drawable.getGL().getGL2ES2(); - if(-1 != swapInterval) { - gl.setSwapInterval(swapInterval); // in case switching the drawable (impl. may bound attribute there) - } if(null == mPlayer) { return; } cube.reshape(drawable, x, y, width, height); } @@ -275,6 +366,7 @@ public class MovieCube implements GLEventListener { @Override public void dispose(GLAutoDrawable drawable) { System.err.println(Thread.currentThread()+" MovieCube.dispose ... "); + drawable.disposeGLEventListener(textRendererGLEL, true); disposeImpl(drawable, true); } @@ -297,6 +389,13 @@ public class MovieCube implements GLEventListener { @Override public void display(GLAutoDrawable drawable) { + if(-1 != swapInterval) { + final GL2ES2 gl = drawable.getGL().getGL2ES2(); + gl.setSwapInterval(swapInterval); // in case switching the drawable (impl. may bound attribute there) + drawable.getAnimator().resetFPSCounter(); + swapIntervalSet = swapInterval; + swapInterval = -1; + } if(null == mPlayer) { return; } if( resetGLState ) { @@ -392,7 +491,7 @@ public class MovieCube implements GLEventListener { System.err.println("forceGLDef "+forceGLDef); System.err.println("swapInterval "+swapInterval); - final MovieCube mc = new MovieCube(-2.3f, 0f, 0f); + final MovieCube mc = new MovieCube(zoom_def, 0f, 0f); mc.setSwapInterval(swapInterval); final GLProfile glp; @@ -437,7 +536,10 @@ public class MovieCube implements GLEventListener { } if( 0 != ( GLMediaEventListener.EVENT_CHANGE_INIT & event_mask ) ) { window.addGLEventListener(mc); - anim.setUpdateFPSFrames(60, System.err); + anim.setUpdateFPSFrames(60, null); + anim.resetFPSCounter(); + } + if( 0 != ( GLMediaEventListener.EVENT_CHANGE_PLAY & event_mask ) ) { anim.resetFPSCounter(); } if( 0 != ( ( GLMediaEventListener.EVENT_CHANGE_ERR | GLMediaEventListener.EVENT_CHANGE_EOS ) & event_mask ) ) { diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieSimple.java b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieSimple.java index fcf1cd2ef..e609dde55 100644 --- a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieSimple.java +++ b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieSimple.java @@ -48,6 +48,7 @@ import javax.media.opengl.fixedfunc.GLMatrixFunc; import com.jogamp.common.os.Platform; import com.jogamp.common.util.IOUtil; +import com.jogamp.graph.curve.Region; import com.jogamp.newt.Window; import com.jogamp.newt.event.KeyAdapter; import com.jogamp.newt.event.KeyEvent; @@ -60,6 +61,7 @@ import com.jogamp.newt.event.WindowEvent; import com.jogamp.newt.opengl.GLWindow; import com.jogamp.opengl.GLExtensions; import com.jogamp.opengl.JoglVersion; +import com.jogamp.opengl.test.junit.graph.TextRendererGLELBase; import com.jogamp.opengl.test.junit.util.MiscUtils; import com.jogamp.opengl.test.junit.util.UITestCase; import com.jogamp.opengl.util.Animator; @@ -101,6 +103,7 @@ public class MovieSimple implements GLEventListener { private int effects = EFFECT_NORMAL; private float alpha = 1.0f; private int swapInterval = 1; + private int swapIntervalSet = -1; private GLMediaPlayer mPlayer; private final boolean mPlayerShared; @@ -127,6 +130,55 @@ public class MovieSimple implements GLEventListener { defURI = _defURI; } + private final class InfoTextRendererGLELBase extends TextRendererGLELBase { + InfoTextRendererGLELBase() { + // FIXME: Graph TextRenderer does not AA well w/o MSAA and FBO + super(Region.VBAA_RENDERING_BIT); + texSizeScale = 2; + + fontSize = 18; + + staticRGBAColor[0] = 1.0f; + staticRGBAColor[1] = 1.0f; + staticRGBAColor[2] = 1.0f; + staticRGBAColor[3] = 1.0f; + } + + @Override + public void display(GLAutoDrawable drawable) { + final GLAnimatorControl anim = drawable.getAnimator(); + final float lfps = null != anim ? anim.getLastFPS() : 0f; + final float tfps = null != anim ? anim.getTotalFPS() : 0f; + final boolean hasVideo = GLMediaPlayer.STREAM_ID_NONE != mPlayer.getVID(); + final float pts = ( hasVideo ? mPlayer.getVideoPTS() : mPlayer.getAudioPTS() ) / 1000f; + + // Note: MODELVIEW is from [ 0 .. height ] + + final int height = drawable.getHeight(); + + final float aspect = (float)mPlayer.getWidth() / (float)mPlayer.getHeight(); + + // FIXME: Graph TextRenderer does not scale well, i.e. text update per 1/10s cause too much recompute of regions! + // final String text1 = String.format("%03.1f/%03.1f s, %s (%01.2fx, vol %01.2f), a %01.2f, fps %02.1f -> %02.1f / %02.1f, v-sync %d", + final String text1 = String.format("%03.0f/%03.0f s, %s (%01.2fx, vol %01.2f), a %01.2f, fps %02.1f -> %02.1f / %02.1f, v-sync %d", + pts, mPlayer.getDuration() / 1000f, + mPlayer.getState().toString().toLowerCase(), mPlayer.getPlaySpeed(), mPlayer.getAudioVolume(), + aspect, mPlayer.getFramerate(), lfps, tfps, swapIntervalSet); + final String text2 = String.format("audio: id %d, kbps %d, codec %s", + mPlayer.getAID(), mPlayer.getAudioBitrate()/1000, mPlayer.getAudioCodec()); + final String text3 = String.format("video: id %d, kbps %d, codec %s", + mPlayer.getVID(), mPlayer.getVideoBitrate()/1000, mPlayer.getVideoCodec()); + final String text4 = mPlayer.getURI().getRawPath(); + if( displayOSD && null != renderer ) { + renderString(drawable, text1, 1 /* col */, 1 /* row */, 0, 0, -1); + renderString(drawable, text2, 1 /* col */, -4 /* row */, 0, height, -1); + renderString(drawable, text3, 1 /* col */, -3 /* row */, 0, height, -1); + renderString(drawable, text4, 1 /* col */, -2 /* row */, 0, height, -1); + } + } }; + private final InfoTextRendererGLELBase textRendererGLEL = new InfoTextRendererGLELBase(); + private boolean displayOSD = true; + private final MouseListener mouseAction = new MouseAdapter() { public void mousePressed(MouseEvent e) { if(e.getY()<=winHeight/2 && null!=mPlayer && 1 == e.getClickCount()) { @@ -170,8 +222,7 @@ public class MovieSimple implements GLEventListener { zoom += e.getRotation()[1]/10f; // vertical: wheel System.err.println("zoom: "+zoom); } - } - }; + } }; private final KeyListener keyAction = new KeyAdapter() { public void keyReleased(KeyEvent e) { @@ -181,7 +232,15 @@ public class MovieSimple implements GLEventListener { System.err.println("MC "+e); final int pts0 = GLMediaPlayer.STREAM_ID_NONE != mPlayer.getVID() ? mPlayer.getVideoPTS() : mPlayer.getAudioPTS(); int pts1 = 0; - switch(e.getKeyCode()) { + switch(e.getKeySymbol()) { + case KeyEvent.VK_V: { + switch(swapIntervalSet) { + case 0: swapInterval = 1; break; + default: swapInterval = 0; break; + } + break; + } + case KeyEvent.VK_O: displayOSD = !displayOSD; break; case KeyEvent.VK_RIGHT: pts1 = pts0 + 1000; break; case KeyEvent.VK_UP: pts1 = pts0 + 10000; break; case KeyEvent.VK_PAGE_UP: pts1 = pts0 + 30000; break; @@ -237,8 +296,7 @@ public class MovieSimple implements GLEventListener { if( 0 != pts1 ) { mPlayer.seek(pts1); } - } - }; + } }; /** * Default constructor which also issues {@link #initStream(URI, int, int, int)} w/ default values @@ -512,6 +570,7 @@ public class MovieSimple implements GLEventListener { winWidth = window.getWidth(); winHeight = window.getHeight(); } + drawable.addGLEventListener(textRendererGLEL); } protected void updateInterleavedVBO(GL gl, Texture tex) { @@ -571,9 +630,6 @@ public class MovieSimple implements GLEventListener { @Override public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) { final GL2ES2 gl = drawable.getGL().getGL2ES2(); - if(-1 != swapInterval) { - gl.setSwapInterval(swapInterval); // in case switching the drawable (impl. may bound attribute there) - } if(null == mPlayer) { return; } winWidth = width; winHeight = height; @@ -609,6 +665,7 @@ public class MovieSimple implements GLEventListener { @Override public void dispose(GLAutoDrawable drawable) { + drawable.disposeGLEventListener(textRendererGLEL, true); disposeImpl(drawable, true); } @@ -646,6 +703,13 @@ public class MovieSimple implements GLEventListener { @Override public void display(GLAutoDrawable drawable) { + final GL2ES2 gl = drawable.getGL().getGL2ES2(); + if(-1 != swapInterval) { + gl.setSwapInterval(swapInterval); // in case switching the drawable (impl. may bound attribute there) + drawable.getAnimator().resetFPSCounter(); + swapIntervalSet = swapInterval; + swapInterval = -1; + } if(null == mPlayer) { return; } if( resetGLState ) { @@ -662,8 +726,6 @@ public class MovieSimple implements GLEventListener { lastPerfPos = currentPos; } - GL2ES2 gl = drawable.getGL().getGL2ES2(); - gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); if(null == st) { @@ -737,7 +799,7 @@ public class MovieSimple implements GLEventListener { // Use GLEventListener in all cases [A+V, V, A] window.addGLEventListener(ms); final GLAnimatorControl anim = window.getAnimator(); - anim.setUpdateFPSFrames(60, System.err); + anim.setUpdateFPSFrames(60, null); anim.resetFPSCounter(); /** @@ -754,6 +816,10 @@ public class MovieSimple implements GLEventListener { System.out.println("play.1 "+ms.mPlayer); */ } + if( 0 != ( GLMediaEventListener.EVENT_CHANGE_PLAY & event_mask ) ) { + window.getAnimator().resetFPSCounter(); + } + boolean destroy = false; Throwable err = null; @@ -916,12 +982,11 @@ public class MovieSimple implements GLEventListener { System.err.println("GLProfile: "+glp); GLCapabilities caps = new GLCapabilities(glp); - final Animator anim = new Animator(); - anim.start(); - final MovieSimple[] mss = new MovieSimple[windowCount]; final GLWindow[] windows = new GLWindow[windowCount]; for(int i=0; i<windowCount; i++) { + final Animator anim = new Animator(); + anim.start(); windows[i] = GLWindow.create(caps); windows[i].addWindowListener(new WindowAdapter() { public void windowDestroyed(WindowEvent e) { |