diff options
author | Sven Gothel <[email protected]> | 2014-07-01 21:02:21 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2014-07-01 21:02:21 +0200 |
commit | 36327e24cf586b50bf18e87d7d13d53eb41cf1d9 (patch) | |
tree | b34c88274eebce4839ee18b5958a2019de565b87 /src | |
parent | a0498e240b9dfde345a704ec6de1d6abcee7b318 (diff) |
Bug 1021: Add OculusVR distortion renderer (single FBO and dual FBO); Add GLEventListener2 (WIP); Refine FloatUtil
- GLEventListener2 extends GLEventListener adds refined control:
- display w/ flags, i.e. repeat, don't clear
- setProjectionModelview(..)
- FloatUtil.* Add return value for chaining, where missing
+++
- jogamp.opengl.oculusvr.OVRDistortion
- Handles all OVR related data and maps it to shader + GL buffers
- display method
- com.jogamp.opengl.oculusvr.OVRSBSRendererSingleFBO implements GLEventListener
- Simple OVRDistortion renderer using single FBO
- Using upstream GLEventListener2 (the content)
- com.jogamp.opengl.oculusvr.OVRSBSRendererDualFBO implements GLEventListener
- Simple OVRDistortion renderer using two FBOs
- Using upstream GLEventListener2 (the content)
Manual Test: com.jogamp.opengl.test.junit.jogl.stereo.ovr.OVRDemo01
Diffstat (limited to 'src')
19 files changed, 1900 insertions, 123 deletions
diff --git a/src/jogl/classes/com/jogamp/opengl/math/FloatUtil.java b/src/jogl/classes/com/jogamp/opengl/math/FloatUtil.java index 7e9d7cdd8..f5200443c 100644 --- a/src/jogl/classes/com/jogamp/opengl/math/FloatUtil.java +++ b/src/jogl/classes/com/jogamp/opengl/math/FloatUtil.java @@ -705,8 +705,8 @@ public final class FloatUtil { * * @param msrc 4x4 matrix in column-major order, the source * @param msrc_offset offset in given array <i>msrc</i>, i.e. start of the 4x4 matrix - * @param mres 4x4 matrix in column-major order, the result - may be <code>msrc</code> (in-place) - * @param mres_offset offset in given array <i>mres</i>, i.e. start of the 4x4 matrix - may be <code>msrc_offset</code> (in-place) + * @param mres 4x4 matrix in column-major order, the result + * @param mres_offset offset in given array <i>mres</i>, i.e. start of the 4x4 matrix * @return given result matrix <i>mres</i> for chaining */ public static float[] transposeMatrix(final float[] msrc, final int msrc_offset, final float[] mres, final int mres_offset) { @@ -737,16 +737,37 @@ public final class FloatUtil { } /** - * Transpose the given matrix in place. + * Transpose the given matrix. * - * @param m 4x4 matrix in column-major order, the source - * @param m_offset offset in given array <i>m</i>, i.e. start of the 4x4 matrix - * @param temp temporary 4*4 float storage - * @return given result matrix <i>m</i> for chaining + * @param msrc 4x4 matrix in column-major order, the source + * @param mres 4x4 matrix in column-major order, the result + * @return given result matrix <i>mres</i> for chaining */ - public static float[] transposeMatrix(final float[] m, final int m_offset, final float[/*4*4*/] temp) { - System.arraycopy(m, m_offset, temp, 0, 16); - return transposeMatrix(temp, 0, m, m_offset); + public static float[] transposeMatrix(final float[] msrc, final float[] mres) { + mres[0] = msrc[0*4]; + mres[1] = msrc[1*4]; + mres[2] = msrc[2*4]; + mres[3] = msrc[3*4]; + + final int i4_1 = 1*4; + mres[0+i4_1] = msrc[1+0*4]; + mres[1+i4_1] = msrc[1+1*4]; + mres[2+i4_1] = msrc[1+2*4]; + mres[3+i4_1] = msrc[1+3*4]; + + final int i4_2 = 2*4; + mres[0+i4_2] = msrc[2+0*4]; + mres[1+i4_2] = msrc[2+1*4]; + mres[2+i4_2] = msrc[2+2*4]; + mres[3+i4_2] = msrc[2+3*4]; + + final int i4_3 = 3*4; + mres[0+i4_3] = msrc[3+0*4]; + mres[1+i4_3] = msrc[3+1*4]; + mres[2+i4_3] = msrc[3+2*4]; + mres[3+i4_3] = msrc[3+3*4]; + + return mres; } /** @@ -1422,8 +1443,9 @@ public final class FloatUtil { * @param a 4x4 matrix in column-major order * @param b 4x4 matrix in column-major order * @param d result a*b in column-major order + * @return given result matrix <i>d</i> for chaining */ - public static void multMatrix(final float[] a, final int a_off, final float[] b, final int b_off, float[] d, final int d_off) { + public static float[] multMatrix(final float[] a, final int a_off, final float[] b, final int b_off, float[] d, final int d_off) { final float b00 = b[b_off+0+0*4]; final float b10 = b[b_off+1+0*4]; final float b20 = b[b_off+2+0*4]; @@ -1476,6 +1498,8 @@ public final class FloatUtil { d[d_off+3+1*4] = ai0 * b01 + ai1 * b11 + ai2 * b21 + ai3 * b31 ; d[d_off+3+2*4] = ai0 * b02 + ai1 * b12 + ai2 * b22 + ai3 * b32 ; d[d_off+3+3*4] = ai0 * b03 + ai1 * b13 + ai2 * b23 + ai3 * b33 ; + + return d; } /** @@ -1483,8 +1507,9 @@ public final class FloatUtil { * @param a 4x4 matrix in column-major order * @param b 4x4 matrix in column-major order * @param d result a*b in column-major order + * @return given result matrix <i>d</i> for chaining */ - public static void multMatrix(final float[] a, final float[] b, float[] d) { + public static float[] multMatrix(final float[] a, final float[] b, float[] d) { final float b00 = b[0+0*4]; final float b10 = b[1+0*4]; final float b20 = b[2+0*4]; @@ -1537,14 +1562,17 @@ public final class FloatUtil { d[3+1*4] = ai0 * b01 + ai1 * b11 + ai2 * b21 + ai3 * b31 ; d[3+2*4] = ai0 * b02 + ai1 * b12 + ai2 * b22 + ai3 * b32 ; d[3+3*4] = ai0 * b03 + ai1 * b13 + ai2 * b23 + ai3 * b33 ; + + return d; } /** * Multiply matrix: [a] = [a] x [b] * @param a 4x4 matrix in column-major order (also result) * @param b 4x4 matrix in column-major order + * @return given result matrix <i>a</i> for chaining */ - public static void multMatrix(final float[] a, final int a_off, final float[] b, final int b_off) { + public static float[] multMatrix(final float[] a, final int a_off, final float[] b, final int b_off) { final float b00 = b[b_off+0+0*4]; final float b10 = b[b_off+1+0*4]; final float b20 = b[b_off+2+0*4]; @@ -1597,14 +1625,17 @@ public final class FloatUtil { a[a_off+3+1*4] = ai0 * b01 + ai1 * b11 + ai2 * b21 + ai3 * b31 ; a[a_off+3+2*4] = ai0 * b02 + ai1 * b12 + ai2 * b22 + ai3 * b32 ; a[a_off+3+3*4] = ai0 * b03 + ai1 * b13 + ai2 * b23 + ai3 * b33 ; + + return a; } /** * Multiply matrix: [a] = [a] x [b] * @param a 4x4 matrix in column-major order (also result) * @param b 4x4 matrix in column-major order + * @return given result matrix <i>a</i> for chaining */ - public static void multMatrix(final float[] a, final float[] b) { + public static float[] multMatrix(final float[] a, final float[] b) { final float b00 = b[0+0*4]; final float b10 = b[1+0*4]; final float b20 = b[2+0*4]; @@ -1657,6 +1688,8 @@ public final class FloatUtil { a[3+1*4] = ai0 * b01 + ai1 * b11 + ai2 * b21 + ai3 * b31 ; a[3+2*4] = ai0 * b02 + ai1 * b12 + ai2 * b22 + ai3 * b32 ; a[3+3*4] = ai0 * b03 + ai1 * b13 + ai2 * b23 + ai3 * b33 ; + + return a; } /** @@ -1705,10 +1738,11 @@ public final class FloatUtil { * @param m_in_off * @param v_in 4-component column-vector * @param v_out m_in * v_in + * @return given result vector <i>v_out</i> for chaining */ - public static void multMatrixVec(final float[] m_in, final int m_in_off, - final float[] v_in, final int v_in_off, - final float[] v_out, final int v_out_off) { + public static float[] multMatrixVec(final float[] m_in, final int m_in_off, + final float[] v_in, final int v_in_off, + final float[] v_out, final int v_out_off) { // (one matrix row in column-major order) X (column vector) v_out[0 + v_out_off] = v_in[0+v_in_off] * m_in[0*4+m_in_off ] + v_in[1+v_in_off] * m_in[1*4+m_in_off ] + v_in[2+v_in_off] * m_in[2*4+m_in_off ] + v_in[3+v_in_off] * m_in[3*4+m_in_off ]; @@ -1724,6 +1758,8 @@ public final class FloatUtil { final int m_in_off_3 = 3+m_in_off; v_out[3 + v_out_off] = v_in[0+v_in_off] * m_in[0*4+m_in_off_3] + v_in[1+v_in_off] * m_in[1*4+m_in_off_3] + v_in[2+v_in_off] * m_in[2*4+m_in_off_3] + v_in[3+v_in_off] * m_in[3*4+m_in_off_3]; + + return v_out; } /** @@ -1731,8 +1767,9 @@ public final class FloatUtil { * @param m_in_off * @param v_in 4-component column-vector * @param v_out m_in * v_in + * @return given result vector <i>v_out</i> for chaining */ - public static void multMatrixVec(final float[] m_in, final float[] v_in, final float[] v_out) { + public static float[] multMatrixVec(final float[] m_in, final float[] v_in, final float[] v_out) { // (one matrix row in column-major order) X (column vector) v_out[0] = v_in[0] * m_in[0*4 ] + v_in[1] * m_in[1*4 ] + v_in[2] * m_in[2*4 ] + v_in[3] * m_in[3*4 ]; @@ -1745,6 +1782,8 @@ public final class FloatUtil { v_out[3] = v_in[0] * m_in[0*4+3] + v_in[1] * m_in[1*4+3] + v_in[2] * m_in[2*4+3] + v_in[3] * m_in[3*4+3]; + + return v_out; } /** @@ -1776,14 +1815,16 @@ public final class FloatUtil { * @param column named column to copy * @param v_out the column-vector storage, at least 3 components long * @param v_out_off offset to storage + * @return given result vector <i>v_out</i> for chaining */ - public static void copyMatrixColumn(final float[] m_in, final int m_in_off, final int column, final float[] v_out, final int v_out_off) { + public static float[] copyMatrixColumn(final float[] m_in, final int m_in_off, final int column, final float[] v_out, final int v_out_off) { v_out[0+v_out_off]=m_in[0+column*4+m_in_off]; v_out[1+v_out_off]=m_in[1+column*4+m_in_off]; v_out[2+v_out_off]=m_in[2+column*4+m_in_off]; if( v_out.length > 3+v_out_off ) { v_out[3+v_out_off]=m_in[3+column*4+m_in_off]; } + return v_out; } /** @@ -1796,14 +1837,16 @@ public final class FloatUtil { * @param row named row to copy * @param v_out the row-vector storage, at least 3 components long * @param v_out_off offset to storage + * @return given result vector <i>v_out</i> for chaining */ - public static void copyMatrixRow(final float[] m_in, final int m_in_off, final int row, final float[] v_out, final int v_out_off) { + public static float[] copyMatrixRow(final float[] m_in, final int m_in_off, final int row, final float[] v_out, final int v_out_off) { v_out[0+v_out_off]=m_in[row+0*4+m_in_off]; v_out[1+v_out_off]=m_in[row+1*4+m_in_off]; v_out[2+v_out_off]=m_in[row+2*4+m_in_off]; if( v_out.length > 3+v_out_off ) { v_out[3+v_out_off]=m_in[row+3*4+m_in_off]; } + return v_out; } /** diff --git a/src/jogl/classes/com/jogamp/opengl/math/Matrix4.java b/src/jogl/classes/com/jogamp/opengl/math/Matrix4.java index 069e6a0a9..830f1a882 100644 --- a/src/jogl/classes/com/jogamp/opengl/math/Matrix4.java +++ b/src/jogl/classes/com/jogamp/opengl/math/Matrix4.java @@ -123,7 +123,8 @@ public class Matrix4 { } public final void transpose() { - FloatUtil.transposeMatrix(matrix, 0, mat4Tmp1); + System.arraycopy(matrix, 0, mat4Tmp1, 0, 16); + FloatUtil.transposeMatrix(mat4Tmp1, matrix); } public final float determinant() { diff --git a/src/jogl/classes/com/jogamp/opengl/util/TileRendererBase.java b/src/jogl/classes/com/jogamp/opengl/util/TileRendererBase.java index dabde83dd..a1735766e 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/TileRendererBase.java +++ b/src/jogl/classes/com/jogamp/opengl/util/TileRendererBase.java @@ -45,7 +45,6 @@ import javax.media.opengl.GLCapabilitiesImmutable; import javax.media.opengl.GLDrawable; import javax.media.opengl.GLEventListener; import javax.media.opengl.GLException; -import javax.media.opengl.GLFBODrawable; import jogamp.opengl.Debug; /** @@ -228,14 +227,14 @@ public abstract class TileRendererBase { protected GLEventListener glEventListenerPre = null; protected GLEventListener glEventListenerPost = null; - private final String hashStr(Object o) { + private final String hashStr(final Object o) { final int h = null != o ? o.hashCode() : 0; return "0x"+Integer.toHexString(h); } - protected StringBuilder tileDetails(StringBuilder sb) { + protected StringBuilder tileDetails(final StringBuilder sb) { return sb.append("cur "+currentTileXPos+"/"+currentTileYPos+" "+currentTileWidth+"x"+currentTileHeight+", buffer "+hashStr(tileBuffer)); } - public StringBuilder toString(StringBuilder sb) { + public StringBuilder toString(final StringBuilder sb) { final int gladListenerCount = null != listeners ? listeners.length : 0; sb.append("tile["); tileDetails(sb); @@ -246,7 +245,7 @@ public abstract class TileRendererBase { } @Override public String toString() { - StringBuilder sb = new StringBuilder(); + final StringBuilder sb = new StringBuilder(); return getClass().getSimpleName()+ "["+toString(sb).toString()+"]"; } @@ -270,7 +269,7 @@ public abstract class TileRendererBase { * * @param buffer The buffer itself. Must be large enough to contain a random tile */ - public final void setTileBuffer(GLPixelBuffer buffer) { + public final void setTileBuffer(final GLPixelBuffer buffer) { tileBuffer = buffer; if( DEBUG ) { System.err.println("TileRenderer: tile-buffer "+tileBuffer); @@ -286,7 +285,7 @@ public abstract class TileRendererBase { * @param width The width of the final image * @param height The height of the final image */ - public void setImageSize(int width, int height) { + public void setImageSize(final int width, final int height) { imageSize.set(width, height); } @@ -298,7 +297,7 @@ public abstract class TileRendererBase { * * @param buffer the buffer itself, must be large enough to hold the final image */ - public final void setImageBuffer(GLPixelBuffer buffer) { + public final void setImageBuffer(final GLPixelBuffer buffer) { imageBuffer = buffer; if( DEBUG ) { System.err.println("TileRenderer: image-buffer "+imageBuffer); @@ -308,7 +307,7 @@ public abstract class TileRendererBase { /** @see #setImageBuffer(GLPixelBuffer) */ public final GLPixelBuffer getImageBuffer() { return imageBuffer; } - /* pp */ final void validateGL(GL gl) throws GLException { + /* pp */ final void validateGL(final GL gl) throws GLException { if( imageBuffer != null && !gl.isGL2ES3()) { throw new GLException("Using image-buffer w/ inssufficient GL context: "+gl.getContext().getGLVersion()+", "+gl.getGLProfile()); } @@ -415,7 +414,7 @@ public abstract class TileRendererBase { * See {@link GLDrawableUtil#swapBuffersBeforeRead(GLCapabilitiesImmutable)}. * </p> */ - public final boolean reqPreSwapBuffers(GLCapabilitiesImmutable chosenCaps) { + public final boolean reqPreSwapBuffers(final GLCapabilitiesImmutable chosenCaps) { return GLDrawableUtil.swapBuffersBeforeRead(chosenCaps); } @@ -466,7 +465,7 @@ public abstract class TileRendererBase { * @see #getAttachedDrawable() * @see #detachAutoDrawable() */ - public final void attachAutoDrawable(GLAutoDrawable glad) throws IllegalStateException { + public final void attachAutoDrawable(final GLAutoDrawable glad) throws IllegalStateException { if( null != this.glad ) { throw new IllegalStateException("GLAutoDrawable already attached"); } @@ -551,7 +550,7 @@ public abstract class TileRendererBase { * @param preTile the pre operations * @param postTile the post operations */ - public final void setGLEventListener(GLEventListener preTile, GLEventListener postTile) { + public final void setGLEventListener(final GLEventListener preTile, final GLEventListener postTile) { glEventListenerPre = preTile; glEventListenerPost = postTile; } @@ -573,7 +572,7 @@ public abstract class TileRendererBase { final TileRenderer tileRenderer = TileRendererBase.this instanceof TileRenderer ? (TileRenderer) TileRendererBase.this : null; @Override - public void init(GLAutoDrawable drawable) { + public void init(final GLAutoDrawable drawable) { if( null != glEventListenerPre ) { glEventListenerPre.init(drawable); } @@ -590,7 +589,7 @@ public abstract class TileRendererBase { } } @Override - public void dispose(GLAutoDrawable drawable) { + public void dispose(final GLAutoDrawable drawable) { if( null != glEventListenerPre ) { glEventListenerPre.dispose(drawable); } @@ -603,7 +602,7 @@ public abstract class TileRendererBase { } } @Override - public void display(GLAutoDrawable drawable) { + public void display(final GLAutoDrawable drawable) { if( null != glEventListenerPre ) { glEventListenerPre.reshape(drawable, 0, 0, currentTileWidth, currentTileHeight); glEventListenerPre.display(drawable); @@ -660,6 +659,6 @@ public abstract class TileRendererBase { } } @Override - public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) { } + public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) { } }; }
\ No newline at end of file diff --git a/src/jogl/classes/javax/media/opengl/GLEventListener2.java b/src/jogl/classes/javax/media/opengl/GLEventListener2.java new file mode 100644 index 000000000..d4e8e84a4 --- /dev/null +++ b/src/jogl/classes/javax/media/opengl/GLEventListener2.java @@ -0,0 +1,74 @@ +/** + * 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 javax.media.opengl; + +/** + * Extended {@link GLEventListener} interface + * supporting more fine grained control over the implementation. + */ +public interface GLEventListener2 extends GLEventListener { + /** + * {@link #display(GLAutoDrawable, int) display flag}: Repeat last produced image. + * <p> + * While a repeated frame shall produce the same artifacts as the last <code>display</code> call, + * e.g. not change animated objects, it shall reflect the {@link #setProjectionModelview(GLAutoDrawable, float[], float[]) current matrix}. + * </p> + */ + public static final int DISPLAY_REPEAT = 1 << 0; + + /** + * {@link #display(GLAutoDrawable, int) display flag}: Do not clear any target buffer, e.g. color-, depth- or stencil-buffers. + */ + public static final int DISPLAY_DONTCLEAR = 1 << 1; + + /** + * Extended {@link #display(GLAutoDrawable) display} method, + * allowing to pass a display flag, e.g. {@link #DISPLAY_REPEAT} or {@link #DISPLAY_DONTCLEAR}. + * <p> + * Method is usually called by a custom rendering loop, + * e.g. for manual stereo rendering or the like. + * </p> + * @param drawable + * @param flags + */ + public void display(final GLAutoDrawable drawable, final int flags); + + /** + * Might be called instead of {@link #reshape(GLAutoDrawable, int, int, int, int) reshape} + * to specify a custom projection and modelview matrix determined by the caller. + * <p> + * Method is usually called by a custom rendering loop, + * e.g. for manual stereo rendering or the like. + * </p> + * + * @param drawable the triggering {@link GLAutoDrawable} + * @param mat4Projection float[16] projection matrix + * @param mat4Modelview float[16] modelview matrix + */ + public void setProjectionModelview(final GLAutoDrawable drawable, final float[] mat4Projection, final float[] mat4Modelview); +} diff --git a/src/oculusvr/classes/com/jogamp/oculusvr/OVRDynamicLibraryBundleInfo.java b/src/oculusvr/classes/com/jogamp/oculusvr/OVRDynamicLibraryBundleInfo.java index 6bd9d1a64..8eafb9927 100644 --- a/src/oculusvr/classes/com/jogamp/oculusvr/OVRDynamicLibraryBundleInfo.java +++ b/src/oculusvr/classes/com/jogamp/oculusvr/OVRDynamicLibraryBundleInfo.java @@ -1,5 +1,5 @@ /** - * Copyright 2010 JogAmp Community. All rights reserved. + * 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: @@ -95,12 +95,12 @@ import java.util.*; } @Override - public final long toolGetProcAddress(long toolGetProcAddressHandle, String funcName) { + public final long toolGetProcAddress(final long toolGetProcAddressHandle, final String funcName) { return 0; } @Override - public final boolean useToolGetProcAdressFirst(String funcName) { + public final boolean useToolGetProcAdressFirst(final String funcName) { return false; } diff --git a/src/oculusvr/classes/com/jogamp/oculusvr/OVRException.java b/src/oculusvr/classes/com/jogamp/oculusvr/OVRException.java index e65338025..f2a254e14 100644 --- a/src/oculusvr/classes/com/jogamp/oculusvr/OVRException.java +++ b/src/oculusvr/classes/com/jogamp/oculusvr/OVRException.java @@ -1,22 +1,29 @@ /** - * Copyright (C) 2014 JogAmp Community. All rights reserved. + * Copyright 2014 JogAmp Community. All rights reserved. * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN - * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * 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.oculusvr; @@ -32,19 +39,19 @@ public class OVRException extends RuntimeException { /** Constructs an ALException object with the specified detail message. */ - public OVRException(String message) { + public OVRException(final String message) { super(message); } /** Constructs an ALException object with the specified detail message and root cause. */ - public OVRException(String message, Throwable cause) { + public OVRException(final String message, final Throwable cause) { super(message, cause); } /** Constructs an ALException object with the specified root cause. */ - public OVRException(Throwable cause) { + public OVRException(final Throwable cause) { super(cause); } } diff --git a/src/oculusvr/classes/com/jogamp/oculusvr/OVRVersion.java b/src/oculusvr/classes/com/jogamp/oculusvr/OVRVersion.java index 25db640e4..eebd771d8 100644 --- a/src/oculusvr/classes/com/jogamp/oculusvr/OVRVersion.java +++ b/src/oculusvr/classes/com/jogamp/oculusvr/OVRVersion.java @@ -29,10 +29,15 @@ package com.jogamp.oculusvr; import com.jogamp.common.GlueGenVersion; - import com.jogamp.common.os.Platform; import com.jogamp.common.util.VersionUtil; import com.jogamp.common.util.JogampVersion; +import com.jogamp.oculusvr.OVR; +import com.jogamp.oculusvr.OvrHmdContext; +import com.jogamp.oculusvr.ovrHmdDesc; +import com.jogamp.oculusvr.ovrSensorDesc; +import com.jogamp.oculusvr.ovrSizei; +import com.jogamp.oculusvr.ovrVector2i; import java.util.jar.Manifest; diff --git a/src/oculusvr/classes/com/jogamp/opengl/oculusvr/OVRSBSRendererDualFBO.java b/src/oculusvr/classes/com/jogamp/opengl/oculusvr/OVRSBSRendererDualFBO.java new file mode 100644 index 000000000..b10a58842 --- /dev/null +++ b/src/oculusvr/classes/com/jogamp/opengl/oculusvr/OVRSBSRendererDualFBO.java @@ -0,0 +1,232 @@ +/** + * 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 javax.media.opengl.GLEventListener2; + +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.TextureAttachment; +import com.jogamp.opengl.FBObject.Attachment.Type; +import com.jogamp.opengl.math.FloatUtil; + +/** + * OculusVR (OVR) <i>Side By Side</i> Distortion Renderer utilizing {@link OVRDistortion} + * implementing {@link GLEventListener} for convenience. + * <p> + * Implementation renders an {@link GLEventListener2} instance + * side-by-side using two {@link FBObject}s according to {@link OVRDistortion}. + * </p> + */ +public class OVRSBSRendererDualFBO implements GLEventListener { + + private final float[] mat4Projection = new float[16]; + private final float[] mat4Modelview = new float[16]; + private final OVRDistortion dist; + private final boolean ownsDist; + private final GLEventListener2 upstream; + private final FBObject[] fbos; + + // final float[] eyePos = { 0.0f, 1.6f, -5.0f }; + private final float[] eyePos = { 0.0f, 0.0f, -20.0f }; + // EyePos.y = ovrHmd_GetFloat(HMD, OVR_KEY_EYE_HEIGHT, EyePos.y); + private float eyeYaw = FloatUtil.PI; // 180 degrees in radians + private float frustumNear = 0.1f; + private float frustumFar = 7000f; + private int numSamples; + private final TextureAttachment[] fboTexs; + + public OVRSBSRendererDualFBO(final OVRDistortion dist, final boolean ownsDist, final GLEventListener2 upstream, final int numSamples) { + this.dist = dist; + this.ownsDist = ownsDist; + this.upstream = upstream; + this.numSamples = numSamples; + fbos = new FBObject[2]; + fbos[0] = new FBObject(); + fbos[1] = new FBObject(); + fboTexs = new TextureAttachment[2]; + } + + /** + * + * @param eyePos + * @param eyeYaw + * @param frustumNear + * @param frustumFar + */ + public void setUpstreamPMVParams(final float[] eyePos, final float eyeYaw, final float frustumNear, final float frustumFar) { + System.arraycopy(eyePos, 0, this.eyePos, 0, 3); + this.eyeYaw = eyeYaw; + this.frustumNear = frustumNear; + this.frustumFar = frustumFar; + } + + 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); + fbos[0].resetSamplingSink(gl); + fbos[1].attachColorbuffer(gl, 0, true); + fbos[1].resetSamplingSink(gl); + fboTexs[0] = fbos[0].getSamplingSink(); + fboTexs[1] = fbos[1].getSamplingSink(); + } else { + fboTexs[0] = fbos[0].attachTexture2D(gl, 0, true); + fboTexs[1] = fbos[1].attachTexture2D(gl, 0, true); + } + numSamples=fbos[0].getNumSamples(); + fbos[0].attachRenderbuffer(gl, Type.DEPTH, 24); + fbos[0].unbind(gl); + fbos[1].attachRenderbuffer(gl, Type.DEPTH, 24); + 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.getSBSUpstreamPMV(eyeNum, eyePos, eyeYaw, frustumNear, frustumFar, mat4Projection, mat4Modelview); + upstream.setProjectionModelview(drawable, mat4Projection, mat4Modelview); + upstream.display(drawable, eyeNum > 0 ? GLEventListener2.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 new file mode 100644 index 000000000..22b8b37ce --- /dev/null +++ b/src/oculusvr/classes/com/jogamp/opengl/oculusvr/OVRSBSRendererSingleFBO.java @@ -0,0 +1,206 @@ +/** + * 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 javax.media.opengl.GLEventListener2; + +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.TextureAttachment; +import com.jogamp.opengl.FBObject.Attachment.Type; +import com.jogamp.opengl.math.FloatUtil; + +/** + * OculusVR (OVR) <i>Side By Side</i> Distortion Renderer utilizing {@link OVRDistortion} + * implementing {@link GLEventListener} for convenience. + * <p> + * Implementation renders an {@link GLEventListener2} instance + * side-by-side within one {@link FBObject} according to {@link OVRDistortion}. + * </p> + */ +public class OVRSBSRendererSingleFBO implements GLEventListener { + + private final float[] mat4Projection = new float[16]; + private final float[] mat4Modelview = new float[16]; + private final OVRDistortion dist; + private final boolean ownsDist; + private final GLEventListener2 upstream; + private final FBObject fbo0; + + // final float[] eyePos = { 0.0f, 1.6f, -5.0f }; + private final float[] eyePos = { 0.0f, 0.0f, -20.0f }; + // EyePos.y = ovrHmd_GetFloat(HMD, OVR_KEY_EYE_HEIGHT, EyePos.y); + private float eyeYaw = FloatUtil.PI; // 180 degrees in radians + private float frustumNear = 0.1f; + private float frustumFar = 7000f; + private int numSamples; + private TextureAttachment fbo0Tex; + + public OVRSBSRendererSingleFBO(final OVRDistortion dist, final boolean ownsDist, final GLEventListener2 upstream, final int numSamples) { + this.dist = dist; + this.ownsDist = ownsDist; + this.upstream = upstream; + this.numSamples = numSamples; + fbo0 = new FBObject(); + } + + /** + * + * @param eyePos + * @param eyeYaw + * @param frustumNear + * @param frustumFar + */ + public void setUpstreamPMVParams(final float[] eyePos, final float eyeYaw, final float frustumNear, final float frustumFar) { + System.arraycopy(eyePos, 0, this.eyePos, 0, 3); + this.eyeYaw = eyeYaw; + this.frustumNear = frustumNear; + this.frustumFar = frustumFar; + } + + 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 + fbo0.detachAllColorbuffer(gl); + + fbo0.reset(gl, width, height, numSamples, false); + numSamples = fbo0.getNumSamples(); + + if(numSamples>0) { + fbo0.attachColorbuffer(gl, 0, true); + fbo0.resetSamplingSink(gl); + fbo0Tex = fbo0.getSamplingSink(); + } else { + fbo0Tex = fbo0.attachTexture2D(gl, 0, true); + } + numSamples=fbo0.getNumSamples(); + fbo0.attachRenderbuffer(gl, Type.DEPTH, 24); + fbo0.unbind(gl); + } + + @SuppressWarnings("unused") + private void resetFBOs(final GL gl, final int width, final int height) { + fbo0.reset(gl, width, height, numSamples, true); + numSamples = fbo0.getNumSamples(); + if(numSamples>0) { + fbo0Tex = fbo0.getSamplingSink(); + } else { + fbo0Tex = (TextureAttachment) fbo0.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); + fbo0.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 ) { + fbo0.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]); + + dist.getSBSUpstreamPMV(eyeNum, eyePos, eyeYaw, frustumNear, frustumFar, mat4Projection, mat4Modelview); + upstream.setProjectionModelview(drawable, mat4Projection, mat4Modelview); + upstream.display(drawable, eyeNum > 0 ? GLEventListener2.DISPLAY_REPEAT | GLEventListener2.DISPLAY_DONTCLEAR : 0); + } + fbo0.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 ) { + fbo0.use(gl, fbo0Tex); + dist.display(gl, frameTiming.getTimewarpPointSeconds()); + fbo0.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 new file mode 100644 index 000000000..590b0e834 --- /dev/null +++ b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRDistortion.java @@ -0,0 +1,638 @@ +/** + * 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.GLException; +import javax.media.opengl.GLUniformData; + +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.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.GLArrayDataServer; +import com.jogamp.opengl.util.glsl.ShaderCode; +import com.jogamp.opengl.util.glsl.ShaderProgram; + +/** + * 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 float[/*3*/] viewAdjust; + + 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 eyeRenderDesc; + public final ovrFovPort eyeRenderFov; + + public ovrPosef eyeRenderPose; + public final Quaternion eyeRenderPoseOrientation; + public final float[] eyeRenderPosePosition; + + 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 ovrEyeRenderDesc eyeRenderDesc, final ovrSizei ovrTextureSize, final int[] eyeRenderViewport) { + this.eyeName = eyeRenderDesc.getEye(); + this.distortionCaps = distortionCaps; + viewport = new int[4]; + viewAdjust = new float[3]; + 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.eyeRenderDesc = eyeRenderDesc; + this.eyeRenderFov = eyeRenderDesc.getFov(); + + this.eyeRenderPoseOrientation = new Quaternion(); + this.eyeRenderPosePosition = new float[3]; + + + final ovrDistortionMesh meshData = ovrDistortionMesh.create(); + final ovrFovPort fov = eyeRenderDesc.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, GL2ES2.GL_SHORT, indexCount, GL.GL_STATIC_DRAW, GL.GL_ELEMENT_ARRAY_BUFFER); + OVRUtil.copyVec3fToFloat(eyeRenderDesc.getViewAdjust(), viewAdjust); + + // 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 #eyeRenderPose} and it's extracted + * {@link #eyeRenderPoseOrientation} and {@link #eyeRenderPosePosition}. + * @param hmdCtx used get the {@link #eyeRenderPose} via {@link OVR#ovrHmd_GetEyePose(OvrHmdContext, int)} + */ + public void updateEyePose(final OvrHmdContext hmdCtx) { + eyeRenderPose = OVR.ovrHmd_GetEyePose(hmdCtx, eyeName); + OVRUtil.copyToQuaternion(eyeRenderPose.getOrientation(), eyeRenderPoseOrientation); + OVRUtil.copyVec3fToFloat(eyeRenderPose.getPosition(), eyeRenderPosePosition); + } + + @Override + public String toString() { + return "Eye["+eyeName+", viewport "+viewport[0]+"/"+viewport[1]+" "+viewport[2]+"x"+viewport[3]+ + " viewAdjust["+viewAdjust[0]+", "+viewAdjust[1]+", "+viewAdjust[2]+ + "], 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(eyeRenderDesc)+"]"; + } + } + + 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 final float[] vec3Tmp1 = new float[3]; + private final float[] vec3Tmp2 = new float[3]; + private final float[] vec3Tmp3 = new float[3]; + + 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()+ + ", "+Platform.NEWLINE+" "+eyes[0]+", "+Platform.NEWLINE+" "+eyes[1]+"]"; + } + + public static OVRDistortion create(final OvrHmdContext hmdCtx, final boolean sbsSingleTexture, + 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, eyeRenderDesc, textureSize, eyeRenderViewports, distortionCaps, 0); + } + + public OVRDistortion(final OvrHmdContext hmdCtx, final boolean sbsSingleTexture, 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, eyeRenderDescs[0], ovrTextureSize, eyeRenderViewports[0]); + eyes[1] = new EyeData(hmdCtx, distortionCaps, 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 void updateUniforms(final GL2ES2 gl, final int eyeNum) { + if( null == sp ) { + throw new IllegalStateException("Not initialized"); + } + gl.glUniform(texUnit0); + eyes[eyeNum].updateUniform(gl, sp); + } + + 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 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.eyeRenderPose, mat4Tmp1, mat4Tmp2); + } + eye.updateUniform(gl, sp); + eye.enableVBO(gl, true); + if( usesDistMesh ) { + gl.glDrawElements(GL.GL_TRIANGLES, eye.indexCount, GL2ES2.GL_UNSIGNED_SHORT, 0); + } else { + gl.glDrawArrays(GL.GL_TRIANGLE_STRIP, 0, eye.vertexCount); + } + eyes[eyeNum].enableVBO(gl, false); + } + + sp.useProgram(gl, false); + } + + 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); + } + + 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.eyeRenderPose, mat4Tmp1, mat4Tmp2); + } + eye.updateUniform(gl, sp); + eye.enableVBO(gl, true); + if( usesDistMesh ) { + gl.glDrawElements(GL.GL_TRIANGLES, eye.indexCount, GL2ES2.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> + * Method also issues {@link EyeData#updateEyePose(OvrHmdContext)}. + * </p> + * @param eyeNum eye denominator + * @param eyePos float[3] eye postion vector + * @param eyeYaw eye direction, i.e. {@link FloatUtil#PI} for 180 degrees + * @param near frustum near value + * @param far frustum far value + * @param mat4Projection float[16] projection matrix result + * @param mat4Modelview float[16] modelview matrix result + */ + public void getSBSUpstreamPMV(final int eyeNum, final float[] eyePos, final float eyeYaw, final float near, final float far, + final float[] mat4Projection, final float[] mat4Modelview) { + final EyeData eyeDist = eyes[eyeNum]; + + eyeDist.updateEyePose(hmdCtx); + + // + // Projection + // + final ovrMatrix4f pm = OVR.ovrMatrix4f_Projection(eyeDist.eyeRenderFov, near, far, true /* rightHanded*/); + /* final float[] mat4Projection = */ FloatUtil.transposeMatrix(pm.getM(0, mat4Tmp1), mat4Projection); + + // + // Modelview + // + final Quaternion rollPitchYaw = new Quaternion(); + rollPitchYaw.rotateByAngleY(eyeYaw); + final float[] shiftedEyePos = rollPitchYaw.rotateVector(vec3Tmp1, 0, eyeDist.eyeRenderPosePosition, 0); + VectorUtil.addVec3(shiftedEyePos, shiftedEyePos, eyePos); + + rollPitchYaw.mult(eyeDist.eyeRenderPoseOrientation); + 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.viewAdjust[0], eyeDist.viewAdjust[1], eyeDist.viewAdjust[2]); + + /* mat4Modelview = */ FloatUtil.multMatrix(mViewAdjust, mLookAt); + } + + +} diff --git a/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRUtil.java b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRUtil.java new file mode 100644 index 000000000..e28a50203 --- /dev/null +++ b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRUtil.java @@ -0,0 +1,111 @@ +/** + * 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 jogamp.opengl.Debug; + +import com.jogamp.oculusvr.ovrEyeRenderDesc; +import com.jogamp.oculusvr.ovrFovPort; +import com.jogamp.oculusvr.ovrQuatf; +import com.jogamp.oculusvr.ovrRecti; +import com.jogamp.oculusvr.ovrSizei; +import com.jogamp.oculusvr.ovrVector2f; +import com.jogamp.oculusvr.ovrVector2i; +import com.jogamp.oculusvr.ovrVector3f; +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(); + final ovrSizei size = res.getSize(); + pos.setX(rect[0]); + pos.setY(rect[1]); + size.setW(rect[2]); + size.setH(rect[3]); + return res; + } + public static ovrRecti[] createOVRRectis(final int[][] rects) { + final ovrRecti[] res = new ovrRecti[rects.length]; + for(int i=0; i<res.length; i++) { + res[0] = createOVRRecti(rects[i]); + } + return res; + } + public static ovrSizei createOVRSizei(final int[] size) { + final ovrSizei res = ovrSizei.create(); + res.setW(size[0]); + 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 void copyToQuaternion(final ovrQuatf in, final Quaternion out) { + out.set(in.getX(), in.getY(), in.getZ(), in.getW()); + } + public static float[] getVec3f(final ovrVector3f v) { + return new float[] { v.getX(), v.getY(), v.getZ() }; + } + public static void copyVec3fToFloat(final ovrVector3f v, final float[] res) { + res[0] = v.getX(); + res[1] = v.getY(); + res[2] = v.getZ(); + } + + public static String toString(final ovrFovPort fov) { + return "["+fov.getLeftTan()+" l, "+fov.getRightTan()+" r, "+ + fov.getUpTan()+" u, "+fov.getDownTan()+" d]"; + } + public static String toString(final ovrSizei rect) { + return "["+rect.getW()+" x "+rect.getH()+"]"; + } + public static String toString(final ovrRecti rect) { + return "["+rect.getPos().getX()+" / "+rect.getPos().getY()+" "+ + rect.getSize().getW()+" x "+rect.getSize().getH()+"]"; + } + public static String toString(final ovrVector2f v2) { + return "["+v2.getX()+", "+v2.getY()+"]"; + } + public static String toString(final ovrVector3f v3) { + return "["+v3.getX()+", "+v3.getY()+", "+v3.getZ()+"]"; + } + + public static String toString(final ovrEyeRenderDesc desc) { + return "["+desc.getEye()+", fov"+toString(desc.getFov())+ + ", viewport"+toString(desc.getDistortedViewport())+ + ", pptCtr"+toString(desc.getPixelsPerTanAngleAtCenter())+ + ", view-adjust"+toString(desc.getViewAdjust())+"]"; + } + +} diff --git a/src/oculusvr/classes/jogamp/opengl/oculusvr/shader/dist01_chroma.fp b/src/oculusvr/classes/jogamp/opengl/oculusvr/shader/dist01_chroma.fp new file mode 100644 index 000000000..6d450fe40 --- /dev/null +++ b/src/oculusvr/classes/jogamp/opengl/oculusvr/shader/dist01_chroma.fp @@ -0,0 +1,26 @@ +//Copyright 2014 JogAmp Community. All rights reserved. + +#if __VERSION__ >= 130 + #define varying in + out vec4 ovr_FragColor; + #define texture2D texture +#else + #define ovr_FragColor gl_FragColor +#endif + +uniform sampler2D ovr_Texture0; + +varying vec3 ovv_Fade; +varying vec2 ovv_TexCoordR; +varying vec2 ovv_TexCoordG; +varying vec2 ovv_TexCoordB; + +void main (void) +{ + // 3 samples for fixing chromatic aberrations + vec3 color = vec3(texture2D(ovr_Texture0, ovv_TexCoordR).r, + texture2D(ovr_Texture0, ovv_TexCoordG).g, + texture2D(ovr_Texture0, ovv_TexCoordB).b); + ovr_FragColor = vec4(ovv_Fade * color, 1.0); // include vignetteFade +} + diff --git a/src/oculusvr/classes/jogamp/opengl/oculusvr/shader/dist01_chroma.vp b/src/oculusvr/classes/jogamp/opengl/oculusvr/shader/dist01_chroma.vp new file mode 100644 index 000000000..254d4b081 --- /dev/null +++ b/src/oculusvr/classes/jogamp/opengl/oculusvr/shader/dist01_chroma.vp @@ -0,0 +1,33 @@ +//Copyright 2014 JogAmp Community. All rights reserved. + +#if __VERSION__ >= 130 + #define attribute in + #define varying out +#endif + +uniform vec2 ovr_EyeToSourceUVScale; +uniform vec2 ovr_EyeToSourceUVOffset; + +attribute vec2 ovr_Position; +attribute vec2 ovr_Params; +attribute vec2 ovr_TexCoordR; +attribute vec2 ovr_TexCoordG; +attribute vec2 ovr_TexCoordB; + +varying vec3 ovv_Fade; +varying vec2 ovv_TexCoordR; +varying vec2 ovv_TexCoordG; +varying vec2 ovv_TexCoordB; + +void main(void) +{ + gl_Position = vec4(ovr_Position.xy, 0.5, 1.0); + ovv_Fade = vec3(ovr_Params.r); // vignetteFade + + ovv_TexCoordR = ovr_TexCoordR * ovr_EyeToSourceUVScale + ovr_EyeToSourceUVOffset; + ovv_TexCoordR.y = 1.0-ovv_TexCoordR.y; + ovv_TexCoordG = ovr_TexCoordG * ovr_EyeToSourceUVScale + ovr_EyeToSourceUVOffset; + ovv_TexCoordG.y = 1.0-ovv_TexCoordG.y; + ovv_TexCoordB = ovr_TexCoordB * ovr_EyeToSourceUVScale + ovr_EyeToSourceUVOffset; + ovv_TexCoordB.y = 1.0-ovv_TexCoordB.y; +} diff --git a/src/oculusvr/classes/jogamp/opengl/oculusvr/shader/dist01_plain.fp b/src/oculusvr/classes/jogamp/opengl/oculusvr/shader/dist01_plain.fp new file mode 100644 index 000000000..d3ee5d04d --- /dev/null +++ b/src/oculusvr/classes/jogamp/opengl/oculusvr/shader/dist01_plain.fp @@ -0,0 +1,22 @@ +//Copyright 2014 JogAmp Community. All rights reserved. + +#if __VERSION__ >= 130 + #define varying in + out vec4 ovr_FragColor; + #define texture2D texture +#else + #define ovr_FragColor gl_FragColor +#endif + +uniform sampler2D ovr_Texture0; + +varying vec3 ovv_Fade; +varying vec2 ovv_TexCoordR; + +void main (void) +{ + // 3 samples for fixing chromatic aberrations + vec3 color = texture2D(ovr_Texture0, ovv_TexCoordR).rgb; + ovr_FragColor = vec4(ovv_Fade * color, 1.0); // include vignetteFade +} + diff --git a/src/oculusvr/classes/jogamp/opengl/oculusvr/shader/dist01_plain.vp b/src/oculusvr/classes/jogamp/opengl/oculusvr/shader/dist01_plain.vp new file mode 100644 index 000000000..6456c7a83 --- /dev/null +++ b/src/oculusvr/classes/jogamp/opengl/oculusvr/shader/dist01_plain.vp @@ -0,0 +1,27 @@ +//Copyright 2014 JogAmp Community. All rights reserved. + +#if __VERSION__ >= 130 + #define attribute in + #define varying out +#endif + +uniform vec2 ovr_EyeToSourceUVScale; +uniform vec2 ovr_EyeToSourceUVOffset; + +attribute vec2 ovr_Position; +attribute vec2 ovr_Params; +attribute vec2 ovr_TexCoordR; + +varying vec3 ovv_Fade; +varying vec2 ovv_TexCoordR; + +void main(void) +{ + gl_Position = vec4(ovr_Position.xy, 0.5, 1.0); + ovv_Fade = vec3(ovr_Params.r); // vignetteFade + + // Vertex inputs are in TanEyeAngle space for the R,G,B channels (i.e. after chromatic aberration and distortion). + // Scale them into the correct [0-1],[0-1] UV lookup space (depending on eye) + ovv_TexCoordR = ovr_TexCoordR * ovr_EyeToSourceUVScale + ovr_EyeToSourceUVOffset; + ovv_TexCoordR.y = 1.0-ovv_TexCoordR.y; +} diff --git a/src/oculusvr/classes/jogamp/opengl/oculusvr/shader/dist01_timewarp.vp b/src/oculusvr/classes/jogamp/opengl/oculusvr/shader/dist01_timewarp.vp new file mode 100644 index 000000000..3485625d2 --- /dev/null +++ b/src/oculusvr/classes/jogamp/opengl/oculusvr/shader/dist01_timewarp.vp @@ -0,0 +1,44 @@ +//Copyright 2014 JogAmp Community. All rights reserved. + +#if __VERSION__ >= 130 + #define attribute in + #define varying out +#endif + +uniform vec2 ovr_EyeToSourceUVScale; +uniform vec2 ovr_EyeToSourceUVOffset; +uniform mat4 ovr_EyeRotationStart; +uniform mat4 ovr_EyeRotationEnd; + +attribute vec2 ovr_Position; +attribute vec2 ovr_Params; +attribute vec2 ovr_TexCoordR; + +varying vec3 ovv_Fade; +varying vec2 ovv_TexCoordR; + +void main(void) +{ + gl_Position = vec4(ovr_Position.xy, 0.0, 1.0); + ovv_Fade = vec3(ovr_Params.r); // vignetteFade + + // Vertex inputs are in TanEyeAngle space for the R,G,B channels (i.e. after chromatic aberration and distortion). + // These are now "real world" vectors in direction (x,y,1) relative to the eye of the HMD. + vec3 TanEyeAngle = vec3 ( ovr_TexCoordR, 1.0 ); + + // Accurate time warp lerp vs. faster + // Apply the two 3x3 timewarp rotations to these vectors. + vec3 TransformedStart = (ovr_EyeRotationStart * vec4(TanEyeAngle, 0)).xyz; + vec3 TransformedEnd = (ovr_EyeRotationEnd * vec4(TanEyeAngle, 0)).xyz; + // And blend between them. + vec3 Transformed = mix ( TransformedStart, TransformedEnd, ovr_Params.g /* timewarpLerpFactor */ ); + + // Project them back onto the Z=1 plane of the rendered images. + float RecipZ = 1.0 / Transformed.z; + vec2 Flattened = vec2 ( Transformed.x * RecipZ, Transformed.y * RecipZ ); + + // These are now still in TanEyeAngle space. + // Scale them into the correct [0-1],[0-1] UV lookup space (depending on eye) + ovv_TexCoordR = Flattened * ovr_EyeToSourceUVScale + ovr_EyeToSourceUVOffset; + ovv_TexCoordR.y = 1.0-ovv_TexCoordR.y; +} diff --git a/src/oculusvr/classes/jogamp/opengl/oculusvr/shader/dist01_timewarp_chroma.vp b/src/oculusvr/classes/jogamp/opengl/oculusvr/shader/dist01_timewarp_chroma.vp new file mode 100644 index 000000000..e2a45e430 --- /dev/null +++ b/src/oculusvr/classes/jogamp/opengl/oculusvr/shader/dist01_timewarp_chroma.vp @@ -0,0 +1,65 @@ +//Copyright 2014 JogAmp Community. All rights reserved. + +#if __VERSION__ >= 130 + #define attribute in + #define varying out +#endif + +uniform vec2 ovr_EyeToSourceUVScale; +uniform vec2 ovr_EyeToSourceUVOffset; +uniform mat4 ovr_EyeRotationStart; +uniform mat4 ovr_EyeRotationEnd; + +attribute vec2 ovr_Position; +attribute vec2 ovr_Params; +attribute vec2 ovr_TexCoordR; +attribute vec2 ovr_TexCoordG; +attribute vec2 ovr_TexCoordB; + +varying vec3 ovv_Fade; +varying vec2 ovv_TexCoordR; +varying vec2 ovv_TexCoordG; +varying vec2 ovv_TexCoordB; + +void main(void) +{ + gl_Position = vec4(ovr_Position.xy, 0.0, 1.0); + ovv_Fade = vec3(ovr_Params.r); // vignetteFade + + // Vertex inputs are in TanEyeAngle space for the R,G,B channels (i.e. after chromatic aberration and distortion). + // These are now "real world" vectors in direction (x,y,1) relative to the eye of the HMD. + vec3 TanEyeAngleR = vec3 ( ovr_TexCoordR, 1.0 ); + vec3 TanEyeAngleG = vec3 ( ovr_TexCoordG, 1.0 ); + vec3 TanEyeAngleB = vec3 ( ovr_TexCoordB, 1.0 ); + + // Accurate time warp lerp vs. faster + // Apply the two 3x3 timewarp rotations to these vectors. + vec3 TransformedRStart = (ovr_EyeRotationStart * vec4(TanEyeAngleR, 0)).xyz; + vec3 TransformedGStart = (ovr_EyeRotationStart * vec4(TanEyeAngleG, 0)).xyz; + vec3 TransformedBStart = (ovr_EyeRotationStart * vec4(TanEyeAngleB, 0)).xyz; + vec3 TransformedREnd = (ovr_EyeRotationEnd * vec4(TanEyeAngleR, 0)).xyz; + vec3 TransformedGEnd = (ovr_EyeRotationEnd * vec4(TanEyeAngleG, 0)).xyz; + vec3 TransformedBEnd = (ovr_EyeRotationEnd * vec4(TanEyeAngleB, 0)).xyz; + + // And blend between them. + vec3 TransformedR = mix ( TransformedRStart, TransformedREnd, ovr_Params.g /* timewarpLerpFactor */ ); + vec3 TransformedG = mix ( TransformedGStart, TransformedGEnd, ovr_Params.g /* timewarpLerpFactor */ ); + vec3 TransformedB = mix ( TransformedBStart, TransformedBEnd, ovr_Params.g /* timewarpLerpFactor */ ); + + // Project them back onto the Z=1 plane of the rendered images. + float RecipZR = 1.0 / TransformedR.z; + float RecipZG = 1.0 / TransformedG.z; + float RecipZB = 1.0 / TransformedB.z; + vec2 FlattenedR = vec2 ( TransformedR.x * RecipZR, TransformedR.y * RecipZR ); + vec2 FlattenedG = vec2 ( TransformedG.x * RecipZG, TransformedG.y * RecipZG ); + vec2 FlattenedB = vec2 ( TransformedB.x * RecipZB, TransformedB.y * RecipZB ); + + // These are now still in TanEyeAngle space. + // Scale them into the correct [0-1],[0-1] UV lookup space (depending on eye) + ovv_TexCoordR = FlattenedR * ovr_EyeToSourceUVScale + ovr_EyeToSourceUVOffset; + ovv_TexCoordR.y = 1.0-ovv_TexCoordR.y; + ovv_TexCoordG = FlattenedG * ovr_EyeToSourceUVScale + ovr_EyeToSourceUVOffset; + ovv_TexCoordG.y = 1.0-ovv_TexCoordG.y; + ovv_TexCoordB = FlattenedB * ovr_EyeToSourceUVScale + ovr_EyeToSourceUVOffset; + ovv_TexCoordB.y = 1.0-ovv_TexCoordB.y; +} 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 b4661e43d..177b573dd 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 @@ -45,7 +45,7 @@ import javax.media.opengl.GL; import javax.media.opengl.GL2ES2; import javax.media.opengl.GLAnimatorControl; import javax.media.opengl.GLAutoDrawable; -import javax.media.opengl.GLEventListener; +import javax.media.opengl.GLEventListener2; import javax.media.opengl.GLProfile; import javax.media.opengl.GLUniformData; @@ -53,7 +53,7 @@ import javax.media.opengl.GLUniformData; * GearsES2.java <BR> * @author Brian Paul (converted to Java by Ron Cemer and Sven Gothel) <P> */ -public class GearsES2 implements GLEventListener, TileRendererBase.TileRendererListener { +public class GearsES2 implements GLEventListener2, TileRendererBase.TileRendererListener { private final FloatBuffer lightPos = Buffers.newDirectFloatBuffer( new float[] { 5.0f, 5.0f, 10.0f } ); private ShaderState st = null; @@ -89,7 +89,7 @@ public class GearsES2 implements GLEventListener, TileRendererBase.TileRendererL private PinchToZoomGesture pinchToZoomGesture = null; - public GearsES2(int swapInterval) { + public GearsES2(final int swapInterval) { this.swapInterval = swapInterval; } @@ -98,49 +98,49 @@ public class GearsES2 implements GLEventListener, TileRendererBase.TileRendererL } @Override - public void addTileRendererNotify(TileRendererBase tr) { + public void addTileRendererNotify(final TileRendererBase tr) { tileRendererInUse = tr; doRotateBeforePrinting = doRotate; setDoRotation(false); } @Override - public void removeTileRendererNotify(TileRendererBase tr) { + public void removeTileRendererNotify(final TileRendererBase tr) { tileRendererInUse = null; setDoRotation(doRotateBeforePrinting); } @Override - public void startTileRendering(TileRendererBase tr) { + public void startTileRendering(final TileRendererBase tr) { System.err.println("GearsES2.startTileRendering: "+sid()+""+tr); } @Override - public void endTileRendering(TileRendererBase tr) { + public void endTileRendering(final TileRendererBase tr) { System.err.println("GearsES2.endTileRendering: "+sid()+""+tr); } - public void setIgnoreFocus(boolean v) { ignoreFocus = v; } - public void setDoRotation(boolean rotate) { this.doRotate = rotate; } - public void setClearBuffers(boolean v) { clearBuffers = v; } - public void setVerbose(boolean v) { verbose = v; } - public void setFlipVerticalInGLOrientation(boolean v) { flipVerticalInGLOrientation=v; } + public void setIgnoreFocus(final boolean v) { ignoreFocus = v; } + public void setDoRotation(final boolean rotate) { this.doRotate = rotate; } + public void setClearBuffers(final boolean v) { clearBuffers = v; } + public void setVerbose(final boolean v) { verbose = v; } + public void setFlipVerticalInGLOrientation(final boolean v) { flipVerticalInGLOrientation=v; } /** float[4] */ - public void setClearColor(float[] clearColor) { + public void setClearColor(final float[] clearColor) { this.clearColor = clearColor; } - public void setGearsColors(FloatBuffer gear1Color, FloatBuffer gear2Color, FloatBuffer gear3Color) { + public void setGearsColors(final FloatBuffer gear1Color, final FloatBuffer gear2Color, final FloatBuffer gear3Color) { this.gear1Color = gear1Color; this.gear2Color = gear2Color; this.gear3Color = gear3Color; } - public void setSharedGearsObjects(GearsObjectES2 g1, GearsObjectES2 g2, GearsObjectES2 g3) { + public void setSharedGearsObjects(final GearsObjectES2 g1, final GearsObjectES2 g2, final GearsObjectES2 g3) { gear1 = g1; gear2 = g2; gear3 = g3; } - public void setSharedGears(GearsES2 shared) { + public void setSharedGears(final GearsES2 shared) { sharedGears = shared; } @@ -161,8 +161,12 @@ public class GearsES2 implements GLEventListener, TileRendererBase.TileRendererL public boolean usesSharedGears() { return usesSharedGears; } - public void setUseMappedBuffers(boolean v) { useMappedBuffers = v; } - public void setValidateBuffers(boolean v) { validateBuffers = v; } + public void setUseMappedBuffers(final boolean v) { useMappedBuffers = v; } + public void setValidateBuffers(final boolean v) { validateBuffers = v; } + + public PMVMatrix getPMVMatrix() { + return pmvMatrix; + } private static final int TIME_OUT = 2000; // 2s private static final int POLL_DIVIDER = 20; // TO/20 @@ -171,7 +175,7 @@ public class GearsES2 implements GLEventListener, TileRendererBase.TileRendererL /** * @return True if this GLEventListener became initialized within TIME_OUT 2s */ - public boolean waitForInit(boolean initialized) throws InterruptedException { + public boolean waitForInit(final boolean initialized) throws InterruptedException { int wait; for (wait=0; wait<POLL_DIVIDER && initialized != isInit ; wait++) { Thread.sleep(TIME_SLICE); @@ -182,7 +186,7 @@ public class GearsES2 implements GLEventListener, TileRendererBase.TileRendererL private final String sid() { return "0x"+Integer.toHexString(hashCode()); } @Override - public void init(GLAutoDrawable drawable) { + public void init(final GLAutoDrawable drawable) { if(null != sharedGears && !sharedGears.isInit() ) { System.err.println(Thread.currentThread()+" GearsES2.init.0 "+sid()+": pending shared Gears .. re-init later XXXXX"); drawable.setGLEventListenerInitState(this, false); @@ -202,8 +206,6 @@ public class GearsES2 implements GLEventListener, TileRendererBase.TileRendererL return; } - gl.glEnable(GL.GL_DEPTH_TEST); - st = new ShaderState(); // st.setVerbose(true); final ShaderCode vp0 = ShaderCode.create(gl, GL2ES2.GL_VERTEX_SHADER, this.getClass(), "shader", @@ -225,7 +227,7 @@ public class GearsES2 implements GLEventListener, TileRendererBase.TileRendererL st.ownUniform(pmvMatrixUniform); st.uniform(gl, pmvMatrixUniform); - GLUniformData lightU = new GLUniformData("lightPos", 3, lightPos); + final GLUniformData lightU = new GLUniformData("lightPos", 3, lightPos); st.ownUniform(lightU); st.uniform(gl, lightU); @@ -315,7 +317,7 @@ public class GearsES2 implements GLEventListener, TileRendererBase.TileRendererL private final GestureHandler.GestureListener pinchToZoomListener = new GestureHandler.GestureListener() { @Override - public void gestureDetected(GestureEvent gh) { + public void gestureDetected(final GestureEvent gh) { final PinchToZoomGesture.ZoomEvent ze = (PinchToZoomGesture.ZoomEvent) gh; final float zoom = ze.getZoom(); // * ( ze.getTrigger().getPointerCount() - 1 ); <- too much .. panZ = zoom * 30f - 30f; // [0 .. 2] -> [-30f .. 30f] @@ -323,7 +325,7 @@ public class GearsES2 implements GLEventListener, TileRendererBase.TileRendererL }; @Override - public void reshape(GLAutoDrawable glad, int x, int y, int width, int height) { + public void reshape(final GLAutoDrawable glad, final int x, final int y, final int width, final int height) { if( !isInit ) { return; } final GL2ES2 gl = glad.getGL().getGL2ES2(); if(-1 != swapInterval) { @@ -333,16 +335,16 @@ public class GearsES2 implements GLEventListener, TileRendererBase.TileRendererL } @Override - public void reshapeTile(TileRendererBase tr, - int tileX, int tileY, int tileWidth, int tileHeight, - int imageWidth, int imageHeight) { + public void reshapeTile(final TileRendererBase tr, + final int tileX, final int tileY, final int tileWidth, final int tileHeight, + final int imageWidth, final int imageHeight) { if( !isInit ) { return; } final GL2ES2 gl = tr.getAttachedDrawable().getGL().getGL2ES2(); gl.setSwapInterval(0); reshapeImpl(gl, tileX, tileY, tileWidth, tileHeight, imageWidth, imageHeight); } - void reshapeImpl(GL2ES2 gl, int tileX, int tileY, int tileWidth, int tileHeight, int imageWidth, int imageHeight) { + void reshapeImpl(final GL2ES2 gl, final int tileX, final int tileY, final int tileWidth, final int tileHeight, final int imageWidth, final int imageHeight) { final boolean msaa = gl.getContext().getGLDrawable().getChosenGLCapabilities().getSampleBuffers(); if(verbose) { System.err.println(Thread.currentThread()+" GearsES2.reshape "+sid()+" "+tileX+"/"+tileY+" "+tileWidth+"x"+tileHeight+" of "+imageWidth+"x"+imageHeight+", swapInterval "+swapInterval+", drawable 0x"+Long.toHexString(gl.getContext().getGLDrawable().getHandle())+", msaa "+msaa+", tileRendererInUse "+tileRendererInUse); @@ -352,18 +354,16 @@ public class GearsES2 implements GLEventListener, TileRendererBase.TileRendererL return; } - st.useProgram(gl, true); - // compute projection parameters 'normal' float left, right, bottom, top; if( imageHeight > imageWidth ) { - float a = (float)imageHeight / (float)imageWidth; + final float a = (float)imageHeight / (float)imageWidth; left = -1.0f; right = 1.0f; bottom = -a; top = a; } else { - float a = (float)imageWidth / (float)imageHeight; + final float a = (float)imageWidth / (float)imageHeight; left = -a; right = a; bottom = -1.0f; @@ -394,15 +394,32 @@ public class GearsES2 implements GLEventListener, TileRendererBase.TileRendererL pmvMatrix.glMatrixMode(PMVMatrix.GL_MODELVIEW); pmvMatrix.glLoadIdentity(); pmvMatrix.glTranslatef(0.0f, 0.0f, -40.0f); + st.useProgram(gl, true); st.uniform(gl, pmvMatrixUniform); st.useProgram(gl, false); - - // System.err.println(Thread.currentThread()+" GearsES2.reshape FIN"); } // private boolean useAndroidDebug = false; @Override - public void dispose(GLAutoDrawable drawable) { + public void setProjectionModelview(final GLAutoDrawable drawable, final float[] mat4Projection, final float[] mat4Modelview) { + final GL2ES2 gl = drawable.getGL().getGL2ES2(); + pmvMatrix.glMatrixMode(PMVMatrix.GL_PROJECTION); + if( flipVerticalInGLOrientation && gl.getContext().getGLDrawable().isGLOriented() ) { + pmvMatrix.glLoadIdentity(); + pmvMatrix.glScalef(1f, -1f, 1f); + pmvMatrix.glMultMatrixf(mat4Projection, 0); + } else { + pmvMatrix.glLoadMatrixf(mat4Projection, 0); + } + pmvMatrix.glMatrixMode(PMVMatrix.GL_MODELVIEW); + pmvMatrix.glLoadMatrixf(mat4Modelview, 0); + st.useProgram(gl, true); + st.uniform(gl, pmvMatrixUniform); + st.useProgram(gl, false); + } + + @Override + public void dispose(final GLAutoDrawable drawable) { if( !isInit ) { return; } isInit = false; if(verbose) { @@ -439,16 +456,25 @@ public class GearsES2 implements GLEventListener, TileRendererBase.TileRendererL } @Override - public void display(GLAutoDrawable drawable) { + public void display(final GLAutoDrawable drawable) { + display(drawable, 0); + } + + @Override + public void display(final GLAutoDrawable drawable, final int flags) { if( !isInit ) { return; } if(null != sharedGears && !sharedGears.isInit() ) { return; } - GLAnimatorControl anim = drawable.getAnimator(); + final GLAnimatorControl anim = drawable.getAnimator(); if( verbose && ( null == anim || !anim.isAnimating() ) ) { 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 != ( GLEventListener2.DISPLAY_REPEAT & flags ); + final boolean dontClear = 0 != ( GLEventListener2.DISPLAY_DONTCLEAR & flags ); + // Turn the gears' teeth - if(doRotate) { - angle += 2.0f; + if( doRotate && !repeatedFrame ) { + angle += 0.5f; } // Get the GL corresponding to the drawable we are animating @@ -462,7 +488,7 @@ public class GearsES2 implements GLEventListener, TileRendererBase.TileRendererL hasFocus = true; } - if( clearBuffers ) { + if( clearBuffers && !dontClear ) { if( null != clearColor ) { gl.glClearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); } else if( null != tileRendererInUse ) { @@ -487,11 +513,7 @@ public class GearsES2 implements GLEventListener, TileRendererBase.TileRendererL return; } - // Only possible if we do not flip the projection matrix - final boolean enableCullFace = ! ( flipVerticalInGLOrientation && gl.getContext().getGLDrawable().isGLOriented() ); - if( enableCullFace ) { - gl.glEnable(GL.GL_CULL_FACE); - } + setGLStates(gl, true); st.useProgram(gl, true); pmvMatrix.glPushMatrix(); @@ -506,8 +528,22 @@ public class GearsES2 implements GLEventListener, TileRendererBase.TileRendererL pmvMatrix.glPopMatrix(); st.useProgram(gl, false); - if( enableCullFace ) { - gl.glDisable(GL.GL_CULL_FACE); + setGLStates(gl, false); + } + + public void setGLStates(final GL2ES2 gl, final boolean enable) { + // Culling only possible if we do not flip the projection matrix + final boolean enableCullFace = ! ( flipVerticalInGLOrientation && gl.getContext().getGLDrawable().isGLOriented() ); + if( enable ) { + gl.glEnable(GL.GL_DEPTH_TEST); + if( enableCullFace ) { + gl.glEnable(GL.GL_CULL_FACE); + } + } else { + gl.glDisable(GL.GL_DEPTH_TEST); + if( enableCullFace ) { + gl.glDisable(GL.GL_CULL_FACE); + } } } @@ -518,13 +554,13 @@ public class GearsES2 implements GLEventListener, TileRendererBase.TileRendererL boolean confinedFixedCenter = false; - public void setConfinedFixedCenter(boolean v) { + public void setConfinedFixedCenter(final boolean v) { confinedFixedCenter = v; } class GearsKeyAdapter extends KeyAdapter { - public void keyPressed(KeyEvent e) { - int kc = e.getKeyCode(); + public void keyPressed(final KeyEvent e) { + final int kc = e.getKeyCode(); if(KeyEvent.VK_LEFT == kc) { view_roty -= 1; } else if(KeyEvent.VK_RIGHT == kc) { @@ -541,20 +577,20 @@ public class GearsES2 implements GLEventListener, TileRendererBase.TileRendererL private int prevMouseX, prevMouseY; @Override - public void mouseClicked(MouseEvent e) { + public void mouseClicked(final MouseEvent e) { } @Override - public void mouseEntered(MouseEvent e) { + public void mouseEntered(final MouseEvent e) { } @Override - public void mouseExited(MouseEvent e) { + public void mouseExited(final MouseEvent e) { } @Override - public void mouseWheelMoved(MouseEvent e) { - float[] rot = e.getRotation(); + public void mouseWheelMoved(final MouseEvent e) { + final float[] rot = e.getRotation(); if( e.isControlDown() ) { // alternative zoom final float incr = e.isShiftDown() ? rot[0] : rot[1] * 0.5f ; @@ -567,7 +603,7 @@ public class GearsES2 implements GLEventListener, TileRendererBase.TileRendererL } } - public void mousePressed(MouseEvent e) { + public void mousePressed(final MouseEvent e) { if( e.getPointerCount()==1 ) { prevMouseX = e.getX(); prevMouseY = e.getY(); @@ -579,10 +615,10 @@ public class GearsES2 implements GLEventListener, TileRendererBase.TileRendererL } } - public void mouseReleased(MouseEvent e) { + public void mouseReleased(final MouseEvent e) { } - public void mouseMoved(MouseEvent e) { + public void mouseMoved(final MouseEvent e) { if( e.isConfined() ) { navigate(e); } else { @@ -593,27 +629,27 @@ public class GearsES2 implements GLEventListener, TileRendererBase.TileRendererL } } - public void mouseDragged(MouseEvent e) { + public void mouseDragged(final MouseEvent e) { navigate(e); } - private void navigate(MouseEvent e) { + private void navigate(final MouseEvent e) { int x = e.getX(); int y = e.getY(); int width, height; - Object source = e.getSource(); + final Object source = e.getSource(); Window window = null; if(source instanceof Window) { window = (Window) source; width=window.getSurfaceWidth(); height=window.getSurfaceHeight(); } else if (source instanceof GLAutoDrawable) { - GLAutoDrawable glad = (GLAutoDrawable) source; + final GLAutoDrawable glad = (GLAutoDrawable) source; width = glad.getSurfaceWidth(); height = glad.getSurfaceHeight(); } else if (GLProfile.isAWTAvailable() && source instanceof java.awt.Component) { - java.awt.Component comp = (java.awt.Component) source; + final java.awt.Component comp = (java.awt.Component) source; width=comp.getWidth(); // FIXME HiDPI: May need to convert window units -> pixel units! height=comp.getHeight(); } else { 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 new file mode 100644 index 000000000..c20af1389 --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/jogl/stereo/ovr/OVRDemo01.java @@ -0,0 +1,208 @@ +/** + * 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.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; + +/** + * All distortions, multisampling and using two FBOs + * <pre> + * java OVRDemo01 -time 10000000 -vignette -chromatic -timewarp -samples 8 + * </pre> + * All distortions, multisampling and using a big single FBO + * <pre> + * java OVRDemo01 -time 10000000 -vignette -chromatic -timewarp -samples 8 -singleFBO + * </pre> + * Test on main screen: + * <pre> + * java OVRDemo01 -time 10000000 -mainScreen + * </pre> + * + */ +public class OVRDemo01 { + static long duration = 10000; // ms + + static boolean useOVRScreen = true; + + static int numSamples = 0; + static boolean useSingleFBO = false; + static boolean useVignette = false; + static boolean useChromatic = false; + static boolean useTimewarp = false; + 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("-singleFBO")) { + useSingleFBO = true; + } else if(args[i].equals("-vignette")) { + useVignette = true; + } else if(args[i].equals("-chromatic")) { + useChromatic = true; + } else if(args[i].equals("-timewarp")) { + useTimewarp = true; + } else if(args[i].equals("-vignette")) { + useVignette = true; + } else if(args[i].equals("-mainScreen")) { + useOVRScreen = false; + } else if(args[i].equals("-autoSwap")) { + useAutoSwap = true; + } + } + final OVRDemo01 demo01 = new OVRDemo01(); + demo01.doIt(0, numSamples, useSingleFBO, useVignette, useChromatic, useTimewarp, + useAutoSwap, true /* useAnimator */, false /* exclusiveContext*/); + } + + public void doIt(final int ovrHmdIndex, 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("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 = OVR.ovrSensorCap_Orientation; + final int supportedSensorCaps = requiredSensorCaps | OVR.ovrSensorCap_YawCorrection | OVR.ovrSensorCap_Position; + OVR.ovrHmd_StartSensor(hmdCtx, supportedSensorCaps, requiredSensorCaps); + + // + // + // + + 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, Animator.MODE_EXPECT_AWT_RENDERING_THREAD); + animator.setExclusiveContext(exclusiveContext); + } + + // + // Oculus Rift setup + // + 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, defaultEyeFov, pixelsPerDisplayPixel, distortionCaps); + System.err.println("OVRDistortion: "+dist); + + final GearsES2 upstream = new GearsES2(0); + upstream.setVerbose(false); + final GLEventListener renderer; + if( useSingleFBO ) { + renderer = new OVRSBSRendererSingleFBO(dist, true /* ownsDist */, upstream, numSamples); + } else { + renderer = new OVRSBSRendererDualFBO(dist, true /* ownsDist */, upstream, 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(); + } +} |