diff options
author | Sven Gothel <[email protected]> | 2014-07-05 04:04:43 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2014-07-05 04:04:43 +0200 |
commit | f8f0f051604721bceaee214b8e5218fd47d2eb9e (patch) | |
tree | e9c3103498984fbc5d2f8567e88009c49e8810f7 | |
parent | 2293a53ba04a8cf2881e8919f8be97c16a9af336 (diff) |
Bug 1021: Make OVR access vendor agnostic: Package 'com.jogamp.opengl.util.stereo' contains all public interfaces/classes
Renamed interfaces:
CustomRendererListener -> CustomGLEventListener
StereoRendererListener -> StereoGLEventListener
New vendor agnostic 'stuff' in com.jogamp.opengl.util.stereo:
1 - StereoDeviceFactory
To create a vendor specific StereoDeviceFactory instance,
which creates the StereoDevice.
2 - StereoDevice
For vendor specific implementation.
Can create StereoDeviceRenderer.
3 - StereoDeviceRenderer
For vendor specific implementation.
4 - StereoClientRenderer
Vendor agnostic client StereoGLEventListener renderer,
using a StereoDeviceRenderer.
Now supports multiple StereoGLEventListener, via add/remove.
- MovieSBSStereo demo-able via StereoDemo01
can show SBS 3D movies.
24 files changed, 2845 insertions, 1395 deletions
diff --git a/make/scripts/tests.sh b/make/scripts/tests.sh index dd7c2c0a0..f4af4c925 100644 --- a/make/scripts/tests.sh +++ b/make/scripts/tests.sh @@ -276,6 +276,7 @@ function jrun() { #D_ARGS="-Djogl.debug.GLMediaPlayer -Djogl.debug.GLSLCode" #D_ARGS="-Djogl.debug.GLMediaPlayer.StreamWorker.delay=25 -Djogl.debug.GLMediaPlayer" #D_ARGS="-Djogl.debug.GLMediaPlayer.Native" + #D_ARGS="-Djogl.debug.StereoDevice" if [ $awton -eq 1 ] ; then export USE_CLASSPATH=$JOGAMP_ALL_AWT_CLASSPATH @@ -370,7 +371,7 @@ function testawtswt() { # # Stereo # -testnoawt com.jogamp.opengl.test.junit.jogl.stereo.ovr.OVRDemo01 $* +testnoawt com.jogamp.opengl.test.junit.jogl.stereo.StereoDemo01 $* # # HiDPI diff --git a/src/jogl/classes/com/jogamp/opengl/util/CustomRendererListener.java b/src/jogl/classes/com/jogamp/opengl/util/CustomGLEventListener.java index 0e6de5178..86443087e 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/CustomRendererListener.java +++ b/src/jogl/classes/com/jogamp/opengl/util/CustomGLEventListener.java @@ -34,7 +34,7 @@ import javax.media.opengl.GLEventListener; * Extended {@link GLEventListener} interface * supporting more fine grained control over the implementation. */ -public interface CustomRendererListener extends GLEventListener { +public interface CustomGLEventListener extends GLEventListener { /** * {@link #display(GLAutoDrawable, int) display flag}: Repeat last produced image. * <p> diff --git a/src/jogl/classes/com/jogamp/opengl/util/stereo/EyeParameter.java b/src/jogl/classes/com/jogamp/opengl/util/stereo/EyeParameter.java index 7774d67e2..075da340b 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/stereo/EyeParameter.java +++ b/src/jogl/classes/com/jogamp/opengl/util/stereo/EyeParameter.java @@ -62,7 +62,7 @@ public final class EyeParameter { this.eyeReliefZ = eyeRelief; } public final String toString() { - return "EyeParam[num"+number+", posOff["+positionOffset[0]+", "+positionOffset[1]+", "+positionOffset[2]+"], "+fovhv+ + return "EyeParam[num "+number+", posOff["+positionOffset[0]+", "+positionOffset[1]+", "+positionOffset[2]+"], "+fovhv+ ", distPupil[noseX "+distNoseToPupilX+", middleY "+distMiddleToPupilY+", reliefZ "+eyeReliefZ+"]]"; } }
\ No newline at end of file diff --git a/src/jogl/classes/com/jogamp/opengl/util/stereo/EyePose.java b/src/jogl/classes/com/jogamp/opengl/util/stereo/EyePose.java index 2690097f1..aa64ff130 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/stereo/EyePose.java +++ b/src/jogl/classes/com/jogamp/opengl/util/stereo/EyePose.java @@ -64,6 +64,6 @@ public final class EyePose { position[2] = posZ; } public final String toString() { - return "EyePose[num"+number+", pos["+position[0]+", "+position[1]+", "+position[2]+"], "+orientation+"]"; + return "EyePose[num "+number+", pos["+position[0]+", "+position[1]+", "+position[2]+"], "+orientation+"]"; } }
\ No newline at end of file diff --git a/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoClientRenderer.java b/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoClientRenderer.java new file mode 100644 index 000000000..9f9ebdf2a --- /dev/null +++ b/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoClientRenderer.java @@ -0,0 +1,255 @@ +/** + * Copyright 2014 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.util.stereo; + +import javax.media.nativewindow.util.DimensionImmutable; +import javax.media.nativewindow.util.RectangleImmutable; +import javax.media.opengl.GL; +import javax.media.opengl.GL2ES2; +import javax.media.opengl.GLAutoDrawable; +import javax.media.opengl.GLEventListener; + +import jogamp.opengl.GLDrawableHelper; +import jogamp.opengl.GLDrawableHelper.GLEventListenerAction; + +import com.jogamp.opengl.FBObject; +import com.jogamp.opengl.FBObject.Attachment; +import com.jogamp.opengl.FBObject.TextureAttachment; +import com.jogamp.opengl.FBObject.Attachment.Type; +import com.jogamp.opengl.util.CustomGLEventListener; + +/** + * {@link StereoClientRenderer} utilizing {@link StereoDeviceRenderer} + * implementing {@link GLEventListener} for convenience. + * <p> + * Implementation renders {@link StereoGLEventListener} + * using one or more {@link FBObject} according to {@link StereoDeviceRenderer#getTextureCount()}. + * </p> + */ +public class StereoClientRenderer implements GLEventListener { + private final GLDrawableHelper helper; + private final StereoDeviceRenderer deviceRenderer; + private final boolean ownsDevice; + private final FBObject[] fbos; + private final int magFilter; + private final int minFilter; + private final boolean usePostprocessing; + + private int numSamples; + private final TextureAttachment[] fboTexs; + + public StereoClientRenderer(final StereoDeviceRenderer deviceRenderer, final boolean ownsDevice, + final int magFilter, final int minFilter, final int numSamples) { + final int fboCount = deviceRenderer.getTextureCount(); + if( 0 > fboCount || 2 < fboCount ) { + throw new IllegalArgumentException("fboCount must be within [0..2], has "+fboCount+", due to "+deviceRenderer); + } + this.helper = new GLDrawableHelper(); + this.deviceRenderer = deviceRenderer; + this.usePostprocessing = deviceRenderer.ppRequired() || deviceRenderer.usesSideBySideStereo() && fboCount > 1; + this.ownsDevice = ownsDevice; + this.magFilter = magFilter; + this.minFilter = minFilter; + + this.numSamples = numSamples; + + this.fbos = new FBObject[fboCount]; + for(int i=0; i<fboCount; i++) { + this.fbos[i] = new FBObject(); + } + this.fboTexs = new TextureAttachment[fboCount]; + } + + private void initFBOs(final GL gl, final DimensionImmutable size) { + for(int i=0; i<fbos.length; i++) { + fbos[i].detachAllColorbuffer(gl); + fbos[i].reset(gl, size.getWidth(), size.getHeight(), numSamples, false); + if( i>0 && fbos[i-1].getNumSamples() != fbos[i].getNumSamples()) { + throw new InternalError("sample size mismatch: \n\t0: "+fbos[i-1]+"\n\t1: "+fbos[i]); + } + numSamples = fbos[i].getNumSamples(); + + if(numSamples>0) { + fbos[i].attachColorbuffer(gl, 0, true); // MSAA requires alpha + fbos[i].attachRenderbuffer(gl, Type.DEPTH, 24); + final FBObject ssink = new FBObject(); + { + ssink.reset(gl, size.getWidth(), size.getHeight()); + ssink.attachTexture2D(gl, 0, false, magFilter, minFilter, GL.GL_CLAMP_TO_EDGE, GL.GL_CLAMP_TO_EDGE); + ssink.attachRenderbuffer(gl, Attachment.Type.DEPTH, 24); + } + fbos[i].setSamplingSink(ssink); + fbos[i].resetSamplingSink(gl); // validate + fboTexs[i] = fbos[i].getSamplingSink(); + } else { + fboTexs[i] = fbos[i].attachTexture2D(gl, 0, false, magFilter, minFilter, GL.GL_CLAMP_TO_EDGE, GL.GL_CLAMP_TO_EDGE); + fbos[i].attachRenderbuffer(gl, Type.DEPTH, 24); + } + fbos[i].unbind(gl); + System.err.println("FBO["+i+"]: "+fbos[i]); + } + + } + + @SuppressWarnings("unused") + private void resetFBOs(final GL gl, final DimensionImmutable size) { + for(int i=0; i<fbos.length; i++) { + fbos[i].reset(gl, size.getWidth(), size.getHeight(), numSamples, true); + if( i>0 && fbos[i-1].getNumSamples() != fbos[i].getNumSamples()) { + throw new InternalError("sample size mismatch: \n\t0: "+fbos[i-1]+"\n\t1: "+fbos[i]); + } + numSamples = fbos[i].getNumSamples(); + if(numSamples>0) { + fboTexs[i] = fbos[i].getSamplingSink(); + } else { + fboTexs[i] = (TextureAttachment) fbos[i].getColorbuffer(0); + } + } + } + + public final StereoDeviceRenderer getStereoDeviceRenderer() { return deviceRenderer; } + + public final void addGLEventListener(final StereoGLEventListener l) { + helper.addGLEventListener(l); + } + public final void removeGLEventListener(final StereoGLEventListener l) { + helper.removeGLEventListener(l); + } + + @Override + public void init(final GLAutoDrawable drawable) { + final GL2ES2 gl = drawable.getGL().getGL2ES2(); + deviceRenderer.init(gl); + + // We will do some offscreen rendering, setup FBO... + final DimensionImmutable textureSize = deviceRenderer.getTextureCount() > 1 ? deviceRenderer.getSingleSurfaceSize() : deviceRenderer.getTotalSurfaceSize(); + initFBOs(gl, textureSize); + helper.init(drawable, false); + + gl.setSwapInterval(1); + } + + @Override + public void dispose(final GLAutoDrawable drawable) { + final GL2ES2 gl = drawable.getGL().getGL2ES2(); + helper.disposeAllGLEventListener(drawable, false); + for(int i=0; i<fbos.length; i++) { + fbos[i].destroy(gl); + fboTexs[i] = null; + } + if( ownsDevice ) { + deviceRenderer.dispose(gl); + } + } + + @Override + public void display(final GLAutoDrawable drawable) { + final GL2ES2 gl = drawable.getGL().getGL2ES2(); + + deviceRenderer.beginFrame(gl); + + if(0 < numSamples) { + gl.glEnable(GL.GL_MULTISAMPLE); + } + + final int fboCount = fbos.length; + final int displayRepeatFlags; + if( 1 == fboCount ) { + displayRepeatFlags = CustomGLEventListener.DISPLAY_DONTCLEAR; + } else { + displayRepeatFlags = 0; + } + + // Update eye pos upfront to have same (almost) results + deviceRenderer.updateEyePose(0); + deviceRenderer.updateEyePose(1); + + if( 1 == fboCount ) { + fbos[0].bind(gl); + } + + for(int eyeNum=0; eyeNum<2; eyeNum++) { + if( 1 < fboCount ) { + fbos[eyeNum].bind(gl); + } + + final StereoDeviceRenderer.Eye eye = deviceRenderer.getEye(eyeNum); + final RectangleImmutable viewport = eye.getViewport(); + gl.glViewport(viewport.getX(), viewport.getY(), viewport.getWidth(), viewport.getHeight()); + + final int displayFlags = eyeNum > 0 ? CustomGLEventListener.DISPLAY_REPEAT | displayRepeatFlags : 0; + final GLEventListenerAction reshapeDisplayAction = new GLEventListenerAction() { + public void run(final GLAutoDrawable drawable, final GLEventListener listener) { + final StereoGLEventListener sl = (StereoGLEventListener) listener; + sl.reshapeForEye(drawable, viewport.getX(), viewport.getY(), viewport.getWidth(), viewport.getHeight(), + eye.getEyeParameter(), eye.getLastEyePose()); + sl.display(drawable, displayFlags); + } }; + helper.runForAllGLEventListener(drawable, reshapeDisplayAction); + + if( 1 < fboCount ) { + fbos[eyeNum].unbind(gl); + } + } + if( 1 == fboCount ) { + fbos[0].unbind(gl); + } + // restore viewport + gl.glViewport(0, 0, drawable.getSurfaceWidth(), drawable.getSurfaceHeight()); + + if( usePostprocessing ) { + deviceRenderer.ppBegin(gl); + if( 1 == fboCount ) { + fbos[0].use(gl, fboTexs[0]); + deviceRenderer.ppBothEyes(gl); + fbos[0].unuse(gl); + } else { + fbos[0].use(gl, fboTexs[0]); + deviceRenderer.ppOneEye(gl, 0); + fbos[0].unuse(gl); + fbos[1].use(gl, fboTexs[1]); + deviceRenderer.ppOneEye(gl, 1); + fbos[1].unuse(gl); + } + deviceRenderer.ppEnd(gl); + } + + if( !drawable.getAutoSwapBufferMode() ) { + drawable.swapBuffers(); + } + deviceRenderer.endFrame(gl); + } + + @Override + public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) { + if( !drawable.getAutoSwapBufferMode() ) { + final GL2ES2 gl = drawable.getGL().getGL2ES2(); + gl.glViewport(0, 0, width, height); + } + } +} diff --git a/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoDevice.java b/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoDevice.java new file mode 100644 index 000000000..e5c0e3646 --- /dev/null +++ b/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoDevice.java @@ -0,0 +1,93 @@ +/** + * Copyright 2014 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.util.stereo; + +import javax.media.nativewindow.util.DimensionImmutable; +import javax.media.nativewindow.util.PointImmutable; + +import jogamp.opengl.Debug; + +import com.jogamp.opengl.math.FovHVHalves; + +/** + * Interface describing a native stereoscopic device + */ +public interface StereoDevice { + public static final boolean DEBUG = Debug.debug("StereoDevice"); + + /** + * Default eye position offset for {@link #createRenderer(int, int, float[], FovHVHalves[], float)}. + * <p> + * Default offset is 1.6f <i>up</i> and 5.0f <i>away</i>. + * </p> + */ + public static final float[] DEFAULT_EYE_POSITION_OFFSET = { 0.0f, 1.6f, -5.0f }; + + /** Disposes this {@link StereoDevice}. */ + public void dispose(); + + /** + * If operation within a device spanning virtual desktop, + * returns the device position. + * <p> + * Otherwise simply 0/0. + * </p> + */ + public PointImmutable getPosition(); + + /** + * Returns the required surface size in pixel. + */ + public DimensionImmutable getSurfaceSize(); + + /** + * Returns the device default {@link FovHVHalves} per eye. + */ + public FovHVHalves[] getDefaultFOV(); + + /** Start or stop sensors. Returns true if action was successful, otherwise false. */ + public boolean startSensors(boolean start); + + /** Return true if sensors have been started, false otherwise */ + public boolean getSensorsStarted(); + + /** + * Create a new {@link StereoDeviceRenderer} instance. + * + * @param distortionBits {@link StereoDeviceRenderer} distortion bits, e.g. {@link StereoDeviceRenderer#DISTORTION_BARREL}, etc. + * @param textureCount desired texture count for post-processing, see {@link StereoDeviceRenderer#getTextureCount()} and {@link StereoDeviceRenderer#ppRequired()} + * @param eyePositionOffset eye position offset, e.g. {@link #DEFAULT_EYE_POSITION_OFFSET}. + * @param eyeFov FovHVHalves[2] field-of-view per eye + * @param pixelsPerDisplayPixel + * @param textureUnit + * @return + */ + public StereoDeviceRenderer createRenderer(final int distortionBits, + final int textureCount, final float[] eyePositionOffset, + final FovHVHalves[] eyeFov, final float pixelsPerDisplayPixel, final int textureUnit); +} diff --git a/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoDeviceFactory.java b/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoDeviceFactory.java new file mode 100644 index 000000000..d9054ce28 --- /dev/null +++ b/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoDeviceFactory.java @@ -0,0 +1,64 @@ +/** + * Copyright 2014 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.util.stereo; + +import com.jogamp.common.util.ReflectionUtil; + +/** + * Platform agnostic {@link StereoDevice} factory. + */ +public abstract class StereoDeviceFactory { + private static final String OVRStereoDeviceClazzName = "jogamp.opengl.oculusvr.OVRStereoDeviceFactory"; + private static final Object[] ctorArgs; + private static final String isAvailableMethodName = "isAvailable"; + + static { + ctorArgs = new Object[6]; + ctorArgs[0] = null; + + } + public static StereoDeviceFactory createDefaultFactory() { + final ClassLoader cl = StereoDeviceFactory.class.getClassLoader(); + final StereoDeviceFactory sink = createFactory(cl, OVRStereoDeviceClazzName); + if( null == sink ) { + // sink = create(cl, ANYOTHERCLAZZNAME); + } + return sink; + } + + public static StereoDeviceFactory createFactory(final ClassLoader cl, final String implName) { + try { + if(((Boolean)ReflectionUtil.callStaticMethod(implName, isAvailableMethodName, null, null, cl)).booleanValue()) { + return (StereoDeviceFactory) ReflectionUtil.createInstance(implName, cl); + } + } catch (final Throwable t) { if(StereoDevice.DEBUG) { System.err.println("Caught "+t.getClass().getName()+": "+t.getMessage()); t.printStackTrace(); } } + return null; + } + + public abstract StereoDevice createDevice(final int deviceIndex, final boolean verbose); +} diff --git a/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoDeviceRenderer.java b/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoDeviceRenderer.java new file mode 100644 index 000000000..fd94f6bc3 --- /dev/null +++ b/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoDeviceRenderer.java @@ -0,0 +1,235 @@ +/** + * Copyright 2014 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.util.stereo; + +import javax.media.nativewindow.util.DimensionImmutable; +import javax.media.nativewindow.util.RectangleImmutable; +import javax.media.opengl.GL; + +/** + * Stereoscopic device rendering interface. + * <p> + * The following pseudo-code describes how to implement a renderer + * using a {@link StereoDeviceRenderer}. + * See {@link StereoClientRenderer} which implements the following: + * <ul> + * <li>device.{@link #beginFrame(GL)}</li> + * <li>For both eyes:<ul> + * <li>device.{@link #updateEyePose(int)}</li> + * <li>if device.{@link #ppRequired()}: Set the render target, e.g. FBO</li> + * <li>Set the viewport using {@link Eye#getViewport()}</li> + * <li>{@link StereoGLEventListener#reshapeForEye(javax.media.opengl.GLAutoDrawable, int, int, int, int, EyeParameter, EyePose) upstream.reshapeEye(..)}</li> + * <li>{@link StereoGLEventListener#display(javax.media.opengl.GLAutoDrawable, int) upstream.display(..)}.</li> + * </ul></li> + * <li>Reset the viewport</li> + * <li>If device.{@link #ppRequired()}:<ul> + * <li>device.{@link #ppBegin(GL)}</li> + * <li>Use render target, e.g. FBO's texture</li> + * <li>device.{@link #ppBothEyes(GL)} or device.{@link #ppOneEye(GL, int)} for both eyes</li> + * <li>device.{@link #ppEnd(GL)}</li> + * </ul></li> + * <li>device.{@link #endFrame(GL)}</li> + * <ul> + */ +public interface StereoDeviceRenderer { + /** + * Distortion Bit: Barrel distortion compensating lens pincushion distortion + */ + public static final int DISTORTION_BARREL = 1 << 0; + + /** + * Distortion Bit: Chromatic distortion compensating lens chromatic aberration. + */ + public static final int DISTORTION_CHROMATIC = 1 << 1; + + /** + * Distortion Bit: Vignette distortion compensating lens chromatic aberration. + */ + public static final int DISTORTION_VIGNETTE = 1 << 2; + + /** + * Distortion Bit: Timewarp distortion technique to predict + * {@link EyePose} movement to reduce latency. + * <p> + * FIXME: Explanation needs refinement! + * </p> + */ + public static final int DISTORTION_TIMEWARP = 1 << 3; + + + /** Returns the {@link StereoDevice} of this {@link StereoDeviceRenderer} instance. */ + public StereoDevice getDevice(); + + /** + * Interface describing one eye of the stereoscopic device, + * see {@link StereoDeviceRenderer#getEye(int)}. + */ + public static interface Eye { + /** + * Returns the viewport for this eye. + */ + public RectangleImmutable getViewport(); + /** + * Returns the {@link EyeParameter} of this eye. + */ + public EyeParameter getEyeParameter(); + /** + * Returns the last {@link EyePose} of this eye. + */ + public EyePose getLastEyePose(); + } + + /** + * Returns the {@link Eye} instance for the denoted <code>eyeNum</code>. + */ + public Eye getEye(final int eyeNum); + + /** + * Updates the {@link Eye#getLastEyePose()} + * for the denoted <code>eyeNum</code>. + */ + public EyePose updateEyePose(final int eyeNum); + + /** + * Returns distortion compensation bits, e.g. {@link #DISTORTION_BARREL}, + * in case the stereoscopic display requires such, i.e. in case lenses are utilized. + * <p> + * Distortion requires {@link #ppRequired() post-processing}. + * </p> + */ + public int getDistortionBits(); + + /** + * Method returns <code>true</code> if using <i>side-by-side</i> (SBS) + * stereoscopic images, otherwise <code>false</code>. + * <p> + * SBS requires that both eye's images are presented + * <i>side-by-side</i> in the final framebuffer. + * </p> + * <p> + * Either the renderer presents the images <i>side-by-side</i> according to the {@link Eye#getViewport() eye's viewport}, + * or {@link #ppRequired() post-processing} is utilized to merge {@link #getTextureCount() textures} + * to a <i>side-by-side</i> configuration. + * </p> + */ + public boolean usesSideBySideStereo(); + + /** + * Returns the unified surface size of one eye's a single image in pixel units. + */ + public DimensionImmutable getSingleSurfaceSize(); + + /** + * Returns the total surface size required for the complete images in pixel units. + * <p> + * If {@link #usesSideBySideStereo()} the total size spans over both {@link #getSingleSurfaceSize()}, side-by-side. + * </p> + * <p> + * Otherwise the size is equal to {@link #getSingleSurfaceSize()}. + * </p> + */ + public DimensionImmutable getTotalSurfaceSize(); + + /** + * Returns the used texture-image count for post-processing, see {@link #ppRequired()}. + * <p> + * In case the renderer does not support multiple textures for post-processing, + * or no post-processing at all, method returns zero despite the request + * from {@link StereoDevice#createRenderer(int, int, float[], com.jogamp.opengl.math.FovHVHalves[], float)}. + * </p> + */ + public int getTextureCount(); + + /** Returns the desired texture-image unit for post-processing, see {@link #ppRequired()}. */ + public int getTextureUnit(); + + /** Initialize OpenGL related resources */ + public void init(final GL gl); + + /** Release all OpenGL related resources */ + public void dispose(final GL gl); + + /** Notifying that a new frame is about to start. */ + public void beginFrame(final GL gl); + + /** Notifying that the frame has been rendered completely. */ + public void endFrame(final GL gl); + + /** + * Returns <code>true</code> if stereoscopic post-processing is required, + * otherwise <code>false</code>. + * <p> + * Stereoscopic post-processing is usually required if: + * <ul> + * <li>one of the <i>distortion</i> modes are set, i.e. {@link #usesBarrelDistortion()}</li> + * <li>texture-images are being used, see {@link #getTextureCount()}</li> + * </ul> + * </p> + * <p> + * If stereoscopic post-processing is used + * the following post-processing methods must be called to before {@link #endFrame()}: + * <ul> + * <li>{@link #ppBegin(GL)}</li> + * <li>{@link #ppBothEyes(GL)} or {@link #ppOneEye(GL, int)} for both eyes</li> + * <li>{@link #ppEnd(GL)}</li> + * </ul> + * </p> + */ + public boolean ppRequired(); + + /** + * Begin stereoscopic post-processing, see {@link #ppRequired()}. + * <p> + * {@link #updateEyePose(int)} for both eyes must be called upfront + * when rendering upstream {@link StereoGLEventListener}. + * </p> + * + * @param gl + */ + public void ppBegin(final GL gl); + + /** + * Performs stereoscopic post-processing for both eyes, see {@link #ppRequired()}. + * @param gl + */ + public void ppBothEyes(final GL gl); + + /** + * Performs stereoscopic post-processing for one eye, see {@link #ppRequired()}. + * @param gl + * @param eyeNum + */ + public void ppOneEye(final GL gl, final int eyeNum); + + /** + * End stereoscopic post-processing, see {@link #ppRequired()}. + * @param gl + */ + public void ppEnd(final GL gl); + +} diff --git a/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoRendererListener.java b/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoGLEventListener.java index 5e6e40a08..ec580cbf9 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoRendererListener.java +++ b/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoGLEventListener.java @@ -31,15 +31,16 @@ import javax.media.opengl.GLAutoDrawable; import javax.media.opengl.GLEventListener; import com.jogamp.opengl.math.FloatUtil; -import com.jogamp.opengl.util.CustomRendererListener; +import com.jogamp.opengl.util.CustomGLEventListener; /** - * Extended {@link GLEventListener} and {@link CustomRendererListener} interface + * Extended {@link GLEventListener} and {@link CustomGLEventListener} interface * supporting stereoscopic client rendering. */ -public interface StereoRendererListener extends CustomRendererListener { +public interface StereoGLEventListener extends CustomGLEventListener { /** - * Stereo capable specialization of {@link #reshape(GLAutoDrawable, int, int, int, int)}. + * Stereo capable specialization of {@link #reshape(GLAutoDrawable, int, int, int, int)} + * for one {@link StereoDeviceRenderer.Eye}. * <p> * Called by the stereo renderer before each {@link #display(GLAutoDrawable)} * or {@link #display(GLAutoDrawable, int)} call. @@ -66,8 +67,8 @@ public interface StereoRendererListener extends CustomRendererListener { * @param eyePose current eye position and orientation * @see FloatUtil#makePerspective(float[], int, boolean, com.jogamp.opengl.math.FloatUtil.FovHVHalves, float, float) */ - public void reshapeEye(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height, - final EyeParameter eyeParam, final EyePose eyePose); + public void reshapeForEye(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height, + final EyeParameter eyeParam, final EyePose eyePose); } diff --git a/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoUtil.java b/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoUtil.java new file mode 100644 index 000000000..280d99233 --- /dev/null +++ b/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoUtil.java @@ -0,0 +1,125 @@ +/** + * Copyright 2014 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.util.stereo; + +import com.jogamp.opengl.math.FloatUtil; +import com.jogamp.opengl.math.Quaternion; +import com.jogamp.opengl.math.VectorUtil; +import com.jogamp.opengl.util.CustomGLEventListener; +import com.jogamp.opengl.util.stereo.StereoDeviceRenderer.Eye; + +public class StereoUtil { + + /** See {@link StereoDeviceRenderer#getDistortionBits()}. */ + public static boolean usesBarrelDistortion(final int distortionBits) { return 0 != ( distortionBits & StereoDeviceRenderer.DISTORTION_BARREL ) ; } + /** See {@link StereoDeviceRenderer#getDistortionBits()}. */ + public static boolean usesTimewarpDistortion(final int distortionBits) { return 0 != ( distortionBits & StereoDeviceRenderer.DISTORTION_TIMEWARP ) ; } + /** See {@link StereoDeviceRenderer#getDistortionBits()}. */ + public static boolean usesChromaticDistortion(final int distortionBits) { return 0 != ( distortionBits & StereoDeviceRenderer.DISTORTION_CHROMATIC ) ; } + /** See {@link StereoDeviceRenderer#getDistortionBits()}. */ + public static boolean usesVignetteDistortion(final int distortionBits) { return 0 != ( distortionBits & StereoDeviceRenderer.DISTORTION_VIGNETTE ) ; } + + /** See {@link StereoDeviceRenderer#getDistortionBits()}. */ + public static String distortionBitsToString(final int distortionBits) { + boolean appendComma = false; + final StringBuilder sb = new StringBuilder(); + if( usesBarrelDistortion(distortionBits) ) { + if( appendComma ) { sb.append(", "); }; + sb.append("barrell"); appendComma=true; + } + if( usesVignetteDistortion(distortionBits) ) { + if( appendComma ) { sb.append(", "); }; + sb.append("vignette"); appendComma=true; + } + if( usesChromaticDistortion(distortionBits) ) { + if( appendComma ) { sb.append(", "); }; + sb.append("chroma"); appendComma=true; + } + if( usesTimewarpDistortion(distortionBits) ) { + if( appendComma ) { sb.append(", "); }; + sb.append("timewarp"); appendComma=true; + } + return sb.toString(); + } + + /** + * Calculates the <i>Side By Side</i>, SBS, projection- and modelview matrix for one eye. + * <p> + * {@link #updateEyePose(int)} must be called upfront. + * </p> + * <p> + * This method merely exist as an example implementation to compute the matrices, + * which shall be adopted by the + * {@link CustomGLEventListener#reshape(javax.media.opengl.GLAutoDrawable, int, int, int, int, EyeParameter, EyePose) upstream client code}. + * </p> + * @param eyeNum eye denominator + * @param zNear frustum near value + * @param zFar frustum far value + * @param mat4Projection float[16] projection matrix result + * @param mat4Modelview float[16] modelview matrix result + */ + public static void getSBSUpstreamPMV(final Eye eye, final float zNear, final float zFar, + final float[] mat4Projection, final float[] mat4Modelview) { + final float[] mat4Tmp1 = new float[16]; + final float[] mat4Tmp2 = new float[16]; + final float[] vec3Tmp1 = new float[3]; + final float[] vec3Tmp2 = new float[3]; + final float[] vec3Tmp3 = new float[3]; + + final EyeParameter eyeParam = eye.getEyeParameter(); + final EyePose eyePose = eye.getLastEyePose(); + + // + // Projection + // + FloatUtil.makePerspective(mat4Projection, 0, true, eyeParam.fovhv, zNear, zFar); + + // + // Modelview + // + final Quaternion rollPitchYaw = new Quaternion(); + // private final float eyeYaw = FloatUtil.PI; // 180 degrees in radians + // rollPitchYaw.rotateByAngleY(eyeYaw); + final float[] shiftedEyePos = rollPitchYaw.rotateVector(vec3Tmp1, 0, eyePose.position, 0); + VectorUtil.addVec3(shiftedEyePos, shiftedEyePos, eyeParam.positionOffset); + + rollPitchYaw.mult(eyePose.orientation); + final float[] up = rollPitchYaw.rotateVector(vec3Tmp2, 0, VectorUtil.VEC3_UNIT_Y, 0); + final float[] forward = rollPitchYaw.rotateVector(vec3Tmp3, 0, VectorUtil.VEC3_UNIT_Z_NEG, 0); + final float[] center = VectorUtil.addVec3(forward, shiftedEyePos, forward); + + final float[] mLookAt = FloatUtil.makeLookAt(mat4Tmp2, 0, shiftedEyePos, 0, center, 0, up, 0, mat4Tmp1); + final float[] mViewAdjust = FloatUtil.makeTranslation(mat4Modelview, true, + eyeParam.distNoseToPupilX, + eyeParam.distMiddleToPupilY, + eyeParam.eyeReliefZ); + + /* mat4Modelview = */ FloatUtil.multMatrix(mViewAdjust, mLookAt); + } + +} diff --git a/src/jogl/classes/jogamp/opengl/GLDrawableHelper.java b/src/jogl/classes/jogamp/opengl/GLDrawableHelper.java index aea9a5b7b..e3ce7001e 100644 --- a/src/jogl/classes/jogamp/opengl/GLDrawableHelper.java +++ b/src/jogl/classes/jogamp/opengl/GLDrawableHelper.java @@ -649,8 +649,10 @@ public class GLDrawableHelper { public final void display(final GLAutoDrawable drawable) { displayImpl(drawable); + // runForAllGLEventListener(drawable, displayAction); if( glRunnables.size()>0 && !execGLRunnables(drawable) ) { // glRunnables volatile OK; execGL.. only executed if size > 0 displayImpl(drawable); + // runForAllGLEventListener(drawable, displayAction); } } private final void displayImpl(final GLAutoDrawable drawable) { @@ -669,6 +671,31 @@ public class GLDrawableHelper { } } + public static interface GLEventListenerAction { + public void run(final GLAutoDrawable drawable, final GLEventListener listener); + } + /** + private static GLEventListenerAction displayAction = new GLEventListenerAction() { + public void run(final GLAutoDrawable drawable, final GLEventListener listener) { + listener.display(drawable); + } }; */ + + public final void runForAllGLEventListener(final GLAutoDrawable drawable, final GLEventListenerAction action) { + synchronized(listenersLock) { + final ArrayList<GLEventListener> _listeners = listeners; + final int listenerCount = _listeners.size(); + for (int i=0; i < listenerCount; i++) { + final GLEventListener listener = _listeners.get(i) ; + // GLEventListener may need to be init, + // in case this one is added after the realization of the GLAutoDrawable + if( listenersToBeInit.remove(listener) ) { + init( listener, drawable, true /* sendReshape */, listenersToBeInit.size() + 1 == listenerCount /* setViewport if 1st init */ ); + } + action.run(drawable, listener); + } + } + } + private final void reshape(final GLEventListener listener, final GLAutoDrawable drawable, final int x, final int y, final int width, final int height, final boolean setViewport, final boolean checkInit) { if(checkInit) { diff --git a/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java b/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java index 9cfa94a60..f36681e3b 100644 --- a/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java +++ b/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java @@ -36,7 +36,6 @@ import java.util.Map; import javax.media.nativewindow.AbstractGraphicsDevice; import javax.media.opengl.GL; -import javax.media.opengl.GL2; import javax.media.opengl.GL2GL3; import javax.media.opengl.GLContext; import javax.media.opengl.GLDrawable; diff --git a/src/oculusvr/classes/com/jogamp/opengl/oculusvr/OVRSBSRendererDualFBO.java b/src/oculusvr/classes/com/jogamp/opengl/oculusvr/OVRSBSRendererDualFBO.java deleted file mode 100644 index 9af3397cf..000000000 --- a/src/oculusvr/classes/com/jogamp/opengl/oculusvr/OVRSBSRendererDualFBO.java +++ /dev/null @@ -1,233 +0,0 @@ -/** - * Copyright 2014 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.oculusvr; - -import javax.media.opengl.GL; -import javax.media.opengl.GL2ES2; -import javax.media.opengl.GLAutoDrawable; -import javax.media.opengl.GLEventListener; - -import jogamp.opengl.oculusvr.OVRDistortion; - -import com.jogamp.oculusvr.OVR; -import com.jogamp.oculusvr.ovrFrameTiming; -import com.jogamp.opengl.FBObject; -import com.jogamp.opengl.FBObject.Attachment; -import com.jogamp.opengl.FBObject.TextureAttachment; -import com.jogamp.opengl.FBObject.Attachment.Type; -import com.jogamp.opengl.util.CustomRendererListener; -import com.jogamp.opengl.util.stereo.StereoRendererListener; - -/** - * OculusVR (OVR) <i>Side By Side</i> Distortion Renderer utilizing {@link OVRDistortion} - * implementing {@link GLEventListener} for convenience. - * <p> - * Implementation renders an {@link StereoRendererListener} instance - * side-by-side using two {@link FBObject}s according to {@link OVRDistortion}. - * </p> - */ -public class OVRSBSRendererDualFBO implements GLEventListener { - private final OVRDistortion dist; - private final boolean ownsDist; - private final StereoRendererListener upstream; - private final FBObject[] fbos; - private final int magFilter; - private final int minFilter; - - private int numSamples; - private final TextureAttachment[] fboTexs; - - public OVRSBSRendererDualFBO(final OVRDistortion dist, final boolean ownsDist, final StereoRendererListener upstream, - final int magFilter, final int minFilter, final int numSamples) { - this.dist = dist; - this.ownsDist = ownsDist; - this.upstream = upstream; - this.fbos = new FBObject[2]; - this.fbos[0] = new FBObject(); - this.fbos[1] = new FBObject(); - this.magFilter = magFilter; - this.minFilter = minFilter; - - this.numSamples = numSamples; - this.fboTexs = new TextureAttachment[2]; - } - - private void initFBOs(final GL gl, final int width, final int height) { - // remove all texture attachments, since MSAA uses just color-render-buffer - // and non-MSAA uses texture2d-buffer - fbos[0].detachAllColorbuffer(gl); - fbos[1].detachAllColorbuffer(gl); - - fbos[0].reset(gl, width, height, numSamples, false); - fbos[1].reset(gl, width, height, numSamples, false); - if(fbos[0].getNumSamples() != fbos[1].getNumSamples()) { - throw new InternalError("sample size mismatch: \n\t0: "+fbos[0]+"\n\t1: "+fbos[1]); - } - numSamples = fbos[0].getNumSamples(); - - if(numSamples>0) { - fbos[0].attachColorbuffer(gl, 0, true); // MSAA requires alpha - fbos[0].attachRenderbuffer(gl, Type.DEPTH, 24); - final FBObject ssink0 = new FBObject(); - { - ssink0.reset(gl, width, height); - ssink0.attachTexture2D(gl, 0, false, magFilter, minFilter, GL.GL_CLAMP_TO_EDGE, GL.GL_CLAMP_TO_EDGE); - ssink0.attachRenderbuffer(gl, Attachment.Type.DEPTH, 24); - } - fbos[0].setSamplingSink(ssink0); - fbos[0].resetSamplingSink(gl); // validate - fboTexs[0] = fbos[0].getSamplingSink(); - - fbos[1].attachColorbuffer(gl, 0, true); // MSAA requires alpha - fbos[1].attachRenderbuffer(gl, Type.DEPTH, 24); - final FBObject ssink1 = new FBObject(); - { - ssink1.reset(gl, width, height); - ssink1.attachTexture2D(gl, 0, false, magFilter, minFilter, GL.GL_CLAMP_TO_EDGE, GL.GL_CLAMP_TO_EDGE); - ssink1.attachRenderbuffer(gl, Attachment.Type.DEPTH, 24); - } - fbos[1].setSamplingSink(ssink1); - fbos[1].resetSamplingSink(gl); // validate - fboTexs[1] = fbos[1].getSamplingSink(); - } else { - fboTexs[0] = fbos[0].attachTexture2D(gl, 0, false, magFilter, minFilter, GL.GL_CLAMP_TO_EDGE, GL.GL_CLAMP_TO_EDGE); - fbos[0].attachRenderbuffer(gl, Type.DEPTH, 24); - fboTexs[1] = fbos[1].attachTexture2D(gl, 0, false, magFilter, minFilter, GL.GL_CLAMP_TO_EDGE, GL.GL_CLAMP_TO_EDGE); - fbos[1].attachRenderbuffer(gl, Type.DEPTH, 24); - } - fbos[0].unbind(gl); - fbos[1].unbind(gl); - } - - @SuppressWarnings("unused") - private void resetFBOs(final GL gl, final int width, final int height) { - fbos[0].reset(gl, width, height, numSamples, true); - fbos[1].reset(gl, width, height, numSamples, true); - if(fbos[0].getNumSamples() != fbos[1].getNumSamples()) { - throw new InternalError("sample size mismatch: \n\t0: "+fbos[0]+"\n\t1: "+fbos[1]); - } - numSamples = fbos[0].getNumSamples(); - if(numSamples>0) { - fboTexs[0] = fbos[0].getSamplingSink(); - fboTexs[1] = fbos[1].getSamplingSink(); - } else { - fboTexs[0] = (TextureAttachment) fbos[0].getColorbuffer(0); - fboTexs[1] = (TextureAttachment) fbos[1].getColorbuffer(0); - } - } - - @Override - public void init(final GLAutoDrawable drawable) { - final GL2ES2 gl = drawable.getGL().getGL2ES2(); - dist.init(gl); - - // We will do some offscreen rendering, setup FBO... - if( null != upstream ) { - final int[] textureSize = dist.textureSize; - initFBOs(gl, textureSize[0], textureSize[1]); - upstream.init(drawable); - } - - gl.setSwapInterval(1); - } - - @Override - public void dispose(final GLAutoDrawable drawable) { - final GL2ES2 gl = drawable.getGL().getGL2ES2(); - // FIXME complete release - if( null != upstream ) { - upstream.dispose(drawable); - fbos[0].destroy(gl); - fbos[1].destroy(gl); - } - if( ownsDist ) { - dist.dispose(gl); - } - } - - @Override - public void display(final GLAutoDrawable drawable) { - final ovrFrameTiming frameTiming = OVR.ovrHmd_BeginFrameTiming(dist.hmdCtx, 0); - - final GL2ES2 gl = drawable.getGL().getGL2ES2(); - if(0 < numSamples) { - gl.glEnable(GL.GL_MULTISAMPLE); - } - - // FIXME: Instead of setting the viewport, - // it's better to change the projection matrix! - if( null != upstream ) { - for(int eyeNum=0; eyeNum<2; eyeNum++) { - // final ovrPosef eyeRenderPose = OVR.ovrHmd_GetEyePose(hmdCtx, eyeNum); - // final float[] eyePos = OVRUtil.getVec3f(eyeRenderPose.getPosition()); - fbos[eyeNum].bind(gl); - - final OVRDistortion.EyeData eyeDist = dist.eyes[eyeNum]; - final int[] viewport = eyeDist.viewport; - gl.glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); - - dist.updateEyePose(eyeNum); - upstream.reshapeEye(drawable, viewport[0], viewport[1], viewport[2], viewport[3], - dist.getEyeParam(eyeNum), dist.updateEyePose(eyeNum)); - upstream.display(drawable, eyeNum > 0 ? CustomRendererListener.DISPLAY_REPEAT : 0); - fbos[eyeNum].unbind(gl); - } - gl.glViewport(0, 0, drawable.getSurfaceWidth(), drawable.getSurfaceHeight()); - } - - gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - gl.glClear(GL.GL_COLOR_BUFFER_BIT); - gl.glActiveTexture(GL.GL_TEXTURE0 + dist.texUnit0.intValue()); - - if( null != upstream ) { - dist.displayOneEyePre(gl, frameTiming.getTimewarpPointSeconds()); - fbos[0].use(gl, fboTexs[0]); - dist.displayOneEye(gl, 0); - fbos[0].unuse(gl); - fbos[1].use(gl, fboTexs[1]); - dist.displayOneEye(gl, 1); - fbos[1].unuse(gl); - dist.displayOneEyePost(gl); - } else { - dist.display(gl, frameTiming.getTimewarpPointSeconds()); - } - - if( !drawable.getAutoSwapBufferMode() ) { - drawable.swapBuffers(); - } - OVR.ovrHmd_EndFrameTiming(dist.hmdCtx); - } - - @Override - public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) { - if( !drawable.getAutoSwapBufferMode() ) { - final GL2ES2 gl = drawable.getGL().getGL2ES2(); - gl.glViewport(0, 0, width, height); - } - } -} diff --git a/src/oculusvr/classes/com/jogamp/opengl/oculusvr/OVRSBSRendererSingleFBO.java b/src/oculusvr/classes/com/jogamp/opengl/oculusvr/OVRSBSRendererSingleFBO.java deleted file mode 100644 index b18d1634e..000000000 --- a/src/oculusvr/classes/com/jogamp/opengl/oculusvr/OVRSBSRendererSingleFBO.java +++ /dev/null @@ -1,206 +0,0 @@ -/** - * Copyright 2014 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.oculusvr; - -import javax.media.opengl.GL; -import javax.media.opengl.GL2ES2; -import javax.media.opengl.GLAutoDrawable; -import javax.media.opengl.GLEventListener; - -import jogamp.opengl.oculusvr.OVRDistortion; - -import com.jogamp.oculusvr.OVR; -import com.jogamp.oculusvr.ovrFrameTiming; -import com.jogamp.opengl.FBObject; -import com.jogamp.opengl.FBObject.Attachment; -import com.jogamp.opengl.FBObject.TextureAttachment; -import com.jogamp.opengl.FBObject.Attachment.Type; -import com.jogamp.opengl.util.CustomRendererListener; -import com.jogamp.opengl.util.stereo.StereoRendererListener; - -/** - * OculusVR (OVR) <i>Side By Side</i> Distortion Renderer utilizing {@link OVRDistortion} - * implementing {@link GLEventListener} for convenience. - * <p> - * Implementation renders an {@link StereoRendererListener} instance - * side-by-side within one {@link FBObject} according to {@link OVRDistortion}. - * </p> - */ -public class OVRSBSRendererSingleFBO implements GLEventListener { - - private final OVRDistortion dist; - private final boolean ownsDist; - private final StereoRendererListener upstream; - private final FBObject fbo; - private final int magFilter; - private final int minFilter; - - private int numSamples; - private TextureAttachment fboTex; - - /** - * @param dist {@link OVRDistortion} instance used for rendering. - * @param ownsDist if true, {@link OVRDistortion#dispose(GL2ES2)} is issued on this instance's {@link #dispose(GLAutoDrawable)} method, otherwise not. - * @param upstream the upstream {@link StereoRendererListener}, a.k.a the <i>content</i> to render for both eyes - * @param magFilter if > 0 value for {@link GL#GL_TEXTURE_MAG_FILTER} - * @param minFilter if > 0 value for {@link GL#GL_TEXTURE_MIN_FILTER} - * @param numSamples sample-count, if > 0 using multisampling w/ given samples, otherwise no multisampling applies - */ - public OVRSBSRendererSingleFBO(final OVRDistortion dist, final boolean ownsDist, final StereoRendererListener upstream, - final int magFilter, final int minFilter, final int numSamples) { - this.dist = dist; - this.ownsDist = ownsDist; - this.upstream = upstream; - this.fbo = new FBObject(); - this.magFilter = magFilter; - this.minFilter = minFilter; - - this.numSamples = numSamples; - } - - private void initFBOs(final GL gl, final int width, final int height) { - // remove all texture attachments, since MSAA uses just color-render-buffer - // and non-MSAA uses texture2d-buffer - fbo.detachAllColorbuffer(gl); - - fbo.reset(gl, width, height, numSamples, false); - numSamples = fbo.getNumSamples(); - - if(numSamples>0) { - fbo.attachColorbuffer(gl, 0, true); // MSAA requires alpha - fbo.attachRenderbuffer(gl, Type.DEPTH, 24); - final FBObject ssink = new FBObject(); - { - ssink.reset(gl, width, height); - ssink.attachTexture2D(gl, 0, false, magFilter, minFilter, GL.GL_CLAMP_TO_EDGE, GL.GL_CLAMP_TO_EDGE); - ssink.attachRenderbuffer(gl, Attachment.Type.DEPTH, 24); - } - fbo.setSamplingSink(ssink); - fbo.resetSamplingSink(gl); // validate - fboTex = fbo.getSamplingSink(); - } else { - fboTex = fbo.attachTexture2D(gl, 0, false, magFilter, minFilter, GL.GL_CLAMP_TO_EDGE, GL.GL_CLAMP_TO_EDGE); - fbo.attachRenderbuffer(gl, Type.DEPTH, 24); - } - fbo.unbind(gl); - } - - @SuppressWarnings("unused") - private void resetFBOs(final GL gl, final int width, final int height) { - fbo.reset(gl, width, height, numSamples, true); - numSamples = fbo.getNumSamples(); - if(numSamples>0) { - fboTex = fbo.getSamplingSink(); - } else { - fboTex = (TextureAttachment) fbo.getColorbuffer(0); - } - } - - @Override - public void init(final GLAutoDrawable drawable) { - final GL2ES2 gl = drawable.getGL().getGL2ES2(); - dist.init(gl); - - // We will do some offscreen rendering, setup FBO... - if( null != upstream ) { - final int[] textureSize = dist.textureSize; - initFBOs(gl, textureSize[0], textureSize[1]); - upstream.init(drawable); - } - - gl.setSwapInterval(1); - } - - @Override - public void dispose(final GLAutoDrawable drawable) { - final GL2ES2 gl = drawable.getGL().getGL2ES2(); - // FIXME complete release - if( null != upstream ) { - upstream.dispose(drawable); - fbo.destroy(gl); - } - if( ownsDist ) { - dist.dispose(gl); - } - } - - @Override - public void display(final GLAutoDrawable drawable) { - final ovrFrameTiming frameTiming = OVR.ovrHmd_BeginFrameTiming(dist.hmdCtx, 0); - - final GL2ES2 gl = drawable.getGL().getGL2ES2(); - if(0 < numSamples) { - gl.glEnable(GL.GL_MULTISAMPLE); - } - - // FIXME: Instead of setting the viewport, - // it's better to change the projection matrix! - if( null != upstream ) { - fbo.bind(gl); - - for(int eyeNum=0; eyeNum<2; eyeNum++) { - // final ovrPosef eyeRenderPose = OVR.ovrHmd_GetEyePose(hmdCtx, eyeNum); - // final float[] eyePos = OVRUtil.getVec3f(eyeRenderPose.getPosition()); - final OVRDistortion.EyeData eyeDist = dist.eyes[eyeNum]; - final int[] viewport = eyeDist.viewport; - gl.glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); - - upstream.reshapeEye(drawable, viewport[0], viewport[1], viewport[2], viewport[3], - dist.getEyeParam(eyeNum), dist.updateEyePose(eyeNum)); - upstream.display(drawable, eyeNum > 0 ? CustomRendererListener.DISPLAY_REPEAT | CustomRendererListener.DISPLAY_DONTCLEAR : 0); - } - fbo.unbind(gl); - gl.glViewport(0, 0, drawable.getSurfaceWidth(), drawable.getSurfaceHeight()); - } - - gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - gl.glClear(GL.GL_COLOR_BUFFER_BIT); - gl.glActiveTexture(GL.GL_TEXTURE0 + dist.texUnit0.intValue()); - - if( null != upstream ) { - fbo.use(gl, fboTex); - dist.display(gl, frameTiming.getTimewarpPointSeconds()); - fbo.unuse(gl); - } else { - dist.display(gl, frameTiming.getTimewarpPointSeconds()); - } - - if( !drawable.getAutoSwapBufferMode() ) { - drawable.swapBuffers(); - } - OVR.ovrHmd_EndFrameTiming(dist.hmdCtx); - } - - @Override - public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) { - if( !drawable.getAutoSwapBufferMode() ) { - final GL2ES2 gl = drawable.getGL().getGL2ES2(); - gl.glViewport(0, 0, width, height); - } - } -} diff --git a/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRDistortion.java b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRDistortion.java deleted file mode 100644 index c32270ac4..000000000 --- a/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRDistortion.java +++ /dev/null @@ -1,690 +0,0 @@ -/** - * Copyright 2014 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 jogamp.opengl.oculusvr; - -import java.nio.FloatBuffer; -import java.nio.ShortBuffer; - -import javax.media.opengl.GL; -import javax.media.opengl.GL2ES2; -import javax.media.opengl.GLArrayData; -import javax.media.opengl.GLEventListener; -import javax.media.opengl.GLException; -import javax.media.opengl.GLUniformData; - -import jogamp.common.os.PlatformPropsImpl; - -import com.jogamp.common.nio.Buffers; -import com.jogamp.common.os.Platform; -import com.jogamp.oculusvr.OVR; -import com.jogamp.oculusvr.OVRException; -import com.jogamp.oculusvr.OvrHmdContext; -import com.jogamp.oculusvr.ovrDistortionMesh; -import com.jogamp.oculusvr.ovrDistortionVertex; -import com.jogamp.oculusvr.ovrEyeRenderDesc; -import com.jogamp.oculusvr.ovrFovPort; -import com.jogamp.oculusvr.ovrMatrix4f; -import com.jogamp.oculusvr.ovrPosef; -import com.jogamp.oculusvr.ovrRecti; -import com.jogamp.oculusvr.ovrSizei; -import com.jogamp.oculusvr.ovrVector2f; -import com.jogamp.oculusvr.ovrVector3f; -import com.jogamp.opengl.JoglVersion; -import com.jogamp.opengl.math.FloatUtil; -import com.jogamp.opengl.math.Quaternion; -import com.jogamp.opengl.math.VectorUtil; -import com.jogamp.opengl.util.CustomRendererListener; -import com.jogamp.opengl.util.GLArrayDataServer; -import com.jogamp.opengl.util.glsl.ShaderCode; -import com.jogamp.opengl.util.glsl.ShaderProgram; -import com.jogamp.opengl.util.stereo.EyeParameter; -import com.jogamp.opengl.util.stereo.EyePose; - -/** - * OculusVR Distortion Data and OpenGL Renderer Utility - */ -public class OVRDistortion { - public static final float[] VEC3_UP = { 0f, 1f, 0f }; - public static final float[] VEC3_FORWARD = { 0f, 0f, -1f }; - - private static final String shaderPrefix01 = "dist01"; - private static final String shaderTimewarpSuffix = "_timewarp"; - private static final String shaderChromaSuffix = "_chroma"; - private static final String shaderPlainSuffix = "_plain"; - - public static boolean useTimewarp(final int distortionCaps) { return 0 != ( distortionCaps & OVR.ovrDistortionCap_TimeWarp ) ; } - public static boolean useChromatic(final int distortionCaps) { return 0 != ( distortionCaps & OVR.ovrDistortionCap_Chromatic ) ; } - public static boolean useVignette(final int distortionCaps) { return 0 != ( distortionCaps & OVR.ovrDistortionCap_Vignette ) ; } - - public static class EyeData { - public final int eyeName; - public final int distortionCaps; - public final int vertexCount; - public final int indexCount; - public final int[/*4*/] viewport; - - public final GLUniformData eyeToSourceUVScale; - public final GLUniformData eyeToSourceUVOffset; - public final GLUniformData eyeRotationStart; - public final GLUniformData eyeRotationEnd; - - /** 2+2+2+2+2: { vec2 position, vec2 color, vec2 texCoordR, vec2 texCoordG, vec2 texCoordB } */ - public final GLArrayDataServer iVBO; - public final GLArrayData vboPos, vboParams, vboTexCoordsR, vboTexCoordsG, vboTexCoordsB; - public final GLArrayDataServer indices; - - public final ovrEyeRenderDesc ovrEyeDesc; - public final ovrFovPort ovrEyeFov; - public final EyeParameter eyeParameter; - - public ovrPosef ovrEyePose; - public EyePose eyePose; - - public final boolean useTimewarp() { return OVRDistortion.useTimewarp(distortionCaps); } - public final boolean useChromatic() { return OVRDistortion.useChromatic(distortionCaps); } - public final boolean useVignette() { return OVRDistortion.useVignette(distortionCaps); } - - private EyeData(final OvrHmdContext hmdCtx, final int distortionCaps, - final float[] eyePositionOffset, final ovrEyeRenderDesc eyeDesc, - final ovrSizei ovrTextureSize, final int[] eyeRenderViewport) { - this.eyeName = eyeDesc.getEye(); - this.distortionCaps = distortionCaps; - viewport = new int[4]; - System.arraycopy(eyeRenderViewport, 0, viewport, 0, 4); - - final FloatBuffer fstash = Buffers.newDirectFloatBuffer(2+2+16+26); - - eyeToSourceUVScale = new GLUniformData("ovr_EyeToSourceUVScale", 2, Buffers.slice2Float(fstash, 0, 2)); - eyeToSourceUVOffset = new GLUniformData("ovr_EyeToSourceUVOffset", 2, Buffers.slice2Float(fstash, 2, 2)); - - if( useTimewarp() ) { - eyeRotationStart = new GLUniformData("ovr_EyeRotationStart", 4, 4, Buffers.slice2Float(fstash, 4, 16)); - eyeRotationEnd = new GLUniformData("ovr_EyeRotationEnd", 4, 4, Buffers.slice2Float(fstash, 20, 16)); - } else { - eyeRotationStart = null; - eyeRotationEnd = null; - } - - this.ovrEyeDesc = eyeDesc; - this.ovrEyeFov = eyeDesc.getFov(); - - final ovrVector3f eyeViewAdjust = eyeDesc.getViewAdjust(); - this.eyeParameter = new EyeParameter(eyeName, eyePositionOffset, OVRUtil.getFovHV(ovrEyeFov), - eyeViewAdjust.getX(), eyeViewAdjust.getY(), eyeViewAdjust.getZ()); - - this.eyePose = new EyePose(eyeName); - - updateEyePose(hmdCtx); - - final ovrDistortionMesh meshData = ovrDistortionMesh.create(); - final ovrFovPort fov = eyeDesc.getFov(); - - if( !OVR.ovrHmd_CreateDistortionMesh(hmdCtx, eyeName, fov, distortionCaps, meshData) ) { - throw new OVRException("Failed to create meshData for eye "+eyeName+" and "+OVRUtil.toString(fov)); - } - vertexCount = meshData.getVertexCount(); - indexCount = meshData.getIndexCount(); - - /** 2+2+2+2+2: { vec2 position, vec2 color, vec2 texCoordR, vec2 texCoordG, vec2 texCoordB } */ - final boolean useChromatic = useChromatic(); - final boolean useVignette = useVignette(); - final int compsPerElement = 2+2+2+( useChromatic ? 2+2 /* texCoordG + texCoordB */: 0 ); - iVBO = GLArrayDataServer.createGLSLInterleaved(compsPerElement, GL.GL_FLOAT, false, vertexCount, GL.GL_STATIC_DRAW); - vboPos = iVBO.addGLSLSubArray("ovr_Position", 2, GL.GL_ARRAY_BUFFER); - vboParams = iVBO.addGLSLSubArray("ovr_Params", 2, GL.GL_ARRAY_BUFFER); - vboTexCoordsR = iVBO.addGLSLSubArray("ovr_TexCoordR", 2, GL.GL_ARRAY_BUFFER); - if( useChromatic ) { - vboTexCoordsG = iVBO.addGLSLSubArray("ovr_TexCoordG", 2, GL.GL_ARRAY_BUFFER); - vboTexCoordsB = iVBO.addGLSLSubArray("ovr_TexCoordB", 2, GL.GL_ARRAY_BUFFER); - } else { - vboTexCoordsG = null; - vboTexCoordsB = null; - } - indices = GLArrayDataServer.createData(1, GL.GL_SHORT, indexCount, GL.GL_STATIC_DRAW, GL.GL_ELEMENT_ARRAY_BUFFER); - - // Setup: eyeToSourceUVScale, eyeToSourceUVOffset - { - final ovrVector2f[] uvScaleOffsetOut = new ovrVector2f[2]; - uvScaleOffsetOut[0] = ovrVector2f.create(); // FIXME: remove ctor / double check - uvScaleOffsetOut[1] = ovrVector2f.create(); - - final ovrRecti ovrEyeRenderViewport = OVRUtil.createOVRRecti(eyeRenderViewport); - OVR.ovrHmd_GetRenderScaleAndOffset(fov, ovrTextureSize, ovrEyeRenderViewport, uvScaleOffsetOut); - if( OVRUtil.DEBUG ) { - System.err.println("XXX."+eyeName+": fov "+OVRUtil.toString(fov)); - System.err.println("XXX."+eyeName+": uvScale "+OVRUtil.toString(uvScaleOffsetOut[0])); - System.err.println("XXX."+eyeName+": uvOffset "+OVRUtil.toString(uvScaleOffsetOut[0])); - System.err.println("XXX."+eyeName+": textureSize "+OVRUtil.toString(ovrTextureSize)); - System.err.println("XXX."+eyeName+": viewport "+OVRUtil.toString(ovrEyeRenderViewport)); - } - final FloatBuffer eyeToSourceUVScaleFB = eyeToSourceUVScale.floatBufferValue(); - eyeToSourceUVScaleFB.put(0, uvScaleOffsetOut[0].getX()); - eyeToSourceUVScaleFB.put(1, uvScaleOffsetOut[0].getY()); - final FloatBuffer eyeToSourceUVOffsetFB = eyeToSourceUVOffset.floatBufferValue(); - eyeToSourceUVOffsetFB.put(0, uvScaleOffsetOut[1].getX()); - eyeToSourceUVOffsetFB.put(1, uvScaleOffsetOut[1].getY()); - } - - /** 2+2+2+2+2: { vec2 position, vec2 color, vec2 texCoordR, vec2 texCoordG, vec2 texCoordB } */ - final FloatBuffer iVBOFB = (FloatBuffer)iVBO.getBuffer(); - final ovrDistortionVertex[] ovRes = new ovrDistortionVertex[1]; - ovRes[0] = ovrDistortionVertex.create(); // FIXME: remove ctor / double check - - for ( int vertNum = 0; vertNum < vertexCount; vertNum++ ) { - final ovrDistortionVertex ov = meshData.getPVertexData(vertNum, ovRes)[0]; - ovrVector2f v; - - // pos - v = ov.getPos(); - iVBOFB.put(v.getX()); - iVBOFB.put(v.getY()); - - // params - if( useVignette ) { - iVBOFB.put(ov.getVignetteFactor()); - } else { - iVBOFB.put(1.0f); - } - iVBOFB.put(ov.getTimeWarpFactor()); - - // texCoordR - v = ov.getTexR(); - iVBOFB.put(v.getX()); - iVBOFB.put(v.getY()); - - if( useChromatic ) { - // texCoordG - v = ov.getTexG(); - iVBOFB.put(v.getX()); - iVBOFB.put(v.getY()); - - // texCoordB - v = ov.getTexB(); - iVBOFB.put(v.getX()); - iVBOFB.put(v.getY()); - } - } - if( OVRUtil.DEBUG ) { - System.err.println("XXX."+eyeName+": iVBO "+iVBO); - } - { - final ShortBuffer in = meshData.getPIndexData(); - final ShortBuffer out = (ShortBuffer) indices.getBuffer(); - out.put(in); - } - if( OVRUtil.DEBUG ) { - System.err.println("XXX."+eyeName+": idx "+indices); - System.err.println("XXX."+eyeName+": distEye "+this); - } - OVR.ovrHmd_DestroyDistortionMesh(meshData); - } - - private void linkData(final GL2ES2 gl, final ShaderProgram sp) { - if( 0 > vboPos.setLocation(gl, sp.program()) ) { - throw new OVRException("Couldn't locate "+vboPos); - } - if( 0 > vboParams.setLocation(gl, sp.program()) ) { - throw new OVRException("Couldn't locate "+vboParams); - } - if( 0 > vboTexCoordsR.setLocation(gl, sp.program()) ) { - throw new OVRException("Couldn't locate "+vboTexCoordsR); - } - if( useChromatic() ) { - if( 0 > vboTexCoordsG.setLocation(gl, sp.program()) ) { - throw new OVRException("Couldn't locate "+vboTexCoordsG); - } - if( 0 > vboTexCoordsB.setLocation(gl, sp.program()) ) { - throw new OVRException("Couldn't locate "+vboTexCoordsB); - } - } - if( 0 > eyeToSourceUVScale.setLocation(gl, sp.program()) ) { - throw new OVRException("Couldn't locate "+eyeToSourceUVScale); - } - if( 0 > eyeToSourceUVOffset.setLocation(gl, sp.program()) ) { - throw new OVRException("Couldn't locate "+eyeToSourceUVOffset); - } - if( useTimewarp() ) { - if( 0 > eyeRotationStart.setLocation(gl, sp.program()) ) { - throw new OVRException("Couldn't locate "+eyeRotationStart); - } - if( 0 > eyeRotationEnd.setLocation(gl, sp.program()) ) { - throw new OVRException("Couldn't locate "+eyeRotationEnd); - } - } - iVBO.seal(gl, true); - iVBO.enableBuffer(gl, false); - indices.seal(gl, true); - indices.enableBuffer(gl, false); - } - - public void dispose(final GL2ES2 gl) { - iVBO.destroy(gl); - indices.destroy(gl); - } - public void enableVBO(final GL2ES2 gl, final boolean enable) { - iVBO.enableBuffer(gl, enable); - indices.bindBuffer(gl, enable); // keeps VBO binding if enable:=true - } - - public void updateUniform(final GL2ES2 gl, final ShaderProgram sp) { - gl.glUniform(eyeToSourceUVScale); - gl.glUniform(eyeToSourceUVOffset); - if( useTimewarp() ) { - gl.glUniform(eyeRotationStart); - gl.glUniform(eyeRotationEnd); - } - } - - public void updateTimewarp(final OvrHmdContext hmdCtx, final ovrPosef eyeRenderPose, final float[] mat4Tmp1, final float[] mat4Tmp2) { - final ovrMatrix4f[] timeWarpMatrices = new ovrMatrix4f[2]; - timeWarpMatrices[0] = ovrMatrix4f.create(); // FIXME: remove ctor / double check - timeWarpMatrices[1] = ovrMatrix4f.create(); - OVR.ovrHmd_GetEyeTimewarpMatrices(hmdCtx, eyeName, eyeRenderPose, timeWarpMatrices); - - final float[] eyeRotationStartM = FloatUtil.transposeMatrix(timeWarpMatrices[0].getM(0, mat4Tmp1), mat4Tmp2); - final FloatBuffer eyeRotationStartU = eyeRotationStart.floatBufferValue(); - eyeRotationStartU.put(eyeRotationStartM); - eyeRotationStartU.rewind(); - - final float[] eyeRotationEndM = FloatUtil.transposeMatrix(timeWarpMatrices[1].getM(0, mat4Tmp1), mat4Tmp2); - final FloatBuffer eyeRotationEndU = eyeRotationEnd.floatBufferValue(); - eyeRotationEndU.put(eyeRotationEndM); - eyeRotationEndU.rewind(); - } - - /** - * Updates {@link #ovrEyePose} and it's extracted - * {@link #eyeRenderPoseOrientation} and {@link #eyeRenderPosePosition}. - * @param hmdCtx used get the {@link #ovrEyePose} via {@link OVR#ovrHmd_GetEyePose(OvrHmdContext, int)} - */ - public EyePose updateEyePose(final OvrHmdContext hmdCtx) { - ovrEyePose = OVR.ovrHmd_GetEyePose(hmdCtx, eyeName); - final ovrVector3f pos = ovrEyePose.getPosition(); - eyePose.setPosition(pos.getX(), pos.getY(), pos.getZ()); - OVRUtil.copyToQuaternion(ovrEyePose.getOrientation(), eyePose.orientation); - return eyePose; - } - - @Override - public String toString() { - return "Eye["+eyeName+", viewport "+viewport[0]+"/"+viewport[1]+" "+viewport[2]+"x"+viewport[3]+ - ", "+eyeParameter+ - ", vertices "+vertexCount+", indices "+indexCount+ - ", uvScale["+eyeToSourceUVScale.floatBufferValue().get(0)+", "+eyeToSourceUVScale.floatBufferValue().get(1)+ - "], uvOffset["+eyeToSourceUVOffset.floatBufferValue().get(0)+", "+eyeToSourceUVOffset.floatBufferValue().get(1)+ - "], desc"+OVRUtil.toString(ovrEyeDesc)+", "+eyePose+"]"; - } - } - - public final OvrHmdContext hmdCtx; - public final EyeData[] eyes; - public final int distortionCaps; - public final int[/*2*/] textureSize; - public final GLUniformData texUnit0; - public final boolean usesDistMesh; - - private final float[] mat4Tmp1 = new float[16]; - private final float[] mat4Tmp2 = new float[16]; - - private ShaderProgram sp; - - @Override - public String toString() { - return "OVRDist[caps 0x"+Integer.toHexString(distortionCaps)+", "+ - ", tex "+textureSize[0]+"x"+textureSize[1]+ - ", vignette "+useVignette()+", chromatic "+useChromatic()+", timewarp "+useTimewarp()+ - ", "+PlatformPropsImpl.NEWLINE+" "+eyes[0]+", "+PlatformPropsImpl.NEWLINE+" "+eyes[1]+"]"; - } - - public static OVRDistortion create(final OvrHmdContext hmdCtx, final boolean sbsSingleTexture, - final float[] eyePositionOffset, final ovrFovPort[] eyeFov, - final float pixelsPerDisplayPixel, final int distortionCaps) { - final ovrEyeRenderDesc[] eyeRenderDesc = new ovrEyeRenderDesc[2]; - eyeRenderDesc[0] = OVR.ovrHmd_GetRenderDesc(hmdCtx, OVR.ovrEye_Left, eyeFov[0]); - eyeRenderDesc[1] = OVR.ovrHmd_GetRenderDesc(hmdCtx, OVR.ovrEye_Right, eyeFov[1]); - if( OVRUtil.DEBUG ) { - System.err.println("XXX: eyeRenderDesc[0] "+OVRUtil.toString(eyeRenderDesc[0])); - System.err.println("XXX: eyeRenderDesc[1] "+OVRUtil.toString(eyeRenderDesc[1])); - } - - final ovrSizei recommenedTex0Size = OVR.ovrHmd_GetFovTextureSize(hmdCtx, OVR.ovrEye_Left, eyeRenderDesc[0].getFov(), pixelsPerDisplayPixel); - final ovrSizei recommenedTex1Size = OVR.ovrHmd_GetFovTextureSize(hmdCtx, OVR.ovrEye_Right, eyeRenderDesc[1].getFov(), pixelsPerDisplayPixel); - if( OVRUtil.DEBUG ) { - System.err.println("XXX: recommenedTex0Size "+OVRUtil.toString(recommenedTex0Size)); - System.err.println("XXX: recommenedTex1Size "+OVRUtil.toString(recommenedTex1Size)); - } - final int[] textureSize = new int[2]; - if( sbsSingleTexture ) { - textureSize[0] = recommenedTex0Size.getW() + recommenedTex1Size.getW(); - } else { - textureSize[0] = Math.max(recommenedTex0Size.getW(), recommenedTex1Size.getW()); - } - textureSize[1] = Math.max(recommenedTex0Size.getH(), recommenedTex1Size.getH()); - if( OVRUtil.DEBUG ) { - System.err.println("XXX: textureSize "+textureSize[0]+"x"+textureSize[1]); - } - - final int[][] eyeRenderViewports = new int[2][4]; - if( sbsSingleTexture ) { - eyeRenderViewports[0][0] = 0; - eyeRenderViewports[0][1] = 0; - eyeRenderViewports[0][2] = textureSize[0] / 2; - eyeRenderViewports[0][3] = textureSize[1]; - eyeRenderViewports[1][0] = (textureSize[0] + 1) / 2; - eyeRenderViewports[1][1] = 0; - eyeRenderViewports[1][2] = textureSize[0] / 2; - eyeRenderViewports[1][3] = textureSize[1]; - } else { - eyeRenderViewports[0][0] = 0; - eyeRenderViewports[0][1] = 0; - eyeRenderViewports[0][2] = textureSize[0]; - eyeRenderViewports[0][3] = textureSize[1]; - eyeRenderViewports[1][0] = 0; - eyeRenderViewports[1][1] = 0; - eyeRenderViewports[1][2] = textureSize[0]; - eyeRenderViewports[1][3] = textureSize[1]; - } - return new OVRDistortion(hmdCtx, sbsSingleTexture, eyePositionOffset, eyeRenderDesc, textureSize, eyeRenderViewports, distortionCaps, 0); - } - - public OVRDistortion(final OvrHmdContext hmdCtx, final boolean sbsSingleTexture, - final float[] eyePositionOffset, final ovrEyeRenderDesc[] eyeRenderDescs, - final int[] textureSize, final int[][] eyeRenderViewports, - final int distortionCaps, final int textureUnit) { - this.hmdCtx = hmdCtx; - this.eyes = new EyeData[2]; - this.distortionCaps = distortionCaps; - this.textureSize = new int[2]; - System.arraycopy(textureSize, 0, this.textureSize, 0, 2); - - texUnit0 = new GLUniformData("ovr_Texture0", textureUnit); - usesDistMesh = true; - - final ovrSizei ovrTextureSize = OVRUtil.createOVRSizei(textureSize); - eyes[0] = new EyeData(hmdCtx, distortionCaps, eyePositionOffset, eyeRenderDescs[0], ovrTextureSize, eyeRenderViewports[0]); - eyes[1] = new EyeData(hmdCtx, distortionCaps, eyePositionOffset, eyeRenderDescs[1], ovrTextureSize, eyeRenderViewports[1]); - sp = null; - } - - public final boolean useTimewarp() { return useTimewarp(distortionCaps); } - public final boolean useChromatic() { return useChromatic(distortionCaps); } - public final boolean useVignette() { return useVignette(distortionCaps); } - - public void updateTimewarp(final ovrPosef eyeRenderPose, final int eyeNum) { - eyes[eyeNum].updateTimewarp(hmdCtx, eyeRenderPose, mat4Tmp1, mat4Tmp2); - } - public void updateTimewarp(final ovrPosef[] eyeRenderPoses) { - eyes[0].updateTimewarp(hmdCtx, eyeRenderPoses[0], mat4Tmp1, mat4Tmp2); - eyes[1].updateTimewarp(hmdCtx, eyeRenderPoses[1], mat4Tmp1, mat4Tmp2); - } - - public void enableVBO(final GL2ES2 gl, final boolean enable, final int eyeNum) { - if( null == sp ) { - throw new IllegalStateException("Not initialized"); - } - eyes[eyeNum].enableVBO(gl, enable); - } - - public final ShaderProgram getShaderProgram() { return sp; } - - public void init(final GL2ES2 gl) { - if( OVRUtil.DEBUG ) { - System.err.println(JoglVersion.getGLInfo(gl, null).toString()); - } - if( null != sp ) { - throw new IllegalStateException("Already initialized"); - } - final String vertexShaderBasename; - final String fragmentShaderBasename; - { - final StringBuilder sb = new StringBuilder(); - sb.append(shaderPrefix01); - if( !useChromatic() && !useTimewarp() ) { - sb.append(shaderPlainSuffix); - } else if( useChromatic() && !useTimewarp() ) { - sb.append(shaderChromaSuffix); - } else if( useTimewarp() ) { - sb.append(shaderTimewarpSuffix); - if( useChromatic() ) { - sb.append(shaderChromaSuffix); - } - } - vertexShaderBasename = sb.toString(); - sb.setLength(0); - sb.append(shaderPrefix01); - if( useChromatic() ) { - sb.append(shaderChromaSuffix); - } else { - sb.append(shaderPlainSuffix); - } - fragmentShaderBasename = sb.toString(); - } - final ShaderCode vp0 = ShaderCode.create(gl, GL2ES2.GL_VERTEX_SHADER, OVRDistortion.class, "shader", - "shader/bin", vertexShaderBasename, true); - final ShaderCode fp0 = ShaderCode.create(gl, GL2ES2.GL_FRAGMENT_SHADER, OVRDistortion.class, "shader", - "shader/bin", fragmentShaderBasename, true); - vp0.defaultShaderCustomization(gl, true, true); - fp0.defaultShaderCustomization(gl, true, true); - - sp = new ShaderProgram(); - sp.add(gl, vp0, System.err); - sp.add(gl, fp0, System.err); - if(!sp.link(gl, System.err)) { - throw new GLException("could not link program: "+sp); - } - sp.useProgram(gl, true); - if( 0 > texUnit0.setLocation(gl, sp.program()) ) { - throw new OVRException("Couldn't locate "+texUnit0); - } - eyes[0].linkData(gl, sp); - eyes[1].linkData(gl, sp); - sp.useProgram(gl, false); - } - - public void dispose(final GL2ES2 gl) { - sp.useProgram(gl, false); - eyes[0].dispose(gl); - eyes[1].dispose(gl); - sp.destroy(gl); - } - - public EyeParameter getEyeParam(final int eyeNum) { - return eyes[eyeNum].eyeParameter; - } - - /** - * Updates the {@link EyeData#ovrEyePose} via {@link EyeData#updateEyePose(OvrHmdContext)} - * for the denoted eye. - */ - public EyePose updateEyePose(final int eyeNum) { - return eyes[eyeNum].updateEyePose(hmdCtx); - } - - public void updateUniforms(final GL2ES2 gl, final int eyeNum) { - if( null == sp ) { - throw new IllegalStateException("Not initialized"); - } - gl.glUniform(texUnit0); - eyes[eyeNum].updateUniform(gl, sp); - } - - /** - * <p> - * {@link #updateEyePose(int)} must be called upfront - * when rendering upstream {@link GLEventListener}. - * </p> - * - * @param gl - * @param timewarpPointSeconds - */ - public void display(final GL2ES2 gl, final double timewarpPointSeconds) { - if( null == sp ) { - throw new IllegalStateException("Not initialized"); - } - if( useTimewarp() ) { - OVR.ovr_WaitTillTime(timewarpPointSeconds); - } - gl.glDisable(GL.GL_CULL_FACE); - gl.glDisable(GL.GL_DEPTH_TEST); - gl.glDisable(GL.GL_BLEND); - - if( !gl.isGLcore() ) { - gl.glEnable(GL.GL_TEXTURE_2D); - } - - sp.useProgram(gl, true); - - gl.glUniform(texUnit0); - - for(int eyeNum=0; eyeNum<2; eyeNum++) { - final EyeData eye = eyes[eyeNum]; - if( useTimewarp() ) { - eye.updateTimewarp(hmdCtx, eye.ovrEyePose, mat4Tmp1, mat4Tmp2); - } - eye.updateUniform(gl, sp); - eye.enableVBO(gl, true); - if( usesDistMesh ) { - gl.glDrawElements(GL.GL_TRIANGLES, eye.indexCount, GL.GL_UNSIGNED_SHORT, 0); - } else { - gl.glDrawArrays(GL.GL_TRIANGLE_STRIP, 0, eye.vertexCount); - } - eyes[eyeNum].enableVBO(gl, false); - } - - sp.useProgram(gl, false); - } - - /** - * - * @param gl - * @param timewarpPointSeconds - */ - public void displayOneEyePre(final GL2ES2 gl, final double timewarpPointSeconds) { - if( null == sp ) { - throw new IllegalStateException("Not initialized"); - } - if( useTimewarp() ) { - OVR.ovr_WaitTillTime(timewarpPointSeconds); - } - gl.glDisable(GL.GL_CULL_FACE); - gl.glDisable(GL.GL_DEPTH_TEST); - gl.glDisable(GL.GL_BLEND); - - if( !gl.isGLcore() ) { - gl.glEnable(GL.GL_TEXTURE_2D); - } - - sp.useProgram(gl, true); - - gl.glUniform(texUnit0); - } - - /** - * <p> - * {@link #updateEyePose(int)} must be called upfront - * when rendering upstream {@link GLEventListener}. - * </p> - * - * @param gl - * @param eyeNum - */ - public void displayOneEye(final GL2ES2 gl, final int eyeNum) { - if( null == sp ) { - throw new IllegalStateException("Not initialized"); - } - final EyeData eye = eyes[eyeNum]; - if( useTimewarp() ) { - eye.updateTimewarp(hmdCtx, eye.ovrEyePose, mat4Tmp1, mat4Tmp2); - } - eye.updateUniform(gl, sp); - eye.enableVBO(gl, true); - if( usesDistMesh ) { - gl.glDrawElements(GL.GL_TRIANGLES, eye.indexCount, GL.GL_UNSIGNED_SHORT, 0); - } else { - gl.glDrawArrays(GL.GL_TRIANGLE_STRIP, 0, eye.vertexCount); - } - eyes[eyeNum].enableVBO(gl, false); - } - - public void displayOneEyePost(final GL2ES2 gl) { - sp.useProgram(gl, false); - } - - /** - * Calculates the <i>Side By Side</i>, SBS, projection- and modelview matrix for one eye. - * <p> - * {@link #updateEyePose(int)} must be called upfront. - * </p> - * <p> - * This method merely exist as an example implementation to compute the matrices, - * which shall be adopted by the - * {@link CustomRendererListener#reshape(javax.media.opengl.GLAutoDrawable, int, int, int, int, EyeParameter, EyePose) upstream client code}. - * </p> - * @param eyeNum eye denominator - * @param zNear frustum near value - * @param zFar frustum far value - * @param mat4Projection float[16] projection matrix result - * @param mat4Modelview float[16] modelview matrix result - * @deprecated Only an example implementation, which should be adopted by the {@link CustomRendererListener#reshape(javax.media.opengl.GLAutoDrawable, int, int, int, int, EyeParameter, EyePose) upstream client code}. - */ - public void getSBSUpstreamPMV(final int eyeNum, final float zNear, final float zFar, - final float[] mat4Projection, final float[] mat4Modelview) { - final EyeData eyeDist = eyes[eyeNum]; - - final float[] vec3Tmp1 = new float[3]; - final float[] vec3Tmp2 = new float[3]; - final float[] vec3Tmp3 = new float[3]; - - // - // Projection - // - FloatUtil.makePerspective(mat4Projection, 0, true, eyeDist.eyeParameter.fovhv, zNear, zFar); - - // - // Modelview - // - final Quaternion rollPitchYaw = new Quaternion(); - // private final float eyeYaw = FloatUtil.PI; // 180 degrees in radians - // rollPitchYaw.rotateByAngleY(eyeYaw); - final float[] shiftedEyePos = rollPitchYaw.rotateVector(vec3Tmp1, 0, eyeDist.eyePose.position, 0); - VectorUtil.addVec3(shiftedEyePos, shiftedEyePos, eyeDist.eyeParameter.positionOffset); - - rollPitchYaw.mult(eyeDist.eyePose.orientation); - final float[] up = rollPitchYaw.rotateVector(vec3Tmp2, 0, VEC3_UP, 0); - final float[] forward = rollPitchYaw.rotateVector(vec3Tmp3, 0, VEC3_FORWARD, 0); - final float[] center = VectorUtil.addVec3(forward, shiftedEyePos, forward); - - final float[] mLookAt = FloatUtil.makeLookAt(mat4Tmp2, 0, shiftedEyePos, 0, center, 0, up, 0, mat4Tmp1); - final float[] mViewAdjust = FloatUtil.makeTranslation(mat4Modelview, true, - eyeDist.eyeParameter.distNoseToPupilX, - eyeDist.eyeParameter.distMiddleToPupilY, - eyeDist.eyeParameter.eyeReliefZ); - - /* mat4Modelview = */ FloatUtil.multMatrix(mViewAdjust, mLookAt); - } -} diff --git a/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDevice.java b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDevice.java new file mode 100644 index 000000000..09a348c46 --- /dev/null +++ b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDevice.java @@ -0,0 +1,158 @@ +/** + * Copyright 2014 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 jogamp.opengl.oculusvr; + +import javax.media.nativewindow.util.Dimension; +import javax.media.nativewindow.util.DimensionImmutable; +import javax.media.nativewindow.util.PointImmutable; +import javax.media.nativewindow.util.Rectangle; +import javax.media.nativewindow.util.RectangleImmutable; + +import com.jogamp.oculusvr.OVR; +import com.jogamp.oculusvr.OvrHmdContext; +import com.jogamp.oculusvr.ovrEyeRenderDesc; +import com.jogamp.oculusvr.ovrFovPort; +import com.jogamp.oculusvr.ovrHmdDesc; +import com.jogamp.oculusvr.ovrSizei; +import com.jogamp.opengl.math.FovHVHalves; +import com.jogamp.opengl.util.stereo.StereoDevice; +import com.jogamp.opengl.util.stereo.StereoDeviceRenderer; + +public class OVRStereoDevice implements StereoDevice { + public final OvrHmdContext handle; + public final int deviceIndex; + public final ovrHmdDesc hmdDesc; + + private boolean sensorsStarted = false; + + public OVRStereoDevice(final OvrHmdContext nativeContext, final int deviceIndex) { + this.handle = nativeContext; + this.deviceIndex = deviceIndex; + this.hmdDesc = ovrHmdDesc.create(); + OVR.ovrHmd_GetDesc(handle, hmdDesc); + } + + @Override + public final void dispose() { + // NOP + } + + @Override + public final PointImmutable getPosition() { + return OVRUtil.getVec2iAsPoint(hmdDesc.getWindowsPos()); + } + + @Override + public final DimensionImmutable getSurfaceSize() { + return OVRUtil.getOVRSizei(hmdDesc.getResolution()); + } + + @Override + public final FovHVHalves[] getDefaultFOV() { + final ovrFovPort[] defaultEyeFov = hmdDesc.getDefaultEyeFov(0, new ovrFovPort[2]); + final FovHVHalves[] eyeFov = new FovHVHalves[2]; + eyeFov[0] = OVRUtil.getFovHV(defaultEyeFov[0]); + eyeFov[1] = OVRUtil.getFovHV(defaultEyeFov[1]); + return eyeFov; + } + + @Override + public final boolean startSensors(final boolean start) { + if( start && !sensorsStarted ) { + // Start the sensor which provides the Rift’s pose and motion. + final int requiredSensorCaps = 0; + final int supportedSensorCaps = requiredSensorCaps | OVR.ovrSensorCap_Orientation | OVR.ovrSensorCap_YawCorrection | OVR.ovrSensorCap_Position; + if( OVR.ovrHmd_StartSensor(handle, supportedSensorCaps, requiredSensorCaps) ) { + sensorsStarted = true; + return true; + } else { + sensorsStarted = false; + return false; + } + } else if( sensorsStarted ) { + OVR.ovrHmd_StopSensor(handle); + sensorsStarted = false; + return true; + } else { + // No state change -> Success + return true; + } + } + @Override + public boolean getSensorsStarted() { return sensorsStarted; } + + @Override + public final StereoDeviceRenderer createRenderer(final int distortionBits, + final int textureCount, final float[] eyePositionOffset, + final FovHVHalves[] eyeFov, final float pixelsPerDisplayPixel, final int textureUnit) { + final ovrFovPort ovrEyeFov0 = OVRUtil.getOVRFovPort(eyeFov[0]); + final ovrFovPort ovrEyeFov1 = OVRUtil.getOVRFovPort(eyeFov[1]); + + final ovrEyeRenderDesc[] eyeRenderDesc = new ovrEyeRenderDesc[2]; + eyeRenderDesc[0] = OVR.ovrHmd_GetRenderDesc(handle, OVR.ovrEye_Left, ovrEyeFov0); + eyeRenderDesc[1] = OVR.ovrHmd_GetRenderDesc(handle, OVR.ovrEye_Right, ovrEyeFov1); + if( StereoDevice.DEBUG ) { + System.err.println("XXX: eyeRenderDesc[0] "+OVRUtil.toString(eyeRenderDesc[0])); + System.err.println("XXX: eyeRenderDesc[1] "+OVRUtil.toString(eyeRenderDesc[1])); + } + + final ovrSizei recommenedTex0Size = OVR.ovrHmd_GetFovTextureSize(handle, OVR.ovrEye_Left, eyeRenderDesc[0].getFov(), pixelsPerDisplayPixel); + final ovrSizei recommenedTex1Size = OVR.ovrHmd_GetFovTextureSize(handle, OVR.ovrEye_Right, eyeRenderDesc[1].getFov(), pixelsPerDisplayPixel); + if( StereoDevice.DEBUG ) { + System.err.println("XXX: recommenedTex0Size "+OVRUtil.toString(recommenedTex0Size)); + System.err.println("XXX: recommenedTex1Size "+OVRUtil.toString(recommenedTex1Size)); + } + final int unifiedW = Math.max(recommenedTex0Size.getW(), recommenedTex1Size.getW()); + final int unifiedH = Math.max(recommenedTex0Size.getH(), recommenedTex1Size.getH()); + + final DimensionImmutable singleTextureSize = new Dimension(unifiedW, unifiedH); + final DimensionImmutable totalTextureSize = new Dimension(recommenedTex0Size.getW() + recommenedTex1Size.getW(), unifiedH); + if( StereoDevice.DEBUG ) { + System.err.println("XXX: textureSize Single "+singleTextureSize); + System.err.println("XXX: textureSize Total "+totalTextureSize); + } + + final RectangleImmutable[] eyeRenderViewports = new RectangleImmutable[2]; + if( 1 == textureCount ) { // validated in ctor below! + eyeRenderViewports[0] = new Rectangle(0, 0, + totalTextureSize.getWidth() / 2, + totalTextureSize.getHeight()); + + eyeRenderViewports[1] = new Rectangle((totalTextureSize.getWidth() + 1) / 2, 0, + totalTextureSize.getWidth() / 2, + totalTextureSize.getHeight()); + } else { + eyeRenderViewports[0] = new Rectangle(0, 0, + singleTextureSize.getWidth(), + singleTextureSize.getHeight()); + eyeRenderViewports[1] = eyeRenderViewports[0]; + } + return new OVRStereoDeviceRenderer(this, distortionBits, textureCount, eyePositionOffset, + eyeRenderDesc, singleTextureSize, totalTextureSize, eyeRenderViewports, textureUnit); + } +}
\ No newline at end of file diff --git a/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDeviceFactory.java b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDeviceFactory.java new file mode 100644 index 000000000..06454e443 --- /dev/null +++ b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDeviceFactory.java @@ -0,0 +1,51 @@ +/** + * Copyright 2014 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 jogamp.opengl.oculusvr; + +import com.jogamp.oculusvr.OVR; +import com.jogamp.oculusvr.OVRVersion; +import com.jogamp.oculusvr.OvrHmdContext; +import com.jogamp.opengl.util.stereo.StereoDevice; +import com.jogamp.opengl.util.stereo.StereoDeviceFactory; + +public class OVRStereoDeviceFactory extends StereoDeviceFactory { + + public static boolean isAvailable() { + return OVR.ovr_Initialize(); // recursive .. + } + + @Override + public final StereoDevice createDevice(final int deviceIndex, final boolean verbose) { + final OvrHmdContext hmdCtx = OVR.ovrHmd_Create(deviceIndex); + final OVRStereoDevice ctx = new OVRStereoDevice(hmdCtx, deviceIndex); + if( verbose ) { + System.err.println(OVRVersion.getAvailableCapabilitiesInfo(ctx.hmdDesc, deviceIndex, null).toString()); + } + return ctx; + } +} diff --git a/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDeviceRenderer.java b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDeviceRenderer.java new file mode 100644 index 000000000..012ad183d --- /dev/null +++ b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDeviceRenderer.java @@ -0,0 +1,590 @@ +/** + * Copyright 2014 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 jogamp.opengl.oculusvr; + +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; + +import javax.media.nativewindow.util.DimensionImmutable; +import javax.media.nativewindow.util.RectangleImmutable; +import javax.media.opengl.GL; +import javax.media.opengl.GL2ES2; +import javax.media.opengl.GLArrayData; +import javax.media.opengl.GLException; +import javax.media.opengl.GLUniformData; + +import jogamp.common.os.PlatformPropsImpl; + +import com.jogamp.common.nio.Buffers; +import com.jogamp.oculusvr.OVR; +import com.jogamp.oculusvr.OVRException; +import com.jogamp.oculusvr.OvrHmdContext; +import com.jogamp.oculusvr.ovrDistortionMesh; +import com.jogamp.oculusvr.ovrDistortionVertex; +import com.jogamp.oculusvr.ovrEyeRenderDesc; +import com.jogamp.oculusvr.ovrFovPort; +import com.jogamp.oculusvr.ovrFrameTiming; +import com.jogamp.oculusvr.ovrMatrix4f; +import com.jogamp.oculusvr.ovrPosef; +import com.jogamp.oculusvr.ovrRecti; +import com.jogamp.oculusvr.ovrSizei; +import com.jogamp.oculusvr.ovrVector2f; +import com.jogamp.oculusvr.ovrVector3f; +import com.jogamp.opengl.JoglVersion; +import com.jogamp.opengl.math.FloatUtil; +import com.jogamp.opengl.util.GLArrayDataServer; +import com.jogamp.opengl.util.glsl.ShaderCode; +import com.jogamp.opengl.util.glsl.ShaderProgram; +import com.jogamp.opengl.util.stereo.EyeParameter; +import com.jogamp.opengl.util.stereo.EyePose; +import com.jogamp.opengl.util.stereo.StereoDevice; +import com.jogamp.opengl.util.stereo.StereoDeviceRenderer; +import com.jogamp.opengl.util.stereo.StereoUtil; + +/** + * OculusVR Distortion Data and OpenGL Renderer Utility + */ +public class OVRStereoDeviceRenderer implements StereoDeviceRenderer { + private static final String shaderPrefix01 = "dist01"; + private static final String shaderTimewarpSuffix = "_timewarp"; + private static final String shaderChromaSuffix = "_chroma"; + private static final String shaderPlainSuffix = "_plain"; + + public static class OVREye implements StereoDeviceRenderer.Eye { + private final int eyeName; + private final int distortionBits; + private final int vertexCount; + private final int indexCount; + private final RectangleImmutable viewport; + + private final GLUniformData eyeToSourceUVScale; + private final GLUniformData eyeToSourceUVOffset; + private final GLUniformData eyeRotationStart; + private final GLUniformData eyeRotationEnd; + + /** 2+2+2+2+2: { vec2 position, vec2 color, vec2 texCoordR, vec2 texCoordG, vec2 texCoordB } */ + private final GLArrayDataServer iVBO; + private final GLArrayData vboPos, vboParams, vboTexCoordsR, vboTexCoordsG, vboTexCoordsB; + private final GLArrayDataServer indices; + + private final ovrEyeRenderDesc ovrEyeDesc; + private final ovrFovPort ovrEyeFov; + private final EyeParameter eyeParameter; + + private ovrPosef ovrEyePose; + private final EyePose eyePose; + + @Override + public final RectangleImmutable getViewport() { return viewport; } + + @Override + public final EyeParameter getEyeParameter() { return eyeParameter; } + + @Override + public final EyePose getLastEyePose() { return eyePose; } + + private OVREye(final OvrHmdContext hmdCtx, final int distortionBits, + final float[] eyePositionOffset, final ovrEyeRenderDesc eyeDesc, + final ovrSizei ovrTextureSize, final RectangleImmutable eyeViewport) { + this.eyeName = eyeDesc.getEye(); + this.distortionBits = distortionBits; + this.viewport = eyeViewport; + + final boolean usesTimewarp = StereoUtil.usesTimewarpDistortion(distortionBits); + final FloatBuffer fstash = Buffers.newDirectFloatBuffer( 2 + 2 + ( usesTimewarp ? 16 + 16 : 0 ) ) ; + + eyeToSourceUVScale = new GLUniformData("ovr_EyeToSourceUVScale", 2, Buffers.slice2Float(fstash, 0, 2)); + eyeToSourceUVOffset = new GLUniformData("ovr_EyeToSourceUVOffset", 2, Buffers.slice2Float(fstash, 2, 2)); + + if( usesTimewarp ) { + eyeRotationStart = new GLUniformData("ovr_EyeRotationStart", 4, 4, Buffers.slice2Float(fstash, 4, 16)); + eyeRotationEnd = new GLUniformData("ovr_EyeRotationEnd", 4, 4, Buffers.slice2Float(fstash, 20, 16)); + } else { + eyeRotationStart = null; + eyeRotationEnd = null; + } + + this.ovrEyeDesc = eyeDesc; + this.ovrEyeFov = eyeDesc.getFov(); + + final ovrVector3f eyeViewAdjust = eyeDesc.getViewAdjust(); + this.eyeParameter = new EyeParameter(eyeName, eyePositionOffset, OVRUtil.getFovHV(ovrEyeFov), + eyeViewAdjust.getX(), eyeViewAdjust.getY(), eyeViewAdjust.getZ()); + + this.eyePose = new EyePose(eyeName); + + updateEyePose(hmdCtx); // 1st init + + final ovrDistortionMesh meshData = ovrDistortionMesh.create(); + final ovrFovPort fov = eyeDesc.getFov(); + + final int ovrDistortionCaps = distBits2OVRDistCaps(distortionBits); + if( !OVR.ovrHmd_CreateDistortionMesh(hmdCtx, eyeName, fov, ovrDistortionCaps, meshData) ) { + throw new OVRException("Failed to create meshData for eye "+eyeName+", "+OVRUtil.toString(fov)+" and "+StereoUtil.distortionBitsToString(distortionBits)); + } + vertexCount = meshData.getVertexCount(); + indexCount = meshData.getIndexCount(); + + /** 2+2+2+2+2: { vec2 position, vec2 color, vec2 texCoordR, vec2 texCoordG, vec2 texCoordB } */ + final boolean useChromatic = StereoUtil.usesChromaticDistortion(distortionBits); + final boolean useVignette = StereoUtil.usesVignetteDistortion(distortionBits); + + final int compsPerElement = 2+2+2+( useChromatic ? 2+2 /* texCoordG + texCoordB */: 0 ); + iVBO = GLArrayDataServer.createGLSLInterleaved(compsPerElement, GL.GL_FLOAT, false, vertexCount, GL.GL_STATIC_DRAW); + vboPos = iVBO.addGLSLSubArray("ovr_Position", 2, GL.GL_ARRAY_BUFFER); + vboParams = iVBO.addGLSLSubArray("ovr_Params", 2, GL.GL_ARRAY_BUFFER); + vboTexCoordsR = iVBO.addGLSLSubArray("ovr_TexCoordR", 2, GL.GL_ARRAY_BUFFER); + if( useChromatic ) { + vboTexCoordsG = iVBO.addGLSLSubArray("ovr_TexCoordG", 2, GL.GL_ARRAY_BUFFER); + vboTexCoordsB = iVBO.addGLSLSubArray("ovr_TexCoordB", 2, GL.GL_ARRAY_BUFFER); + } else { + vboTexCoordsG = null; + vboTexCoordsB = null; + } + indices = GLArrayDataServer.createData(1, GL.GL_SHORT, indexCount, GL.GL_STATIC_DRAW, GL.GL_ELEMENT_ARRAY_BUFFER); + + // Setup: eyeToSourceUVScale, eyeToSourceUVOffset + { + final ovrVector2f[] uvScaleOffsetOut = new ovrVector2f[2]; + uvScaleOffsetOut[0] = ovrVector2f.create(); // FIXME: remove ctor / double check + uvScaleOffsetOut[1] = ovrVector2f.create(); + + final ovrRecti ovrEyeRenderViewport = OVRUtil.createOVRRecti(eyeViewport); + OVR.ovrHmd_GetRenderScaleAndOffset(fov, ovrTextureSize, ovrEyeRenderViewport, uvScaleOffsetOut); + if( StereoDevice.DEBUG ) { + System.err.println("XXX."+eyeName+": fov "+OVRUtil.toString(fov)); + System.err.println("XXX."+eyeName+": uvScale "+OVRUtil.toString(uvScaleOffsetOut[0])); + System.err.println("XXX."+eyeName+": uvOffset "+OVRUtil.toString(uvScaleOffsetOut[1])); + System.err.println("XXX."+eyeName+": textureSize "+OVRUtil.toString(ovrTextureSize)); + System.err.println("XXX."+eyeName+": viewport "+OVRUtil.toString(ovrEyeRenderViewport)); + } + final FloatBuffer eyeToSourceUVScaleFB = eyeToSourceUVScale.floatBufferValue(); + eyeToSourceUVScaleFB.put(0, uvScaleOffsetOut[0].getX()); + eyeToSourceUVScaleFB.put(1, uvScaleOffsetOut[0].getY()); + final FloatBuffer eyeToSourceUVOffsetFB = eyeToSourceUVOffset.floatBufferValue(); + eyeToSourceUVOffsetFB.put(0, uvScaleOffsetOut[1].getX()); + eyeToSourceUVOffsetFB.put(1, uvScaleOffsetOut[1].getY()); + } + + /** 2+2+2+2+2: { vec2 position, vec2 color, vec2 texCoordR, vec2 texCoordG, vec2 texCoordB } */ + final FloatBuffer iVBOFB = (FloatBuffer)iVBO.getBuffer(); + final ovrDistortionVertex[] ovRes = new ovrDistortionVertex[1]; + ovRes[0] = ovrDistortionVertex.create(); // FIXME: remove ctor / double check + + for ( int vertNum = 0; vertNum < vertexCount; vertNum++ ) { + final ovrDistortionVertex ov = meshData.getPVertexData(vertNum, ovRes)[0]; + ovrVector2f v; + + // pos + v = ov.getPos(); + iVBOFB.put(v.getX()); + iVBOFB.put(v.getY()); + + // params + if( useVignette ) { + iVBOFB.put(ov.getVignetteFactor()); + } else { + iVBOFB.put(1.0f); + } + iVBOFB.put(ov.getTimeWarpFactor()); + + // texCoordR + v = ov.getTexR(); + iVBOFB.put(v.getX()); + iVBOFB.put(v.getY()); + + if( useChromatic ) { + // texCoordG + v = ov.getTexG(); + iVBOFB.put(v.getX()); + iVBOFB.put(v.getY()); + + // texCoordB + v = ov.getTexB(); + iVBOFB.put(v.getX()); + iVBOFB.put(v.getY()); + } + } + if( StereoDevice.DEBUG ) { + System.err.println("XXX."+eyeName+": iVBO "+iVBO); + } + { + final ShortBuffer in = meshData.getPIndexData(); + final ShortBuffer out = (ShortBuffer) indices.getBuffer(); + out.put(in); + } + if( StereoDevice.DEBUG ) { + System.err.println("XXX."+eyeName+": idx "+indices); + System.err.println("XXX."+eyeName+": "+this); + } + OVR.ovrHmd_DestroyDistortionMesh(meshData); + } + + private void linkData(final GL2ES2 gl, final ShaderProgram sp) { + if( 0 > vboPos.setLocation(gl, sp.program()) ) { + throw new OVRException("Couldn't locate "+vboPos); + } + if( 0 > vboParams.setLocation(gl, sp.program()) ) { + throw new OVRException("Couldn't locate "+vboParams); + } + if( 0 > vboTexCoordsR.setLocation(gl, sp.program()) ) { + throw new OVRException("Couldn't locate "+vboTexCoordsR); + } + if( StereoUtil.usesChromaticDistortion(distortionBits) ) { + if( 0 > vboTexCoordsG.setLocation(gl, sp.program()) ) { + throw new OVRException("Couldn't locate "+vboTexCoordsG); + } + if( 0 > vboTexCoordsB.setLocation(gl, sp.program()) ) { + throw new OVRException("Couldn't locate "+vboTexCoordsB); + } + } + if( 0 > eyeToSourceUVScale.setLocation(gl, sp.program()) ) { + throw new OVRException("Couldn't locate "+eyeToSourceUVScale); + } + if( 0 > eyeToSourceUVOffset.setLocation(gl, sp.program()) ) { + throw new OVRException("Couldn't locate "+eyeToSourceUVOffset); + } + if( StereoUtil.usesTimewarpDistortion(distortionBits) ) { + if( 0 > eyeRotationStart.setLocation(gl, sp.program()) ) { + throw new OVRException("Couldn't locate "+eyeRotationStart); + } + if( 0 > eyeRotationEnd.setLocation(gl, sp.program()) ) { + throw new OVRException("Couldn't locate "+eyeRotationEnd); + } + } + iVBO.seal(gl, true); + iVBO.enableBuffer(gl, false); + indices.seal(gl, true); + indices.enableBuffer(gl, false); + } + + private void dispose(final GL2ES2 gl) { + iVBO.destroy(gl); + indices.destroy(gl); + } + private void enableVBO(final GL2ES2 gl, final boolean enable) { + iVBO.enableBuffer(gl, enable); + indices.bindBuffer(gl, enable); // keeps VBO binding if enable:=true + } + + private void updateUniform(final GL2ES2 gl, final ShaderProgram sp) { + gl.glUniform(eyeToSourceUVScale); + gl.glUniform(eyeToSourceUVOffset); + if( StereoUtil.usesTimewarpDistortion(distortionBits) ) { + gl.glUniform(eyeRotationStart); + gl.glUniform(eyeRotationEnd); + } + } + + private void updateTimewarp(final OvrHmdContext hmdCtx, final ovrPosef eyeRenderPose, final float[] mat4Tmp1, final float[] mat4Tmp2) { + final ovrMatrix4f[] timeWarpMatrices = new ovrMatrix4f[2]; + timeWarpMatrices[0] = ovrMatrix4f.create(); // FIXME: remove ctor / double check + timeWarpMatrices[1] = ovrMatrix4f.create(); + OVR.ovrHmd_GetEyeTimewarpMatrices(hmdCtx, eyeName, eyeRenderPose, timeWarpMatrices); + + final float[] eyeRotationStartM = FloatUtil.transposeMatrix(timeWarpMatrices[0].getM(0, mat4Tmp1), mat4Tmp2); + final FloatBuffer eyeRotationStartU = eyeRotationStart.floatBufferValue(); + eyeRotationStartU.put(eyeRotationStartM); + eyeRotationStartU.rewind(); + + final float[] eyeRotationEndM = FloatUtil.transposeMatrix(timeWarpMatrices[1].getM(0, mat4Tmp1), mat4Tmp2); + final FloatBuffer eyeRotationEndU = eyeRotationEnd.floatBufferValue(); + eyeRotationEndU.put(eyeRotationEndM); + eyeRotationEndU.rewind(); + } + + /** + * Updates {@link #ovrEyePose} and it's extracted + * {@link #eyeRenderPoseOrientation} and {@link #eyeRenderPosePosition}. + * @param hmdCtx used get the {@link #ovrEyePose} via {@link OVR#ovrHmd_GetEyePose(OvrHmdContext, int)} + */ + private EyePose updateEyePose(final OvrHmdContext hmdCtx) { + ovrEyePose = OVR.ovrHmd_GetEyePose(hmdCtx, eyeName); + final ovrVector3f pos = ovrEyePose.getPosition(); + eyePose.setPosition(pos.getX(), pos.getY(), pos.getZ()); + OVRUtil.copyToQuaternion(ovrEyePose.getOrientation(), eyePose.orientation); + return eyePose; + } + + @Override + public String toString() { + return "Eye["+eyeName+", viewport "+viewport+ + ", "+eyeParameter+ + ", vertices "+vertexCount+", indices "+indexCount+ + ", uvScale["+eyeToSourceUVScale.floatBufferValue().get(0)+", "+eyeToSourceUVScale.floatBufferValue().get(1)+ + "], uvOffset["+eyeToSourceUVOffset.floatBufferValue().get(0)+", "+eyeToSourceUVOffset.floatBufferValue().get(1)+ + "], desc"+OVRUtil.toString(ovrEyeDesc)+", "+eyePose+"]"; + } + } + + private final OVRStereoDevice context; + private final OVREye[] eyes; + private final int distortionBits; + private final int textureCount; + private final DimensionImmutable singleTextureSize; + private final DimensionImmutable totalTextureSize; + private final GLUniformData texUnit0; + + + private final float[] mat4Tmp1 = new float[16]; + private final float[] mat4Tmp2 = new float[16]; + + private ShaderProgram sp; + private ovrFrameTiming frameTiming; + + @Override + public String toString() { + return "OVRDist[distortion["+StereoUtil.distortionBitsToString(distortionBits)+ + "], singleSize "+singleTextureSize+ + ", sbsSize "+totalTextureSize+ + ", texCount "+textureCount+", texUnit "+getTextureUnit()+ + ", "+PlatformPropsImpl.NEWLINE+" "+eyes[0]+", "+PlatformPropsImpl.NEWLINE+" "+eyes[1]+"]"; + } + + + private static int distBits2OVRDistCaps(final int distortionBits) { + int bits = 0; + if( StereoUtil.usesTimewarpDistortion(distortionBits) ) { + bits |= OVR.ovrDistortionCap_TimeWarp; + } + if( StereoUtil.usesChromaticDistortion(distortionBits) ) { + bits |= OVR.ovrDistortionCap_Chromatic; + } + if( StereoUtil.usesVignetteDistortion(distortionBits) ) { + bits |= OVR.ovrDistortionCap_Vignette; + } + return bits; + } + + /* pp */ OVRStereoDeviceRenderer(final OVRStereoDevice context, final int distortionBits, + final int textureCount, final float[] eyePositionOffset, + final ovrEyeRenderDesc[] eyeRenderDescs, final DimensionImmutable singleTextureSize, final DimensionImmutable totalTextureSize, + final RectangleImmutable[] eyeViewports, final int textureUnit) { + if( 1 > textureCount || 2 < textureCount ) { + throw new IllegalArgumentException("textureCount can only be 1 or 2, has "+textureCount); + } + this.context = context; + this.eyes = new OVREye[2]; + this.distortionBits = distortionBits | StereoDeviceRenderer.DISTORTION_BARREL /* always */; + this.textureCount = textureCount; + this.singleTextureSize = singleTextureSize; + this.totalTextureSize = totalTextureSize; + + texUnit0 = new GLUniformData("ovr_Texture0", textureUnit); + + final ovrSizei ovrTextureSize = OVRUtil.createOVRSizei( 1 == textureCount ? totalTextureSize : singleTextureSize ); + eyes[0] = new OVREye(context.handle, this.distortionBits, eyePositionOffset, eyeRenderDescs[0], ovrTextureSize, eyeViewports[0]); + eyes[1] = new OVREye(context.handle, this.distortionBits, eyePositionOffset, eyeRenderDescs[1], ovrTextureSize, eyeViewports[1]); + sp = null; + frameTiming = null; + } + + @Override + public StereoDevice getDevice() { + return context; + } + + @Override + public final int getDistortionBits() { return distortionBits; } + + @Override + public final boolean usesSideBySideStereo() { return true; } + + @Override + public final DimensionImmutable getSingleSurfaceSize() { return singleTextureSize; } + + @Override + public final DimensionImmutable getTotalSurfaceSize() { return totalTextureSize; } + + @Override + public final int getTextureCount() { return textureCount; } + + @Override + public final int getTextureUnit() { return texUnit0.intValue(); } + + @Override + public final boolean ppRequired() { return true; } + + @Override + public final void init(final GL gl) { + if( StereoDevice.DEBUG ) { + System.err.println(JoglVersion.getGLInfo(gl, null).toString()); + } + if( null != sp ) { + throw new IllegalStateException("Already initialized"); + } + final GL2ES2 gl2es2 = gl.getGL2ES2(); + + final String vertexShaderBasename; + final String fragmentShaderBasename; + { + final boolean usesTimewarp = StereoUtil.usesTimewarpDistortion(distortionBits); + final boolean usesChromatic = StereoUtil.usesChromaticDistortion(distortionBits); + + final StringBuilder sb = new StringBuilder(); + sb.append(shaderPrefix01); + if( !usesChromatic && !usesTimewarp ) { + sb.append(shaderPlainSuffix); + } else if( usesChromatic && !usesTimewarp ) { + sb.append(shaderChromaSuffix); + } else if( usesTimewarp ) { + sb.append(shaderTimewarpSuffix); + if( usesChromatic ) { + sb.append(shaderChromaSuffix); + } + } + vertexShaderBasename = sb.toString(); + sb.setLength(0); + sb.append(shaderPrefix01); + if( usesChromatic ) { + sb.append(shaderChromaSuffix); + } else { + sb.append(shaderPlainSuffix); + } + fragmentShaderBasename = sb.toString(); + } + final ShaderCode vp0 = ShaderCode.create(gl2es2, GL2ES2.GL_VERTEX_SHADER, OVRStereoDeviceRenderer.class, "shader", + "shader/bin", vertexShaderBasename, true); + final ShaderCode fp0 = ShaderCode.create(gl2es2, GL2ES2.GL_FRAGMENT_SHADER, OVRStereoDeviceRenderer.class, "shader", + "shader/bin", fragmentShaderBasename, true); + vp0.defaultShaderCustomization(gl2es2, true, true); + fp0.defaultShaderCustomization(gl2es2, true, true); + + sp = new ShaderProgram(); + sp.add(gl2es2, vp0, System.err); + sp.add(gl2es2, fp0, System.err); + if(!sp.link(gl2es2, System.err)) { + throw new GLException("could not link program: "+sp); + } + sp.useProgram(gl2es2, true); + if( 0 > texUnit0.setLocation(gl2es2, sp.program()) ) { + throw new OVRException("Couldn't locate "+texUnit0); + } + eyes[0].linkData(gl2es2, sp); + eyes[1].linkData(gl2es2, sp); + sp.useProgram(gl2es2, false); + } + + @Override + public final void dispose(final GL gl) { + final GL2ES2 gl2es2 = gl.getGL2ES2(); + sp.useProgram(gl2es2, false); + eyes[0].dispose(gl2es2); + eyes[1].dispose(gl2es2); + sp.destroy(gl2es2); + frameTiming = null; + } + + @Override + public final Eye getEye(final int eyeNum) { + return eyes[eyeNum]; + } + + @Override + public final EyePose updateEyePose(final int eyeNum) { + return eyes[eyeNum].updateEyePose(context.handle); + } + + @Override + public final void beginFrame(final GL gl) { + frameTiming = OVR.ovrHmd_BeginFrameTiming(context.handle, 0); + } + + @Override + public final void endFrame(final GL gl) { + if( null == frameTiming ) { + throw new IllegalStateException("beginFrame not called"); + } + OVR.ovrHmd_EndFrameTiming(context.handle); + frameTiming = null; + } + + @Override + public final void ppBegin(final GL gl) { + if( null == sp ) { + throw new IllegalStateException("Not initialized"); + } + if( null == frameTiming ) { + throw new IllegalStateException("beginFrame not called"); + } + if( StereoUtil.usesTimewarpDistortion(distortionBits) ) { + OVR.ovr_WaitTillTime(frameTiming.getTimewarpPointSeconds()); + } + final GL2ES2 gl2es2 = gl.getGL2ES2(); + + gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + gl.glClear(GL.GL_COLOR_BUFFER_BIT); + gl.glActiveTexture(GL.GL_TEXTURE0 + getTextureUnit()); + + gl2es2.glDisable(GL.GL_CULL_FACE); + gl2es2.glDisable(GL.GL_DEPTH_TEST); + gl2es2.glDisable(GL.GL_BLEND); + + if( !gl2es2.isGLcore() ) { + gl2es2.glEnable(GL.GL_TEXTURE_2D); + } + + sp.useProgram(gl2es2, true); + + gl2es2.glUniform(texUnit0); + } + + @Override + public final void ppBothEyes(final GL gl) { + final GL2ES2 gl2es2 = gl.getGL2ES2(); + for(int eyeNum=0; eyeNum<2; eyeNum++) { + final OVREye eye = eyes[eyeNum]; + if( StereoUtil.usesTimewarpDistortion(distortionBits) ) { + eye.updateTimewarp(context.handle, eye.ovrEyePose, mat4Tmp1, mat4Tmp2); + } + eye.updateUniform(gl2es2, sp); + eye.enableVBO(gl2es2, true); + gl2es2.glDrawElements(GL.GL_TRIANGLES, eye.indexCount, GL.GL_UNSIGNED_SHORT, 0); + eyes[eyeNum].enableVBO(gl2es2, false); + } + } + + @Override + public final void ppOneEye(final GL gl, final int eyeNum) { + final OVREye eye = eyes[eyeNum]; + if( StereoUtil.usesTimewarpDistortion(distortionBits) ) { + eye.updateTimewarp(context.handle, eye.ovrEyePose, mat4Tmp1, mat4Tmp2); + } + final GL2ES2 gl2es2 = gl.getGL2ES2(); + + eye.updateUniform(gl2es2, sp); + eye.enableVBO(gl2es2, true); + gl2es2.glDrawElements(GL.GL_TRIANGLES, eye.indexCount, GL.GL_UNSIGNED_SHORT, 0); + eyes[eyeNum].enableVBO(gl2es2, false); + } + + @Override + public final void ppEnd(final GL gl) { + sp.useProgram(gl.getGL2ES2(), false); + } +} diff --git a/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRUtil.java b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRUtil.java index 6c1cdc015..4de05fc92 100644 --- a/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRUtil.java +++ b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRUtil.java @@ -27,7 +27,11 @@ */ package jogamp.opengl.oculusvr; -import jogamp.opengl.Debug; +import javax.media.nativewindow.util.Dimension; +import javax.media.nativewindow.util.DimensionImmutable; +import javax.media.nativewindow.util.Point; +import javax.media.nativewindow.util.PointImmutable; +import javax.media.nativewindow.util.RectangleImmutable; import com.jogamp.oculusvr.ovrEyeRenderDesc; import com.jogamp.oculusvr.ovrFovPort; @@ -44,8 +48,6 @@ import com.jogamp.opengl.math.Quaternion; * OculusVR Data Conversion Helper Functions */ public class OVRUtil { - public static final boolean DEBUG = Debug.debug("OVR"); - public static ovrRecti createOVRRecti(final int[] rect) { final ovrRecti res = ovrRecti.create(); final ovrVector2i pos = res.getPos(); @@ -56,6 +58,16 @@ public class OVRUtil { size.setH(rect[3]); return res; } + public static ovrRecti createOVRRecti(final RectangleImmutable rect) { + final ovrRecti res = ovrRecti.create(); + final ovrVector2i pos = res.getPos(); + final ovrSizei size = res.getSize(); + pos.setX(rect.getX()); + pos.setY(rect.getY()); + size.setW(rect.getWidth()); + size.setH(rect.getHeight()); + return res; + } public static ovrRecti[] createOVRRectis(final int[][] rects) { final ovrRecti[] res = new ovrRecti[rects.length]; for(int i=0; i<res.length; i++) { @@ -69,11 +81,24 @@ public class OVRUtil { res.setH(size[1]); return res; } - public static Quaternion getQuaternion(final ovrQuatf q) { - return new Quaternion(q.getX(), q.getY(), q.getZ(), q.getW()); + public static ovrSizei createOVRSizei(final DimensionImmutable size) { + final ovrSizei res = ovrSizei.create(); + res.setW(size.getWidth()); + res.setH(size.getHeight()); + return res; } - public static void copyToQuaternion(final ovrQuatf in, final Quaternion out) { - out.set(in.getX(), in.getY(), in.getZ(), in.getW()); + public static DimensionImmutable getOVRSizei(final ovrSizei v) { + return new Dimension(v.getW(), v.getH()); + } + public static PointImmutable getVec2iAsPoint(final ovrVector2i v) { + return new Point(v.getX(), v.getY()); + } + public static int[] getVec2i(final ovrVector2i v) { + return new int[] { v.getX(), v.getY() }; + } + public static void copyVec2iToInt(final ovrVector2i v, final int[] res) { + res[0] = v.getX(); + res[1] = v.getY(); } public static float[] getVec3f(final ovrVector3f v) { return new float[] { v.getX(), v.getY(), v.getZ() }; @@ -83,12 +108,33 @@ public class OVRUtil { res[1] = v.getY(); res[2] = v.getZ(); } + public static Quaternion getQuaternion(final ovrQuatf q) { + return new Quaternion(q.getX(), q.getY(), q.getZ(), q.getW()); + } + public static void copyToQuaternion(final ovrQuatf in, final Quaternion out) { + out.set(in.getX(), in.getY(), in.getZ(), in.getW()); + } public static FovHVHalves getFovHV(final ovrFovPort tanHalfFov) { return new FovHVHalves(tanHalfFov.getLeftTan(), tanHalfFov.getRightTan(), tanHalfFov.getUpTan(), tanHalfFov.getDownTan(), true); } + public static ovrFovPort getOVRFovPort(final FovHVHalves fovHVHalves) { + final ovrFovPort tanHalfFov = ovrFovPort.create(); + if( fovHVHalves.inTangents ) { + tanHalfFov.setLeftTan(fovHVHalves.left); + tanHalfFov.setRightTan(fovHVHalves.right); + tanHalfFov.setUpTan(fovHVHalves.top); + tanHalfFov.setDownTan(fovHVHalves.bottom); + } else { + tanHalfFov.setLeftTan((float)Math.tan(fovHVHalves.left)); + tanHalfFov.setRightTan((float)Math.tan(fovHVHalves.right)); + tanHalfFov.setUpTan((float)Math.tan(fovHVHalves.top)); + tanHalfFov.setDownTan((float)Math.tan(fovHVHalves.bottom)); + } + return tanHalfFov; + } public static String toString(final ovrFovPort fov) { return "["+fov.getLeftTan()+" l, "+fov.getRightTan()+" r, "+ diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/GearsES2.java b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/GearsES2.java index 2709aa608..84cd8936e 100644 --- a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/GearsES2.java +++ b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/GearsES2.java @@ -35,7 +35,7 @@ import com.jogamp.opengl.math.FloatUtil; import com.jogamp.opengl.math.Quaternion; import com.jogamp.opengl.math.VectorUtil; import com.jogamp.opengl.test.junit.jogl.demos.GearsObject; -import com.jogamp.opengl.util.CustomRendererListener; +import com.jogamp.opengl.util.CustomGLEventListener; import com.jogamp.opengl.util.PMVMatrix; import com.jogamp.opengl.util.TileRendererBase; import com.jogamp.opengl.util.glsl.ShaderCode; @@ -43,7 +43,7 @@ import com.jogamp.opengl.util.glsl.ShaderProgram; import com.jogamp.opengl.util.glsl.ShaderState; import com.jogamp.opengl.util.stereo.EyeParameter; import com.jogamp.opengl.util.stereo.EyePose; -import com.jogamp.opengl.util.stereo.StereoRendererListener; +import com.jogamp.opengl.util.stereo.StereoGLEventListener; import java.nio.FloatBuffer; @@ -60,7 +60,7 @@ import javax.media.opengl.fixedfunc.GLMatrixFunc; * GearsES2.java <BR> * @author Brian Paul (converted to Java by Ron Cemer and Sven Gothel) <P> */ -public class GearsES2 implements StereoRendererListener, TileRendererBase.TileRendererListener { +public class GearsES2 implements StereoGLEventListener, TileRendererBase.TileRendererListener { private final FloatBuffer lightPos = Buffers.newDirectFloatBuffer( new float[] { 5.0f, 5.0f, 10.0f } ); private ShaderState st = null; @@ -419,8 +419,8 @@ public class GearsES2 implements StereoRendererListener, TileRendererBase.TileRe private final float[] vec3Tmp3 = new float[3]; @Override - public void reshapeEye(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height, - final EyeParameter eyeParam, final EyePose eyePose) { + public void reshapeForEye(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height, + final EyeParameter eyeParam, final EyePose eyePose) { final GL2ES2 gl = drawable.getGL().getGL2ES2(); pmvMatrix.glMatrixMode(GLMatrixFunc.GL_PROJECTION); final float[] mat4Projection = FloatUtil.makePerspective(mat4Tmp1, 0, true, eyeParam.fovhv, zNear, zFar); @@ -507,8 +507,8 @@ public class GearsES2 implements StereoRendererListener, TileRendererBase.TileRe System.err.println(Thread.currentThread()+" GearsES2.display "+sid()+" "+drawable.getSurfaceWidth()+"x"+drawable.getSurfaceHeight()+", swapInterval "+swapInterval+", drawable 0x"+Long.toHexString(drawable.getHandle())); } - final boolean repeatedFrame = 0 != ( CustomRendererListener.DISPLAY_REPEAT & flags ); - final boolean dontClear = 0 != ( CustomRendererListener.DISPLAY_DONTCLEAR & flags ); + final boolean repeatedFrame = 0 != ( CustomGLEventListener.DISPLAY_REPEAT & flags ); + final boolean dontClear = 0 != ( CustomGLEventListener.DISPLAY_DONTCLEAR & flags ); // Turn the gears' teeth if( doRotate && !repeatedFrame ) { diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieSBSStereo.java b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieSBSStereo.java new file mode 100644 index 000000000..a2a39d631 --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieSBSStereo.java @@ -0,0 +1,859 @@ +/** + * Copyright 2012 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.demos.es2.av; + +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.FloatBuffer; + +import javax.media.opengl.GL; +import javax.media.opengl.GL2ES2; +import javax.media.opengl.GLAnimatorControl; +import javax.media.opengl.GLAutoDrawable; +import javax.media.opengl.GLES2; +import javax.media.opengl.GLException; +import javax.media.opengl.GLUniformData; +import javax.media.opengl.fixedfunc.GLMatrixFunc; + +import com.jogamp.common.os.Platform; +import com.jogamp.graph.curve.Region; +import com.jogamp.graph.curve.opengl.GLRegion; +import com.jogamp.graph.curve.opengl.RegionRenderer; +import com.jogamp.graph.font.Font; +import com.jogamp.newt.Window; +import com.jogamp.newt.event.KeyAdapter; +import com.jogamp.newt.event.KeyEvent; +import com.jogamp.newt.event.KeyListener; +import com.jogamp.newt.event.MouseAdapter; +import com.jogamp.newt.event.MouseEvent; +import com.jogamp.newt.event.MouseListener; +import com.jogamp.newt.opengl.GLWindow; +import com.jogamp.opengl.GLExtensions; +import com.jogamp.opengl.JoglVersion; +import com.jogamp.opengl.math.FloatUtil; +import com.jogamp.opengl.math.Quaternion; +import com.jogamp.opengl.math.VectorUtil; +import com.jogamp.opengl.test.junit.graph.TextRendererGLELBase; +import com.jogamp.opengl.test.junit.util.UITestCase; +import com.jogamp.opengl.util.CustomGLEventListener; +import com.jogamp.opengl.util.GLArrayDataServer; +import com.jogamp.opengl.util.PMVMatrix; +import com.jogamp.opengl.util.av.GLMediaPlayer; +import com.jogamp.opengl.util.av.GLMediaPlayer.GLMediaEventListener; +import com.jogamp.opengl.util.av.GLMediaPlayerFactory; +import com.jogamp.opengl.util.glsl.ShaderCode; +import com.jogamp.opengl.util.glsl.ShaderProgram; +import com.jogamp.opengl.util.glsl.ShaderState; +import com.jogamp.opengl.util.stereo.EyeParameter; +import com.jogamp.opengl.util.stereo.EyePose; +import com.jogamp.opengl.util.stereo.StereoClientRenderer; +import com.jogamp.opengl.util.stereo.StereoGLEventListener; +import com.jogamp.opengl.util.texture.Texture; +import com.jogamp.opengl.util.texture.TextureCoords; +import com.jogamp.opengl.util.texture.TextureSequence; +import com.jogamp.opengl.util.texture.TextureSequence.TextureFrame; + +/** + * Side-By-Side (SBS) 3D Movie Player for {@link StereoClientRenderer} + * <p> + * The movie is assumed to be symmetrical SBS, + * the left-eye receives the left-part of the texture + * and the right-eye the right-part. + * </p> + */ +public class MovieSBSStereo implements StereoGLEventListener { + public static final String WINDOW_KEY = "window"; + public static final String STEREO_RENDERER_KEY = "stereo"; + public static final String PLAYER = "player"; + + private static boolean waitForKey = false; + private int surfWidth, surfHeight; + private int prevMouseX; // , prevMouseY; + private int rotate = 0; + private float zoom0; + private float zoom1; + private float zoom; + private long startTime; + private final float alpha = 1.0f; + + private GLMediaPlayer mPlayer; + private boolean mPlayerScaleOrig; + private float[] verts = null; + private GLArrayDataServer interleavedVBOLeft; + private GLArrayDataServer interleavedVBORight; + private volatile boolean resetGLState = false; + private StereoClientRenderer stereoClientRenderer; + + private ShaderState st; + private PMVMatrix pmvMatrix; + private GLUniformData pmvMatrixUniform; + private static final String shaderBasename = "texsequence_xxx"; + private static final String myTextureLookupName = "myTexture2D"; + + /** Blender's Big Buck Bunny: 24f 416p H.264, AAC 48000 Hz, 2 ch, mpeg stream. */ + public static final URI defURI; + static { + URI _defURI = null; + try { + // Blender's Big Buck Bunny Trailer: 24f 640p VP8, Vorbis 44100Hz mono, WebM/Matroska Stream. + // _defURI = new URI("http://video.webmfiles.org/big-buck-bunny_trailer.webm"); + _defURI = new URI("http://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4"); + } catch (final URISyntaxException e) { + e.printStackTrace(); + } + defURI = _defURI; + } + + final int[] textSampleCount = { 4 }; + + private final class InfoTextRendererGLELBase extends TextRendererGLELBase { + private final Font font = getFont(0, 0, 0); + private final float fontSize = 1f; // 0.01f; + private final GLRegion regionFPS; + + InfoTextRendererGLELBase(final int rmode, final boolean lowPerfDevice) { + // FIXME: Graph TextRenderer does not AA well w/o MSAA and FBO + super(rmode, textSampleCount); + this.setRendererCallbacks(RegionRenderer.defaultBlendEnable, RegionRenderer.defaultBlendDisable); + if( lowPerfDevice ) { + regionFPS = null; + } else { + regionFPS = GLRegion.create(renderModes, null); + System.err.println("RegionFPS "+Region.getRenderModeString(renderModes)+", sampleCount "+textSampleCount[0]+", class "+regionFPS.getClass().getName()); + } + staticRGBAColor[0] = 0.9f; + staticRGBAColor[1] = 0.9f; + staticRGBAColor[2] = 0.9f; + staticRGBAColor[3] = 1.0f; + } + + @Override + public void init(final GLAutoDrawable drawable) { + super.init(drawable); + } + + @Override + public void dispose(final GLAutoDrawable drawable) { + if( null != regionFPS ) { + regionFPS.destroy(drawable.getGL().getGL2ES2()); + } + super.dispose(drawable); + } + + @Override + public void display(final 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 = 0; // drawable.getSurfaceHeight(); + + final float aspect = (float)mPlayer.getWidth() / (float)mPlayer.getHeight(); + + final String ptsPrec = null != regionFPS ? "3.1" : "3.0"; + final String text1 = String.format("%0"+ptsPrec+"f/%0"+ptsPrec+"f s, %s (%01.2fx, vol %01.2f), a %01.2f, fps %02.1f -> %02.1f / %02.1f", + pts, mPlayer.getDuration() / 1000f, + mPlayer.getState().toString().toLowerCase(), mPlayer.getPlaySpeed(), mPlayer.getAudioVolume(), + aspect, mPlayer.getFramerate(), lfps, tfps); + 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 ) { + // We share ClearColor w/ MovieSimple's init ! + final float pixelSize = font.getPixelSize(fontSize, dpiH); + if( null != regionFPS ) { + renderString(drawable, font, pixelSize, text1, 1 /* col */, 1 /* row */, 0, 0, -1, regionFPS); // no-cache + } else { + renderString(drawable, font, pixelSize, text1, 1 /* col */, 1 /* row */, 0, 0, -1, true); + } + renderString(drawable, font, pixelSize, text2, 1 /* col */, -4 /* row */, 0, height, -1, true); + renderString(drawable, font, pixelSize, text3, 1 /* col */, -3 /* row */, 0, height, 0, true); + renderString(drawable, font, pixelSize, text4, 1 /* col */, -2 /* row */, 0, height, 1, true); + } + } }; + private InfoTextRendererGLELBase textRendererGLEL = null; + private boolean displayOSD = false; + + private final MouseListener mouseAction = new MouseAdapter() { + public void mousePressed(final MouseEvent e) { + if(e.getY()<=surfHeight/2 && null!=mPlayer && 1 == e.getClickCount()) { + if(GLMediaPlayer.State.Playing == mPlayer.getState()) { + mPlayer.pause(false); + } else { + mPlayer.play(); + } + } + } + public void mouseReleased(final MouseEvent e) { + if(e.getY()<=surfHeight/2) { + rotate = -1; + zoom = zoom0; + System.err.println("zoom: "+zoom); + } + } + public void mouseMoved(final MouseEvent e) { + prevMouseX = e.getX(); + // prevMouseY = e.getY(); + } + public void mouseDragged(final MouseEvent e) { + final int x = e.getX(); + final int y = e.getY(); + + if(y>surfHeight/2) { + final float dp = (float)(x-prevMouseX)/(float)surfWidth; + final int pts0 = GLMediaPlayer.STREAM_ID_NONE != mPlayer.getVID() ? mPlayer.getVideoPTS() : mPlayer.getAudioPTS(); + mPlayer.seek(pts0 + (int) (mPlayer.getDuration() * dp)); + } else { + mPlayer.play(); + rotate = 1; + zoom = zoom1; + } + + prevMouseX = x; + // prevMouseY = y; + } + public void mouseWheelMoved(final MouseEvent e) { + if( !e.isShiftDown() ) { + zoom += e.getRotation()[1]/10f; // vertical: wheel + System.err.println("zoom: "+zoom); + } + } }; + + private final KeyListener keyAction = new KeyAdapter() { + public void keyReleased(final KeyEvent e) { + if( e.isAutoRepeat() ) { + return; + } + System.err.println("MC "+e); + final int pts0 = GLMediaPlayer.STREAM_ID_NONE != mPlayer.getVID() ? mPlayer.getVideoPTS() : mPlayer.getAudioPTS(); + int pts1 = 0; + switch(e.getKeySymbol()) { + 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; + case KeyEvent.VK_LEFT: pts1 = pts0 - 1000; break; + case KeyEvent.VK_DOWN: pts1 = pts0 - 10000; break; + case KeyEvent.VK_PAGE_DOWN: pts1 = pts0 - 30000; break; + case KeyEvent.VK_ESCAPE: + case KeyEvent.VK_HOME: + case KeyEvent.VK_BACK_SPACE: { + mPlayer.seek(0); + break; + } + case KeyEvent.VK_SPACE: { + if(GLMediaPlayer.State.Paused == mPlayer.getState()) { + mPlayer.play(); + } else { + mPlayer.pause(false); + } + break; + } + case KeyEvent.VK_MULTIPLY: + mPlayer.setPlaySpeed(1.0f); + break; + case KeyEvent.VK_SUBTRACT: { + float playSpeed = mPlayer.getPlaySpeed(); + if( e.isShiftDown() ) { + playSpeed /= 2.0f; + } else { + playSpeed -= 0.1f; + } + mPlayer.setPlaySpeed(playSpeed); + } break; + case KeyEvent.VK_ADD: { + float playSpeed = mPlayer.getPlaySpeed(); + if( e.isShiftDown() ) { + playSpeed *= 2.0f; + } else { + playSpeed += 0.1f; + } + mPlayer.setPlaySpeed(playSpeed); + } break; + case KeyEvent.VK_M: { + float audioVolume = mPlayer.getAudioVolume(); + if( audioVolume > 0.5f ) { + audioVolume = 0f; + } else { + audioVolume = 1f; + } + mPlayer.setAudioVolume(audioVolume); + } break; + } + + if( 0 != pts1 ) { + mPlayer.seek(pts1); + } + } }; + + /** user needs to issue {@link #initStream(URI, int, int, int)} afterwards. */ + public MovieSBSStereo() throws IllegalStateException { + mPlayerScaleOrig = false; + mPlayer = GLMediaPlayerFactory.createDefault(); + mPlayer.attachObject(PLAYER, this); + System.out.println("pC.1a "+mPlayer); + } + + public void initStream(final URI streamLoc, final int vid, final int aid, final int textureCount) { + mPlayer.initStream(streamLoc, vid, aid, textureCount); + System.out.println("pC.1b "+mPlayer); + } + + public GLMediaPlayer getGLMediaPlayer() { return mPlayer; } + + public void setScaleOrig(final boolean v) { + mPlayerScaleOrig = v; + } + + public void setStereoClientRenderer(final StereoClientRenderer scr) { + stereoClientRenderer = scr; + } + public StereoClientRenderer getStereoClientRenderer() { return stereoClientRenderer; } + + public void resetGLState() { + resetGLState = true; + } + + private void initShader(final GL2ES2 gl) { + // Create & Compile the shader objects + final ShaderCode rsVp = ShaderCode.create(gl, GL2ES2.GL_VERTEX_SHADER, MovieSBSStereo.class, + "../shader", "../shader/bin", shaderBasename, true); + final ShaderCode rsFp = ShaderCode.create(gl, GL2ES2.GL_FRAGMENT_SHADER, MovieSBSStereo.class, + "../shader", "../shader/bin", shaderBasename, true); + + boolean preludeGLSLVersion = true; + if( GLES2.GL_TEXTURE_EXTERNAL_OES == mPlayer.getTextureTarget() ) { + if( !gl.isExtensionAvailable(GLExtensions.OES_EGL_image_external) ) { + throw new GLException(GLExtensions.OES_EGL_image_external+" requested but not available"); + } + if( Platform.OSType.ANDROID == Platform.getOSType() && gl.isGLES3() ) { + // Bug on Nexus 10, ES3 - Android 4.3, where + // GL_OES_EGL_image_external extension directive leads to a failure _with_ '#version 300 es' ! + // P0003: Extension 'GL_OES_EGL_image_external' not supported + preludeGLSLVersion = false; + } + } + rsVp.defaultShaderCustomization(gl, preludeGLSLVersion, true); + + int rsFpPos = preludeGLSLVersion ? rsFp.addGLSLVersion(gl) : 0; + rsFpPos = rsFp.insertShaderSource(0, rsFpPos, mPlayer.getRequiredExtensionsShaderStub()); + rsFpPos = rsFp.addDefaultShaderPrecision(gl, rsFpPos); + + final String texLookupFuncName = mPlayer.getTextureLookupFunctionName(myTextureLookupName); + rsFp.replaceInShaderSource(myTextureLookupName, texLookupFuncName); + + // Inject TextureSequence shader details + final StringBuilder sFpIns = new StringBuilder(); + sFpIns.append("uniform ").append(mPlayer.getTextureSampler2DType()).append(" mgl_ActiveTexture;\n"); + sFpIns.append(mPlayer.getTextureLookupFragmentShaderImpl()); + rsFp.insertShaderSource(0, "TEXTURE-SEQUENCE-CODE-BEGIN", 0, sFpIns); + + // Create & Link the shader program + final ShaderProgram sp = new ShaderProgram(); + sp.add(rsVp); + sp.add(rsFp); + if(!sp.link(gl, System.err)) { + throw new GLException("Couldn't link program: "+sp); + } + + // Let's manage all our states using ShaderState. + st = new ShaderState(); + st.attachShaderProgram(gl, sp, false); + } + + @Override + public void init(final GLAutoDrawable drawable) { + if(null == mPlayer) { + throw new InternalError("mPlayer null"); + } + if( GLMediaPlayer.State.Uninitialized == mPlayer.getState() ) { + throw new IllegalStateException("mPlayer in uninitialized state: "+mPlayer); + } + final boolean hasVideo = GLMediaPlayer.STREAM_ID_NONE != mPlayer.getVID(); + resetGLState = false; + + zoom0 = -2.1f; + zoom1 = -5f; + zoom = 0f; + + final GL2ES2 gl = drawable.getGL().getGL2ES2(); + System.err.println(JoglVersion.getGLInfo(gl, null)); + System.err.println("Alpha: "+alpha+", opaque "+drawable.getChosenGLCapabilities().isBackgroundOpaque()+ + ", "+drawable.getClass().getName()+", "+drawable); + + if(waitForKey) { + UITestCase.waitForKey("Init>"); + } + final Texture tex; + try { + System.out.println("p0 "+mPlayer); + if(GLMediaPlayer.State.Initialized == mPlayer.getState() ) { + mPlayer.initGL(gl); + } + System.out.println("p1 "+mPlayer); + final TextureFrame frame = mPlayer.getLastTexture(); + if( null != frame ) { + if( !hasVideo ) { + throw new InternalError("XXX: "+mPlayer); + } + tex = frame.getTexture(); + if( null == tex ) { + throw new InternalError("XXX: "+mPlayer); + } + } else { + tex = null; + if( hasVideo ) { + throw new InternalError("XXX: "+mPlayer); + } + } + mPlayer.setTextureMinMagFilter( new int[] { GL.GL_NEAREST, GL.GL_NEAREST } ); + } catch (final Exception glex) { + glex.printStackTrace(); + if(null != mPlayer) { + mPlayer.destroy(gl); + mPlayer = null; + } + throw new GLException(glex); + } + + if( hasVideo ) { + initShader(gl); + + // Push the 1st uniform down the path + st.useProgram(gl, true); + + final int[] viewPort = new int[] { 0, 0, drawable.getSurfaceWidth(), drawable.getSurfaceHeight()}; + pmvMatrix = new PMVMatrix(); + reshapePMV(viewPort[2], viewPort[3]); + pmvMatrixUniform = new GLUniformData("mgl_PMVMatrix", 4, 4, pmvMatrix.glGetPMvMatrixf()); + if(!st.uniform(gl, pmvMatrixUniform)) { + throw new GLException("Error setting PMVMatrix in shader: "+st); + } + if(!st.uniform(gl, new GLUniformData("mgl_ActiveTexture", mPlayer.getTextureUnit()))) { + throw new GLException("Error setting mgl_ActiveTexture in shader: "+st); + } + + final float dWidth = drawable.getSurfaceWidth(); + final float dHeight = drawable.getSurfaceHeight(); + final float mWidth = mPlayer.getWidth(); + final float mHeight = mPlayer.getHeight(); + final float mAspect = mWidth/mHeight; + System.err.println("XXX0: mov aspect: "+mAspect); + float xs, ys; + if(mPlayerScaleOrig && mWidth < dWidth && mHeight < dHeight) { + xs = mAspect * ( mWidth / dWidth ) ; ys = xs / mAspect ; + } else { + xs = mAspect; ys = 1f; // b>h + } + verts = new float[] { -1f*xs, -1f*ys, 0f, // LB + 1f*xs, 1f*ys, 0f // RT + }; + { + System.err.println("XXX0: pixel LB: "+verts[0]+", "+verts[1]+", "+verts[2]); + System.err.println("XXX0: pixel RT: "+verts[3]+", "+verts[4]+", "+verts[5]); + final float[] winLB = new float[3]; + final float[] winRT = new float[3]; + pmvMatrix.gluProject(verts[0], verts[1], verts[2], viewPort, 0, winLB, 0); + pmvMatrix.gluProject(verts[3], verts[4], verts[5], viewPort, 0, winRT, 0); + System.err.println("XXX0: win LB: "+winLB[0]+", "+winLB[1]+", "+winLB[2]); + System.err.println("XXX0: win RT: "+winRT[0]+", "+winRT[1]+", "+winRT[2]); + } + + interleavedVBOLeft = GLArrayDataServer.createGLSLInterleaved(3+4+2, GL.GL_FLOAT, false, 3*4, GL.GL_STATIC_DRAW); + { + interleavedVBOLeft.addGLSLSubArray("mgl_Vertex", 3, GL.GL_ARRAY_BUFFER); + interleavedVBOLeft.addGLSLSubArray("mgl_Color", 4, GL.GL_ARRAY_BUFFER); + interleavedVBOLeft.addGLSLSubArray("mgl_MultiTexCoord", 2, GL.GL_ARRAY_BUFFER); + } + interleavedVBORight = GLArrayDataServer.createGLSLInterleaved(3+4+2, GL.GL_FLOAT, false, 3*4, GL.GL_STATIC_DRAW); + { + interleavedVBORight.addGLSLSubArray("mgl_Vertex", 3, GL.GL_ARRAY_BUFFER); + interleavedVBORight.addGLSLSubArray("mgl_Color", 4, GL.GL_ARRAY_BUFFER); + interleavedVBORight.addGLSLSubArray("mgl_MultiTexCoord", 2, GL.GL_ARRAY_BUFFER); + } + updateInterleavedVBO(gl, interleavedVBOLeft, tex, 0); + updateInterleavedVBO(gl, interleavedVBORight, tex, 1); + + st.ownAttribute(interleavedVBOLeft, true); + st.ownAttribute(interleavedVBORight, true); + gl.glClearColor(0.3f, 0.3f, 0.3f, 0.3f); + + gl.glEnable(GL.GL_DEPTH_TEST); + + st.useProgram(gl, false); + + // Let's show the completed shader state .. + System.out.println("iVBOLeft : "+interleavedVBOLeft); + System.out.println("iVBORight: "+interleavedVBORight); + System.out.println(st); + } + + mPlayer.play(); + System.out.println("play.0 "+mPlayer); + startTime = System.currentTimeMillis(); + + final Object upstreamWidget = drawable.getUpstreamWidget(); + if (upstreamWidget instanceof Window) { + final Window window = (Window) upstreamWidget; + window.addMouseListener(mouseAction); + window.addKeyListener(keyAction); + surfWidth = window.getSurfaceWidth(); + surfHeight = window.getSurfaceHeight(); + } + final int rmode = drawable.getChosenGLCapabilities().getSampleBuffers() ? 0 : Region.VBAA_RENDERING_BIT; + final boolean lowPerfDevice = gl.isGLES(); + textRendererGLEL = new InfoTextRendererGLELBase(rmode, lowPerfDevice); + textRendererGLEL.init(drawable); + } + + protected void updateInterleavedVBO(final GL gl, final GLArrayDataServer iVBO, final Texture tex, final int eyeNum) { + final boolean wasEnabled = iVBO.enabled(); + iVBO.seal(gl, false); + iVBO.rewind(); + { + final FloatBuffer ib = (FloatBuffer)iVBO.getBuffer(); + final TextureCoords tc = tex.getImageTexCoords(); + final float texHalfWidth = tc.right()/2f; + System.err.println("XXX0: "+tc+", texHalfWidth "+texHalfWidth); + System.err.println("XXX0: tex aspect: "+tex.getAspectRatio()); + System.err.println("XXX0: tex y-flip: "+tex.getMustFlipVertically()); + + // left-bottom + ib.put(verts[0]); ib.put(verts[1]); ib.put(verts[2]); + ib.put( 1); ib.put( 1); ib.put( 1); ib.put(alpha); + if( 0 == eyeNum ) { + ib.put( tc.left() ); ib.put( tc.bottom() ); + } else { + ib.put( tc.left() + texHalfWidth ); ib.put( tc.bottom() ); + } + + // right-bottom + ib.put(verts[3]); ib.put(verts[1]); ib.put(verts[2]); + ib.put( 1); ib.put( 1); ib.put( 1); ib.put(alpha); + if( 0 == eyeNum ) { + ib.put( texHalfWidth ); ib.put( tc.bottom() ); + } else { + ib.put( tc.right() ); ib.put( tc.bottom() ); + } + + // left-top + ib.put(verts[0]); ib.put(verts[4]); ib.put(verts[2]); + ib.put( 1); ib.put( 1); ib.put( 1); ib.put(alpha); + if( 0 == eyeNum ) { + ib.put( tc.left() ); ib.put( tc.top() ); + } else { + ib.put( tc.left() + texHalfWidth ); ib.put( tc.top() ); + } + + // right-top + ib.put(verts[3]); ib.put(verts[4]); ib.put(verts[2]); + ib.put( 1); ib.put( 1); ib.put( 1); ib.put(alpha); + if( 0 == eyeNum ) { + ib.put( texHalfWidth ); ib.put( tc.top() ); + } else { + ib.put( tc.right() ); ib.put( tc.top() ); + } + } + iVBO.seal(gl, true); + if( !wasEnabled ) { + iVBO.enableBuffer(gl, false); + } + } + + @Override + public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) { + final GL2ES2 gl = drawable.getGL().getGL2ES2(); + surfWidth = width; + surfHeight = height; + + if(null == mPlayer) { return; } + + if(null != st) { + reshapePMV(width, height); + st.useProgram(gl, true); + st.uniform(gl, pmvMatrixUniform); + st.useProgram(gl, false); + } + + System.out.println("pR "+mPlayer); + textRendererGLEL.reshape(drawable, x, y, width, height); + } + + private final float zNear = 0.1f; + private final float zFar = 10000f; + + private void reshapePMV(final int width, final int height) { + pmvMatrix.glMatrixMode(GLMatrixFunc.GL_PROJECTION); + pmvMatrix.glLoadIdentity(); + pmvMatrix.gluPerspective(45.0f, (float)width / (float)height, zNear, zFar); + + pmvMatrix.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); + pmvMatrix.glLoadIdentity(); + pmvMatrix.glTranslatef(0, 0, zoom0); + } + + private final float[] mat4Tmp1 = new float[16]; + private final float[] mat4Tmp2 = new float[16]; + private final float[] vec3Tmp1 = new float[3]; + private final float[] vec3Tmp2 = new float[3]; + private final float[] vec3Tmp3 = new float[3]; + + GLArrayDataServer interleavedVBOCurrent = null; + + @Override + public void reshapeForEye(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height, + final EyeParameter eyeParam, final EyePose eyePose) { + final GL2ES2 gl = drawable.getGL().getGL2ES2(); + interleavedVBOCurrent = 0 == eyeParam.number ? interleavedVBOLeft : interleavedVBORight; + + surfWidth = drawable.getSurfaceWidth(); + surfHeight = drawable.getSurfaceHeight(); + + if(null == mPlayer) { return; } + if(null == st) { return; } + + pmvMatrix.glMatrixMode(GLMatrixFunc.GL_PROJECTION); + final float[] mat4Projection = FloatUtil.makePerspective(mat4Tmp1, 0, true, eyeParam.fovhv, zNear, zFar); + pmvMatrix.glLoadMatrixf(mat4Projection, 0); + + pmvMatrix.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); + final Quaternion rollPitchYaw = new Quaternion(); + final float[] shiftedEyePos = rollPitchYaw.rotateVector(vec3Tmp1, 0, eyePose.position, 0); + VectorUtil.addVec3(shiftedEyePos, shiftedEyePos, eyeParam.positionOffset); + + rollPitchYaw.mult(eyePose.orientation); + final float[] up = rollPitchYaw.rotateVector(vec3Tmp2, 0, VectorUtil.VEC3_UNIT_Y, 0); + final float[] forward = rollPitchYaw.rotateVector(vec3Tmp3, 0, VectorUtil.VEC3_UNIT_Z_NEG, 0); + final float[] center = VectorUtil.addVec3(forward, shiftedEyePos, forward); + + final float[] mLookAt = FloatUtil.makeLookAt(mat4Tmp1, 0, shiftedEyePos, 0, center, 0, up, 0, mat4Tmp2); + final float[] mViewAdjust = FloatUtil.makeTranslation(mat4Tmp2, true, eyeParam.distNoseToPupilX, eyeParam.distMiddleToPupilY, eyeParam.eyeReliefZ); + final float[] mat4Modelview = FloatUtil.multMatrix(mViewAdjust, mLookAt); + pmvMatrix.glLoadMatrixf(mat4Modelview, 0); + pmvMatrix.glTranslatef(0, 0, zoom0); + st.useProgram(gl, true); + st.uniform(gl, pmvMatrixUniform); + st.useProgram(gl, false); + textRendererGLEL.reshape(drawable, x, y, width, height); + } + + @Override + public void dispose(final GLAutoDrawable drawable) { + textRendererGLEL.dispose(drawable); + textRendererGLEL = null; + disposeImpl(drawable, true); + } + + private void disposeImpl(final GLAutoDrawable drawable, final boolean disposePlayer) { + if(null == mPlayer) { return; } + + final Object upstreamWidget = drawable.getUpstreamWidget(); + if (upstreamWidget instanceof Window) { + final Window window = (Window) upstreamWidget; + window.removeMouseListener(mouseAction); + window.removeKeyListener(keyAction); + } + + System.out.println("pD.1 "+mPlayer+", disposePlayer "+disposePlayer); + final GL2ES2 gl = drawable.getGL().getGL2ES2(); + if( disposePlayer ) { + mPlayer.destroy(gl); + System.out.println("pD.X "+mPlayer); + mPlayer=null; + } + pmvMatrixUniform = null; + if(null != pmvMatrix) { + pmvMatrix=null; + } + if(null != st) { + st.destroy(gl); + st=null; + } + } + + long lastPerfPos = 0; + + @Override + public void display(final GLAutoDrawable drawable) { + display(drawable, 0); + } + + @Override + public void display(final GLAutoDrawable drawable, final int flags) { + // TODO Auto-generated method stub + final boolean repeatedFrame = 0 != ( CustomGLEventListener.DISPLAY_REPEAT & flags ); + final boolean dontClear = 0 != ( CustomGLEventListener.DISPLAY_DONTCLEAR & flags ); + final GLArrayDataServer iVBO = null != interleavedVBOCurrent ? interleavedVBOCurrent : interleavedVBOLeft; + + final GL2ES2 gl = drawable.getGL().getGL2ES2(); + if(null == mPlayer) { return; } + + if( resetGLState ) { + resetGLState = false; + System.err.println("XXX resetGLState"); + disposeImpl(drawable, false); + init(drawable); + reshape(drawable, 0, 0, drawable.getSurfaceWidth(), drawable.getSurfaceHeight()); + } + + final long currentPos = System.currentTimeMillis(); + if( currentPos - lastPerfPos > 2000 ) { + System.err.println( mPlayer.getPerfString() ); + lastPerfPos = currentPos; + } + + if( !dontClear ) { + gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); + } + + if(null == st) { + return; + } + + st.useProgram(gl, true); + + pmvMatrix.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); + pmvMatrix.glPushMatrix(); + pmvMatrix.glTranslatef(0, 0, zoom); + if(rotate > 0) { + final float ang = ((System.currentTimeMillis() - startTime) * 360.0f) / 8000.0f; + pmvMatrix.glRotatef(ang, 0, 0, 1); + } else { + rotate = 0; + } + st.uniform(gl, pmvMatrixUniform); + iVBO.enableBuffer(gl, true); + Texture tex = null; + if(null!=mPlayer) { + final TextureSequence.TextureFrame texFrame; + if( repeatedFrame ) { + texFrame=mPlayer.getLastTexture(); + } else { + texFrame=mPlayer.getNextTexture(gl); + } + if(null != texFrame) { + tex = texFrame.getTexture(); + gl.glActiveTexture(GL.GL_TEXTURE0+mPlayer.getTextureUnit()); + tex.enable(gl); + tex.bind(gl); + } + } + gl.glDrawArrays(GL.GL_TRIANGLE_STRIP, 0, 4); + if(null != tex) { + tex.disable(gl); + } + iVBO.enableBuffer(gl, false); + st.useProgram(gl, false); + pmvMatrix.glPopMatrix(); + + textRendererGLEL.display(drawable); + } + + static class StereoGLMediaEventListener implements GLMediaEventListener { + void destroyWindow(final Window window) { + new Thread() { + public void run() { + window.destroy(); + } }.start(); + } + + @Override + public void newFrameAvailable(final GLMediaPlayer ts, final TextureFrame newFrame, final long when) { + } + + @Override + public void attributesChanged(final GLMediaPlayer mp, final int event_mask, final long when) { + System.err.println("MovieSimple AttributesChanges: events_mask 0x"+Integer.toHexString(event_mask)+", when "+when); + System.err.println("MovieSimple State: "+mp); + final GLWindow window = (GLWindow) mp.getAttachedObject(WINDOW_KEY); + final MovieSBSStereo ms = (MovieSBSStereo)mp.getAttachedObject(PLAYER); + final StereoClientRenderer stereoClientRenderer = (StereoClientRenderer) mp.getAttachedObject(STEREO_RENDERER_KEY); + + if( 0 != ( GLMediaEventListener.EVENT_CHANGE_SIZE & event_mask ) ) { + System.err.println("MovieSimple State: CHANGE_SIZE"); + // window.disposeGLEventListener(ms, false /* remove */ ); + ms.resetGLState(); + } + if( 0 != ( GLMediaEventListener.EVENT_CHANGE_INIT & event_mask ) ) { + System.err.println("MovieSimple State: INIT"); + // Use GLEventListener in all cases [A+V, V, A] + stereoClientRenderer.addGLEventListener(ms); + final GLAnimatorControl anim = window.getAnimator(); + anim.setUpdateFPSFrames(60, null); + anim.resetFPSCounter(); + ms.setStereoClientRenderer(stereoClientRenderer); + } + if( 0 != ( GLMediaEventListener.EVENT_CHANGE_PLAY & event_mask ) ) { + window.getAnimator().resetFPSCounter(); + } + + boolean destroy = false; + Throwable err = null; + + if( 0 != ( GLMediaEventListener.EVENT_CHANGE_EOS & event_mask ) ) { + err = ms.mPlayer.getStreamException(); + if( null != err ) { + System.err.println("MovieSimple State: EOS + Exception"); + destroy = true; + } else { + System.err.println("MovieSimple State: EOS"); + new Thread() { + public void run() { + mp.setPlaySpeed(1f); + mp.seek(0); + mp.play(); + } + }.start(); + } + } + if( 0 != ( GLMediaEventListener.EVENT_CHANGE_ERR & event_mask ) ) { + err = ms.mPlayer.getStreamException(); + if( null != err ) { + System.err.println("MovieSimple State: ERR + Exception"); + } else { + System.err.println("MovieSimple State: ERR"); + } + destroy = true; + } + if( destroy ) { + if( null != err ) { + err.printStackTrace(); + } + destroyWindow(window); + } + } + }; + public final static StereoGLMediaEventListener stereoGLMediaEventListener = new StereoGLMediaEventListener(); +} 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 8760cd2c3..5918e4e6b 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 @@ -90,8 +90,8 @@ public class MovieSimple implements GLEventListener { public static final int EFFECT_GRADIENT_BOTTOM2TOP = 1<<1; public static final int EFFECT_TRANSPARENT = 1<<3; - private static final String WINDOW_KEY = "window"; - private static final String PLAYER = "player"; + public static final String WINDOW_KEY = "window"; + public static final String PLAYER = "player"; private static boolean waitForKey = false; private int surfWidth, surfHeight; @@ -682,6 +682,9 @@ public class MovieSimple implements GLEventListener { System.out.println("pR "+mPlayer); } + private final float zNear = 1f; + private final float zFar = 10f; + private void reshapePMV(final int width, final int height) { pmvMatrix.glMatrixMode(GLMatrixFunc.GL_PROJECTION); pmvMatrix.glLoadIdentity(); @@ -691,7 +694,7 @@ public class MovieSimple implements GLEventListener { pmvMatrix.glOrthof(-fw, fw, -fh, fh, -1.0f, 1.0f); nearPlaneNormalized = 0f; } else { - pmvMatrix.gluPerspective(45.0f, (float)width / (float)height, 1f, 10.0f); + pmvMatrix.gluPerspective(45.0f, (float)width / (float)height, zNear, zFar); nearPlaneNormalized = 1f/(10f-1f); } System.err.println("XXX0: Perspective nearPlaneNormalized: "+nearPlaneNormalized); @@ -786,7 +789,7 @@ public class MovieSimple implements GLEventListener { Texture tex = null; if(null!=mPlayer) { final TextureSequence.TextureFrame texFrame; - if(mPlayerShared) { + if( mPlayerShared ) { texFrame=mPlayer.getLastTexture(); } else { texFrame=mPlayer.getNextTexture(gl); @@ -903,7 +906,7 @@ public class MovieSimple implements GLEventListener { } } }; - final static MyGLMediaEventListener myGLMediaEventListener = new MyGLMediaEventListener(); + public final static MyGLMediaEventListener myGLMediaEventListener = new MyGLMediaEventListener(); static boolean loopEOS = false; static boolean origSize; @@ -1068,4 +1071,5 @@ public class MovieSimple implements GLEventListener { mss[i].initStream(streamLocN, vid, aid, textureCount); } } + } diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/stereo/StereoDemo01.java b/src/test/com/jogamp/opengl/test/junit/jogl/stereo/StereoDemo01.java new file mode 100644 index 000000000..c4db20f37 --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/jogl/stereo/StereoDemo01.java @@ -0,0 +1,307 @@ +/** + * Copyright 2014 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.stereo; + +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; + +import javax.media.nativewindow.util.DimensionImmutable; +import javax.media.nativewindow.util.PointImmutable; +import javax.media.opengl.GL; +import javax.media.opengl.GLCapabilities; +import javax.media.opengl.GLProfile; + +import com.jogamp.common.util.IOUtil; +import com.jogamp.common.util.ReflectionUtil; +import com.jogamp.newt.event.KeyAdapter; +import com.jogamp.newt.event.KeyEvent; +import com.jogamp.newt.opengl.GLWindow; +import com.jogamp.opengl.math.FovHVHalves; +import com.jogamp.opengl.test.junit.jogl.demos.es2.GearsES2; +import com.jogamp.opengl.test.junit.jogl.demos.es2.av.MovieSBSStereo; +import com.jogamp.opengl.test.junit.jogl.demos.es2.av.MovieSimple; +import com.jogamp.opengl.test.junit.util.MiscUtils; +import com.jogamp.opengl.test.junit.util.QuitAdapter; +import com.jogamp.opengl.util.Animator; +import com.jogamp.opengl.util.AnimatorBase; +import com.jogamp.opengl.util.av.GLMediaPlayer; +import com.jogamp.opengl.util.stereo.StereoDevice; +import com.jogamp.opengl.util.stereo.StereoDeviceRenderer; +import com.jogamp.opengl.util.stereo.StereoDeviceFactory; +import com.jogamp.opengl.util.stereo.StereoClientRenderer; +import com.jogamp.opengl.util.stereo.StereoGLEventListener; + +/** + * All distortions, no multisampling, bilinear filtering, manual-swap and using two FBOs (default, good) + * <pre> + * java StereoDemo01 -time 10000000 + * </pre> + * All distortions, 8x multisampling, bilinear filtering, manual-swap and using two FBOs (best - slowest) + * <pre> + * java StereoDemo01 -time 10000000 -samples 8 + * </pre> + * All distortions, 8x multisampling, bilinear filtering, manual-swap and using one a big single FBO (w/ all commandline params) + * <pre> + * java StereoDemo01 -time 10000000 -vignette true -chromatic true -timewarp false -samples 8 -biLinear true -autoSwap false -singleFBO true -mainScreen false + * </pre> + * No distortions, no multisampling, no filtering, auto-swap and using a big single FBO (worst and fastest) + * <pre> + * java StereoDemo01 -time 10000000 -vignette false -chromatic false -timewarp false -samples 0 -biLinear false -autoSwap true -singleFBO true + * </pre> + * Test on main screen: + * <pre> + * java StereoDemo01 -time 10000000 -mainScreen true + * </pre> + * Test a 3D SBS Movie: + * <pre> + * java StereoDemo01 -time 10000000 -filmFile Some_SBS_3D_Movie.mkv + * java StereoDemo01 -time 10000000 -filmURI http://whoknows.not/Some_SBS_3D_Movie.mkv + * </pre> + * <p> + * Key 'R' enables/disables the VR's sensors, i.e. head rotation .. + * </p> + * + */ +public class StereoDemo01 { + static long duration = 10000; // ms + + static boolean useStereoScreen = true; + + static int numSamples = 0; + static boolean biLinear = true; + static boolean useSingleFBO = false; + static boolean useVignette = true; + static boolean useChromatic = true; + static boolean useTimewarp = true; + static boolean useAutoSwap = false; + static String useFilmFile = null; + static String useFilmURI = null; + static String stereoRendererListenerName = null; + + public static void main(final String args[]) throws InterruptedException, URISyntaxException { + for(int i=0; i<args.length; i++) { + if(args[i].equals("-time")) { + i++; + duration = MiscUtils.atol(args[i], duration); + } else if(args[i].equals("-samples")) { + i++; + numSamples = MiscUtils.atoi(args[i], numSamples); + } else if(args[i].equals("-biLinear")) { + i++; + biLinear = MiscUtils.atob(args[i], biLinear); + } else if(args[i].equals("-singleFBO")) { + i++; + useSingleFBO = MiscUtils.atob(args[i], useSingleFBO); + } else if(args[i].equals("-vignette")) { + i++; + useVignette = MiscUtils.atob(args[i], useVignette); + } else if(args[i].equals("-chromatic")) { + i++; + useChromatic = MiscUtils.atob(args[i], useChromatic); + } else if(args[i].equals("-timewarp")) { + i++; + useTimewarp = MiscUtils.atob(args[i], useTimewarp); + } else if(args[i].equals("-vignette")) { + i++; + useVignette = MiscUtils.atob(args[i], useVignette); + } else if(args[i].equals("-mainScreen")) { + i++; + useStereoScreen = !MiscUtils.atob(args[i], useStereoScreen); + } else if(args[i].equals("-autoSwap")) { + i++; + useAutoSwap = MiscUtils.atob(args[i], useAutoSwap); + } else if(args[i].equals("-test")) { + i++; + stereoRendererListenerName = args[i]; + } else if(args[i].equals("-filmFile")) { + i++; + useFilmFile = args[i]; + } else if(args[i].equals("-filmURI")) { + i++; + useFilmURI = args[i]; + } + } + if( null != stereoRendererListenerName ) { + try { + final Object stereoRendererListener = ReflectionUtil.createInstance(stereoRendererListenerName, null); + } catch (final Exception e) { + e.printStackTrace(); + } + } + final StereoGLEventListener upstream; + final MovieSBSStereo movieSimple; + final URI movieURI; + if( null != useFilmFile ) { + movieSimple = new MovieSBSStereo(); + movieURI = IOUtil.toURISimple(new File(useFilmFile)); + upstream = movieSimple; + } else if( null != useFilmURI ) { + movieSimple = new MovieSBSStereo(); + movieURI = new URI(useFilmURI); + upstream = movieSimple; + } else { + final GearsES2 demo = new GearsES2(0); + demo.setVerbose(false); + upstream = demo; + movieSimple = null; + movieURI = null; + } + final StereoDemo01 demo01 = new StereoDemo01(); + demo01.doIt(0, upstream, movieSimple, movieURI, biLinear, numSamples, useSingleFBO, useVignette, useChromatic, useTimewarp, + useAutoSwap, true /* useAnimator */, false /* exclusiveContext*/); + } + + public void doIt(final int stereoDeviceIndex, + final StereoGLEventListener upstream, final MovieSBSStereo movieSimple, final URI movieURI, + final boolean biLinear, final int numSamples, final boolean useSingleFBO, + final boolean useVignette, final boolean useChromatic, final boolean useTimewarp, + final boolean useAutoSwap, final boolean useAnimator, final boolean exclusiveContext) throws InterruptedException { + + System.err.println("glob duration "+duration); + System.err.println("glob useStereoScreen "+useStereoScreen); + System.err.println("biLinear "+biLinear); + System.err.println("numSamples "+numSamples); + System.err.println("useSingleFBO "+useSingleFBO); + System.err.println("useVignette "+useVignette); + System.err.println("useChromatic "+useChromatic); + System.err.println("useTimewarp "+useTimewarp); + System.err.println("useAutoSwap "+useAutoSwap); + + final StereoDeviceFactory stereoDeviceFactory = StereoDeviceFactory.createDefaultFactory(); + if( null == stereoDeviceFactory ) { + System.err.println("No StereoDeviceFactory available"); + return; + } + + final StereoDevice stereoDevice = stereoDeviceFactory.createDevice(stereoDeviceIndex, true /* verbose */); + if( null == stereoDevice ) { + System.err.println("No StereoDevice.Context available for index "+stereoDeviceIndex); + return; + } + + // Start the sensor which provides the Rift’s pose and motion. + if( !stereoDevice.startSensors(true) ) { + System.err.println("Could not start sensors on device "+stereoDeviceIndex); + } + + // + // + // + final GLCapabilities caps = new GLCapabilities(GLProfile.getMaxProgrammable(true /* favorHardwareRasterizer */)); + final GLWindow window = GLWindow.create(caps); + + final PointImmutable devicePos = stereoDevice.getPosition(); + final DimensionImmutable deviceRes = stereoDevice.getSurfaceSize(); + window.setSize(deviceRes.getWidth(), deviceRes.getHeight()); + if( useStereoScreen ) { + window.setPosition(devicePos.getX(), devicePos.getY()); + } + window.setAutoSwapBufferMode(useAutoSwap); + window.setUndecorated(true); + + final Animator animator = useAnimator ? new Animator() : null; + if( useAnimator ) { + animator.setModeBits(false, AnimatorBase.MODE_EXPECT_AWT_RENDERING_THREAD); + animator.setExclusiveContext(exclusiveContext); + } + + // + // Oculus Rift setup + // + // EyePos.y = ovrHmd_GetFloat(HMD, OVR_KEY_EYE_HEIGHT, EyePos.y); + final FovHVHalves[] defaultEyeFov = stereoDevice.getDefaultFOV(); + System.err.println("Default Fov[0]: "+defaultEyeFov[0]); + System.err.println("Default Fov[1]: "+defaultEyeFov[1]); + + final float[] eyePositionOffset = null == movieSimple ? StereoDevice.DEFAULT_EYE_POSITION_OFFSET // default + : new float[] { 0f, 0.3f, 0f }; // better fixed movie position + final int textureUnit = 0; + final int distortionBits = ( useVignette ? StereoDeviceRenderer.DISTORTION_VIGNETTE : 0 ) | + ( useChromatic ? StereoDeviceRenderer.DISTORTION_CHROMATIC : 0 ) | + ( useTimewarp ? StereoDeviceRenderer.DISTORTION_TIMEWARP : 0 ); + final float pixelsPerDisplayPixel = 1f; + final StereoDeviceRenderer stereoDeviceRenderer = + stereoDevice.createRenderer(distortionBits, useSingleFBO ? 1 : 2, eyePositionOffset, + defaultEyeFov, pixelsPerDisplayPixel, textureUnit); + System.err.println("StereoDeviceRenderer: "+stereoDeviceRenderer); + + final int texFilter = biLinear ? GL.GL_LINEAR : GL.GL_NEAREST; + final StereoClientRenderer renderer = new StereoClientRenderer(stereoDeviceRenderer, true /* ownsDist */, texFilter, texFilter, numSamples); + if( null != movieSimple && null != movieURI) { + movieSimple.setScaleOrig(true); + final GLMediaPlayer mp = movieSimple.getGLMediaPlayer(); + mp.attachObject(MovieSimple.WINDOW_KEY, window); + mp.attachObject(MovieSBSStereo.STEREO_RENDERER_KEY, renderer); + mp.addEventListener(MovieSBSStereo.stereoGLMediaEventListener); + movieSimple.initStream(movieURI, GLMediaPlayer.STREAM_ID_AUTO, GLMediaPlayer.STREAM_ID_AUTO, 3); + } else { + renderer.addGLEventListener(upstream); + } + window.addGLEventListener(renderer); + + final QuitAdapter quitAdapter = new QuitAdapter(); + window.addKeyListener(quitAdapter); + window.addWindowListener(quitAdapter); + + window.addKeyListener(new KeyAdapter() { + public void keyReleased(final KeyEvent e) { + if( e.isAutoRepeat() ) { + return; + } + switch(e.getKeySymbol()) { + case KeyEvent.VK_R: { + stereoDevice.startSensors(!stereoDevice.getSensorsStarted()); + break; + } + } + } } ); + + if( useAnimator ) { + animator.add(window); + animator.start(); + } + window.setVisible(true); + if( useAnimator ) { + animator.setUpdateFPSFrames(60, System.err); + } + + final long t0 = System.currentTimeMillis(); + long t1 = t0; + while(!quitAdapter.shouldQuit() && t1-t0<duration) { + Thread.sleep(100); + t1 = System.currentTimeMillis(); + } + + if( useAnimator ) { + animator.stop(); + } + window.destroy(); + stereoDevice.dispose(); + } +} diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/stereo/ovr/OVRDemo01.java b/src/test/com/jogamp/opengl/test/junit/jogl/stereo/ovr/OVRDemo01.java deleted file mode 100644 index 1cc07ec22..000000000 --- a/src/test/com/jogamp/opengl/test/junit/jogl/stereo/ovr/OVRDemo01.java +++ /dev/null @@ -1,236 +0,0 @@ -/** - * Copyright 2014 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.stereo.ovr; - -import javax.media.opengl.GL; -import javax.media.opengl.GLCapabilities; -import javax.media.opengl.GLEventListener; -import javax.media.opengl.GLProfile; - -import jogamp.opengl.oculusvr.OVRDistortion; - -import com.jogamp.newt.opengl.GLWindow; -import com.jogamp.oculusvr.OVR; -import com.jogamp.oculusvr.OVRException; -import com.jogamp.oculusvr.OVRVersion; -import com.jogamp.oculusvr.OvrHmdContext; -import com.jogamp.oculusvr.ovrFovPort; -import com.jogamp.oculusvr.ovrHmdDesc; -import com.jogamp.oculusvr.ovrSizei; -import com.jogamp.oculusvr.ovrVector2i; -import com.jogamp.opengl.oculusvr.OVRSBSRendererDualFBO; -import com.jogamp.opengl.oculusvr.OVRSBSRendererSingleFBO; -import com.jogamp.opengl.test.junit.jogl.demos.es2.GearsES2; -import com.jogamp.opengl.test.junit.util.MiscUtils; -import com.jogamp.opengl.test.junit.util.QuitAdapter; -import com.jogamp.opengl.util.Animator; -import com.jogamp.opengl.util.AnimatorBase; - -/** - * All distortions, no multisampling, bilinear filtering, manual-swap and using two FBOs (default, good) - * <pre> - * java OVRDemo01 -time 10000000 - * </pre> - * All distortions, 8x multisampling, bilinear filtering, manual-swap and using two FBOs (best - slowest) - * <pre> - * java OVRDemo01 -time 10000000 -samples 8 - * </pre> - * All distortions, 8x multisampling, bilinear filtering, manual-swap and using one a big single FBO (w/ all commandline params) - * <pre> - * java OVRDemo01 -time 10000000 -vignette true -chromatic true -timewarp false -samples 8 -biLinear true -autoSwap false -singleFBO true -mainScreen false - * </pre> - * No distortions, no multisampling, no filtering, auto-swap and using a big single FBO (worst and fastest) - * <pre> - * java OVRDemo01 -time 10000000 -vignette false -chromatic false -timewarp false -samples 0 -biLinear false -autoSwap true -singleFBO true - * </pre> - * Test on main screen: - * <pre> - * java OVRDemo01 -time 10000000 -mainScreen true - * </pre> - * - */ -public class OVRDemo01 { - static long duration = 10000; // ms - - static boolean useOVRScreen = true; - - static int numSamples = 0; - static boolean biLinear = true; - static boolean useSingleFBO = false; - static boolean useVignette = true; - static boolean useChromatic = true; - static boolean useTimewarp = true; - static boolean useAutoSwap = false; - - public static void main(final String args[]) throws InterruptedException { - for(int i=0; i<args.length; i++) { - if(args[i].equals("-time")) { - i++; - duration = MiscUtils.atol(args[i], duration); - } else if(args[i].equals("-samples")) { - i++; - numSamples = MiscUtils.atoi(args[i], numSamples); - } else if(args[i].equals("-biLinear")) { - i++; - biLinear = MiscUtils.atob(args[i], biLinear); - } else if(args[i].equals("-singleFBO")) { - i++; - useSingleFBO = MiscUtils.atob(args[i], useSingleFBO); - } else if(args[i].equals("-vignette")) { - i++; - useVignette = MiscUtils.atob(args[i], useVignette); - } else if(args[i].equals("-chromatic")) { - i++; - useChromatic = MiscUtils.atob(args[i], useChromatic); - } else if(args[i].equals("-timewarp")) { - i++; - useTimewarp = MiscUtils.atob(args[i], useTimewarp); - } else if(args[i].equals("-vignette")) { - i++; - useVignette = MiscUtils.atob(args[i], useVignette); - } else if(args[i].equals("-mainScreen")) { - i++; - useOVRScreen = !MiscUtils.atob(args[i], useOVRScreen); - } else if(args[i].equals("-autoSwap")) { - i++; - useAutoSwap = MiscUtils.atob(args[i], useAutoSwap); - } - } - final OVRDemo01 demo01 = new OVRDemo01(); - demo01.doIt(0, biLinear, numSamples, useSingleFBO, useVignette, useChromatic, useTimewarp, - useAutoSwap, true /* useAnimator */, false /* exclusiveContext*/); - } - - public void doIt(final int ovrHmdIndex, final boolean biLinear, final int numSamples, - final boolean useSingleFBO, - final boolean useVignette, final boolean useChromatic, final boolean useTimewarp, - final boolean useAutoSwap, - final boolean useAnimator, final boolean exclusiveContext) throws InterruptedException { - - System.err.println("glob duration "+duration); - System.err.println("glob useOVRScreen "+useOVRScreen); - System.err.println("biLinear "+biLinear); - System.err.println("numSamples "+numSamples); - System.err.println("useSingleFBO "+useSingleFBO); - System.err.println("useVignette "+useVignette); - System.err.println("useChromatic "+useChromatic); - System.err.println("useTimewarp "+useTimewarp); - System.err.println("useAutoSwap "+useAutoSwap); - - // Initialize LibOVR... - if( !OVR.ovr_Initialize() ) { // recursive .. - throw new OVRException("OVR not available"); - } - final OvrHmdContext hmdCtx = OVR.ovrHmd_Create(ovrHmdIndex); - if( null == hmdCtx ) { - throw new OVRException("OVR HMD #"+ovrHmdIndex+" not available"); - } - final ovrHmdDesc hmdDesc = ovrHmdDesc.create(); - OVR.ovrHmd_GetDesc(hmdCtx, hmdDesc); - System.err.println(OVRVersion.getAvailableCapabilitiesInfo(hmdDesc, ovrHmdIndex, null).toString()); - - // Start the sensor which provides the Rift’s pose and motion. - final int requiredSensorCaps = 0; - final int supportedSensorCaps = requiredSensorCaps | OVR.ovrSensorCap_Orientation | OVR.ovrSensorCap_YawCorrection | OVR.ovrSensorCap_Position; - if( !OVR.ovrHmd_StartSensor(hmdCtx, supportedSensorCaps, requiredSensorCaps) ) { - throw new OVRException("OVR HMD #"+ovrHmdIndex+" required sensors not available"); - } - - // - // - // - - final GLCapabilities caps = new GLCapabilities(GLProfile.getMaxProgrammable(true /* favorHardwareRasterizer */)); - final GLWindow window = GLWindow.create(caps); - final ovrVector2i ovrPos = hmdDesc.getWindowsPos(); - final ovrSizei ovrRes = hmdDesc.getResolution(); - window.setSize(ovrRes.getW(), ovrRes.getH()); - if( useOVRScreen ) { - window.setPosition(ovrPos.getX(), ovrPos.getY()); - } - window.setAutoSwapBufferMode(useAutoSwap); - window.setUndecorated(true); - - final Animator animator = useAnimator ? new Animator() : null; - if( useAnimator ) { - animator.setModeBits(false, AnimatorBase.MODE_EXPECT_AWT_RENDERING_THREAD); - animator.setExclusiveContext(exclusiveContext); - } - - // - // Oculus Rift setup - // - final float[] eyePositionOffset = { 0.0f, 1.6f, -5.0f }; - // EyePos.y = ovrHmd_GetFloat(HMD, OVR_KEY_EYE_HEIGHT, EyePos.y); - - final ovrFovPort[] defaultEyeFov = hmdDesc.getDefaultEyeFov(0, new ovrFovPort[2]); - final int distortionCaps = ( useVignette ? OVR.ovrDistortionCap_Vignette : 0 ) | - ( useChromatic ? OVR.ovrDistortionCap_Chromatic : 0 ) | - ( useTimewarp ? OVR.ovrDistortionCap_TimeWarp : 0 ); - final float pixelsPerDisplayPixel = 1f; - final OVRDistortion dist = OVRDistortion.create(hmdCtx, useSingleFBO, eyePositionOffset, defaultEyeFov, pixelsPerDisplayPixel, distortionCaps); - System.err.println("OVRDistortion: "+dist); - - final int texFilter = biLinear ? GL.GL_LINEAR : GL.GL_NEAREST; - final GearsES2 upstream = new GearsES2(0); - upstream.setVerbose(false); - final GLEventListener renderer; - if( useSingleFBO ) { - renderer = new OVRSBSRendererSingleFBO(dist, true /* ownsDist */, upstream, texFilter, texFilter, numSamples); - } else { - renderer = new OVRSBSRendererDualFBO(dist, true /* ownsDist */, upstream, texFilter, texFilter, numSamples); - } - window.addGLEventListener(renderer); - - final QuitAdapter quitAdapter = new QuitAdapter(); - window.addKeyListener(quitAdapter); - window.addWindowListener(quitAdapter); - - if( useAnimator ) { - animator.add(window); - animator.start(); - } - window.setVisible(true); - if( useAnimator ) { - animator.setUpdateFPSFrames(60, System.err); - } - - final long t0 = System.currentTimeMillis(); - long t1 = t0; - while(!quitAdapter.shouldQuit() && t1-t0<duration) { - Thread.sleep(100); - t1 = System.currentTimeMillis(); - } - - if( useAnimator ) { - animator.stop(); - } - window.destroy(); - // OVR.ovr_Shutdown(); - } -} |