aboutsummaryrefslogtreecommitdiffstats
path: root/src/oculusvr
diff options
context:
space:
mode:
Diffstat (limited to 'src/oculusvr')
-rw-r--r--src/oculusvr/classes/com/jogamp/oculusvr/OVRDynamicLibraryBundleInfo.java6
-rw-r--r--src/oculusvr/classes/com/jogamp/oculusvr/OVRException.java43
-rw-r--r--src/oculusvr/classes/com/jogamp/oculusvr/OVRVersion.java19
-rw-r--r--src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDevice.java158
-rw-r--r--src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDeviceFactory.java51
-rw-r--r--src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDeviceRenderer.java590
-rw-r--r--src/oculusvr/classes/jogamp/opengl/oculusvr/OVRUtil.java156
-rw-r--r--src/oculusvr/classes/jogamp/opengl/oculusvr/shader/dist01_chroma.fp26
-rw-r--r--src/oculusvr/classes/jogamp/opengl/oculusvr/shader/dist01_chroma.vp33
-rw-r--r--src/oculusvr/classes/jogamp/opengl/oculusvr/shader/dist01_plain.fp22
-rw-r--r--src/oculusvr/classes/jogamp/opengl/oculusvr/shader/dist01_plain.vp27
-rw-r--r--src/oculusvr/classes/jogamp/opengl/oculusvr/shader/dist01_timewarp.vp44
-rw-r--r--src/oculusvr/classes/jogamp/opengl/oculusvr/shader/dist01_timewarp_chroma.vp65
13 files changed, 1212 insertions, 28 deletions
diff --git a/src/oculusvr/classes/com/jogamp/oculusvr/OVRDynamicLibraryBundleInfo.java b/src/oculusvr/classes/com/jogamp/oculusvr/OVRDynamicLibraryBundleInfo.java
index 3f4e94195..ad0105bdb 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..cf6eb5dda 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;
@@ -40,7 +45,7 @@ public class OVRVersion extends JogampVersion {
protected static volatile OVRVersion jogampCommonVersionInfo;
- protected OVRVersion(String packageName, Manifest mf) {
+ protected OVRVersion(final String packageName, final Manifest mf) {
super(packageName, mf);
}
@@ -90,11 +95,11 @@ public class OVRVersion extends JogampVersion {
if(null==sb) {
sb = new StringBuilder();
}
- ovrHmdDesc hmdDesc = ovrHmdDesc.create();
+ final ovrHmdDesc hmdDesc = ovrHmdDesc.create();
OVR.ovrHmd_GetDesc(ovrHmdCtx, hmdDesc);
getAvailableCapabilitiesInfo(hmdDesc, ovrHmdIndex, sb);
- ovrSensorDesc sensorDesc = ovrSensorDesc.create();
+ final ovrSensorDesc sensorDesc = ovrSensorDesc.create();
if( OVR.ovrHmd_GetSensorDesc(ovrHmdCtx, sensorDesc) ) {
sb.append("\thmd."+ovrHmdIndex+".sensor.productId:\t0x"+Integer.toHexString(sensorDesc.getProductId())).append(Platform.getNewline());
sb.append("\thmd."+ovrHmdIndex+".sensor.vendorId:\t0x"+Integer.toHexString(sensorDesc.getVendorId())).append(Platform.getNewline());
@@ -111,7 +116,7 @@ public class OVRVersion extends JogampVersion {
* @param sb
* @return
*/
- public static StringBuilder getAvailableCapabilitiesInfo(ovrHmdDesc hmdDesc, final int ovrHmdIndex, StringBuilder sb) {
+ public static StringBuilder getAvailableCapabilitiesInfo(final ovrHmdDesc hmdDesc, final int ovrHmdIndex, StringBuilder sb) {
if(null==sb) {
sb = new StringBuilder();
}
@@ -124,7 +129,7 @@ public class OVRVersion extends JogampVersion {
sb.append("\thmd."+ovrHmdIndex+".sensorCaps:\t"+hmdDesc.getSensorCaps()).append(Platform.getNewline());
final ovrSizei resolution = hmdDesc.getResolution();
sb.append("\thmd."+ovrHmdIndex+".resolution:\t"+resolution.getW()+"x"+resolution.getH()).append(Platform.getNewline());
- ovrVector2i winPos = hmdDesc.getWindowsPos();
+ final ovrVector2i winPos = hmdDesc.getWindowsPos();
sb.append("\thmd."+ovrHmdIndex+".winPos:\t"+winPos.getX()+" / "+winPos.getY()).append(Platform.getNewline());
return sb;
}
@@ -139,7 +144,7 @@ public class OVRVersion extends JogampVersion {
return sb;
}
- public static void main(String args[]) {
+ public static void main(final String args[]) {
System.err.println(VersionUtil.getPlatformInfo());
System.err.println(GlueGenVersion.getInstance());
// System.err.println(NativeWindowVersion.getInstance());
diff --git a/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDevice.java b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDevice.java
new file mode 100644
index 000000000..09a348c46
--- /dev/null
+++ b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDevice.java
@@ -0,0 +1,158 @@
+/**
+ * Copyright 2014 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+package jogamp.opengl.oculusvr;
+
+import javax.media.nativewindow.util.Dimension;
+import javax.media.nativewindow.util.DimensionImmutable;
+import javax.media.nativewindow.util.PointImmutable;
+import javax.media.nativewindow.util.Rectangle;
+import javax.media.nativewindow.util.RectangleImmutable;
+
+import com.jogamp.oculusvr.OVR;
+import com.jogamp.oculusvr.OvrHmdContext;
+import com.jogamp.oculusvr.ovrEyeRenderDesc;
+import com.jogamp.oculusvr.ovrFovPort;
+import com.jogamp.oculusvr.ovrHmdDesc;
+import com.jogamp.oculusvr.ovrSizei;
+import com.jogamp.opengl.math.FovHVHalves;
+import com.jogamp.opengl.util.stereo.StereoDevice;
+import com.jogamp.opengl.util.stereo.StereoDeviceRenderer;
+
+public class OVRStereoDevice implements StereoDevice {
+ public final OvrHmdContext handle;
+ public final int deviceIndex;
+ public final ovrHmdDesc hmdDesc;
+
+ private boolean sensorsStarted = false;
+
+ public OVRStereoDevice(final OvrHmdContext nativeContext, final int deviceIndex) {
+ this.handle = nativeContext;
+ this.deviceIndex = deviceIndex;
+ this.hmdDesc = ovrHmdDesc.create();
+ OVR.ovrHmd_GetDesc(handle, hmdDesc);
+ }
+
+ @Override
+ public final void dispose() {
+ // NOP
+ }
+
+ @Override
+ public final PointImmutable getPosition() {
+ return OVRUtil.getVec2iAsPoint(hmdDesc.getWindowsPos());
+ }
+
+ @Override
+ public final DimensionImmutable getSurfaceSize() {
+ return OVRUtil.getOVRSizei(hmdDesc.getResolution());
+ }
+
+ @Override
+ public final FovHVHalves[] getDefaultFOV() {
+ final ovrFovPort[] defaultEyeFov = hmdDesc.getDefaultEyeFov(0, new ovrFovPort[2]);
+ final FovHVHalves[] eyeFov = new FovHVHalves[2];
+ eyeFov[0] = OVRUtil.getFovHV(defaultEyeFov[0]);
+ eyeFov[1] = OVRUtil.getFovHV(defaultEyeFov[1]);
+ return eyeFov;
+ }
+
+ @Override
+ public final boolean startSensors(final boolean start) {
+ if( start && !sensorsStarted ) {
+ // Start the sensor which provides the Rift’s pose and motion.
+ final int requiredSensorCaps = 0;
+ final int supportedSensorCaps = requiredSensorCaps | OVR.ovrSensorCap_Orientation | OVR.ovrSensorCap_YawCorrection | OVR.ovrSensorCap_Position;
+ if( OVR.ovrHmd_StartSensor(handle, supportedSensorCaps, requiredSensorCaps) ) {
+ sensorsStarted = true;
+ return true;
+ } else {
+ sensorsStarted = false;
+ return false;
+ }
+ } else if( sensorsStarted ) {
+ OVR.ovrHmd_StopSensor(handle);
+ sensorsStarted = false;
+ return true;
+ } else {
+ // No state change -> Success
+ return true;
+ }
+ }
+ @Override
+ public boolean getSensorsStarted() { return sensorsStarted; }
+
+ @Override
+ public final StereoDeviceRenderer createRenderer(final int distortionBits,
+ final int textureCount, final float[] eyePositionOffset,
+ final FovHVHalves[] eyeFov, final float pixelsPerDisplayPixel, final int textureUnit) {
+ final ovrFovPort ovrEyeFov0 = OVRUtil.getOVRFovPort(eyeFov[0]);
+ final ovrFovPort ovrEyeFov1 = OVRUtil.getOVRFovPort(eyeFov[1]);
+
+ final ovrEyeRenderDesc[] eyeRenderDesc = new ovrEyeRenderDesc[2];
+ eyeRenderDesc[0] = OVR.ovrHmd_GetRenderDesc(handle, OVR.ovrEye_Left, ovrEyeFov0);
+ eyeRenderDesc[1] = OVR.ovrHmd_GetRenderDesc(handle, OVR.ovrEye_Right, ovrEyeFov1);
+ if( StereoDevice.DEBUG ) {
+ System.err.println("XXX: eyeRenderDesc[0] "+OVRUtil.toString(eyeRenderDesc[0]));
+ System.err.println("XXX: eyeRenderDesc[1] "+OVRUtil.toString(eyeRenderDesc[1]));
+ }
+
+ final ovrSizei recommenedTex0Size = OVR.ovrHmd_GetFovTextureSize(handle, OVR.ovrEye_Left, eyeRenderDesc[0].getFov(), pixelsPerDisplayPixel);
+ final ovrSizei recommenedTex1Size = OVR.ovrHmd_GetFovTextureSize(handle, OVR.ovrEye_Right, eyeRenderDesc[1].getFov(), pixelsPerDisplayPixel);
+ if( StereoDevice.DEBUG ) {
+ System.err.println("XXX: recommenedTex0Size "+OVRUtil.toString(recommenedTex0Size));
+ System.err.println("XXX: recommenedTex1Size "+OVRUtil.toString(recommenedTex1Size));
+ }
+ final int unifiedW = Math.max(recommenedTex0Size.getW(), recommenedTex1Size.getW());
+ final int unifiedH = Math.max(recommenedTex0Size.getH(), recommenedTex1Size.getH());
+
+ final DimensionImmutable singleTextureSize = new Dimension(unifiedW, unifiedH);
+ final DimensionImmutable totalTextureSize = new Dimension(recommenedTex0Size.getW() + recommenedTex1Size.getW(), unifiedH);
+ if( StereoDevice.DEBUG ) {
+ System.err.println("XXX: textureSize Single "+singleTextureSize);
+ System.err.println("XXX: textureSize Total "+totalTextureSize);
+ }
+
+ final RectangleImmutable[] eyeRenderViewports = new RectangleImmutable[2];
+ if( 1 == textureCount ) { // validated in ctor below!
+ eyeRenderViewports[0] = new Rectangle(0, 0,
+ totalTextureSize.getWidth() / 2,
+ totalTextureSize.getHeight());
+
+ eyeRenderViewports[1] = new Rectangle((totalTextureSize.getWidth() + 1) / 2, 0,
+ totalTextureSize.getWidth() / 2,
+ totalTextureSize.getHeight());
+ } else {
+ eyeRenderViewports[0] = new Rectangle(0, 0,
+ singleTextureSize.getWidth(),
+ singleTextureSize.getHeight());
+ eyeRenderViewports[1] = eyeRenderViewports[0];
+ }
+ return new OVRStereoDeviceRenderer(this, distortionBits, textureCount, eyePositionOffset,
+ eyeRenderDesc, singleTextureSize, totalTextureSize, eyeRenderViewports, textureUnit);
+ }
+} \ No newline at end of file
diff --git a/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDeviceFactory.java b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDeviceFactory.java
new file mode 100644
index 000000000..06454e443
--- /dev/null
+++ b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDeviceFactory.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright 2014 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+package jogamp.opengl.oculusvr;
+
+import com.jogamp.oculusvr.OVR;
+import com.jogamp.oculusvr.OVRVersion;
+import com.jogamp.oculusvr.OvrHmdContext;
+import com.jogamp.opengl.util.stereo.StereoDevice;
+import com.jogamp.opengl.util.stereo.StereoDeviceFactory;
+
+public class OVRStereoDeviceFactory extends StereoDeviceFactory {
+
+ public static boolean isAvailable() {
+ return OVR.ovr_Initialize(); // recursive ..
+ }
+
+ @Override
+ public final StereoDevice createDevice(final int deviceIndex, final boolean verbose) {
+ final OvrHmdContext hmdCtx = OVR.ovrHmd_Create(deviceIndex);
+ final OVRStereoDevice ctx = new OVRStereoDevice(hmdCtx, deviceIndex);
+ if( verbose ) {
+ System.err.println(OVRVersion.getAvailableCapabilitiesInfo(ctx.hmdDesc, deviceIndex, null).toString());
+ }
+ return ctx;
+ }
+}
diff --git a/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDeviceRenderer.java b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDeviceRenderer.java
new file mode 100644
index 000000000..012ad183d
--- /dev/null
+++ b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDeviceRenderer.java
@@ -0,0 +1,590 @@
+/**
+ * Copyright 2014 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+package jogamp.opengl.oculusvr;
+
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+import javax.media.nativewindow.util.DimensionImmutable;
+import javax.media.nativewindow.util.RectangleImmutable;
+import javax.media.opengl.GL;
+import javax.media.opengl.GL2ES2;
+import javax.media.opengl.GLArrayData;
+import javax.media.opengl.GLException;
+import javax.media.opengl.GLUniformData;
+
+import jogamp.common.os.PlatformPropsImpl;
+
+import com.jogamp.common.nio.Buffers;
+import com.jogamp.oculusvr.OVR;
+import com.jogamp.oculusvr.OVRException;
+import com.jogamp.oculusvr.OvrHmdContext;
+import com.jogamp.oculusvr.ovrDistortionMesh;
+import com.jogamp.oculusvr.ovrDistortionVertex;
+import com.jogamp.oculusvr.ovrEyeRenderDesc;
+import com.jogamp.oculusvr.ovrFovPort;
+import com.jogamp.oculusvr.ovrFrameTiming;
+import com.jogamp.oculusvr.ovrMatrix4f;
+import com.jogamp.oculusvr.ovrPosef;
+import com.jogamp.oculusvr.ovrRecti;
+import com.jogamp.oculusvr.ovrSizei;
+import com.jogamp.oculusvr.ovrVector2f;
+import com.jogamp.oculusvr.ovrVector3f;
+import com.jogamp.opengl.JoglVersion;
+import com.jogamp.opengl.math.FloatUtil;
+import com.jogamp.opengl.util.GLArrayDataServer;
+import com.jogamp.opengl.util.glsl.ShaderCode;
+import com.jogamp.opengl.util.glsl.ShaderProgram;
+import com.jogamp.opengl.util.stereo.EyeParameter;
+import com.jogamp.opengl.util.stereo.EyePose;
+import com.jogamp.opengl.util.stereo.StereoDevice;
+import com.jogamp.opengl.util.stereo.StereoDeviceRenderer;
+import com.jogamp.opengl.util.stereo.StereoUtil;
+
+/**
+ * OculusVR Distortion Data and OpenGL Renderer Utility
+ */
+public class OVRStereoDeviceRenderer implements StereoDeviceRenderer {
+ private static final String shaderPrefix01 = "dist01";
+ private static final String shaderTimewarpSuffix = "_timewarp";
+ private static final String shaderChromaSuffix = "_chroma";
+ private static final String shaderPlainSuffix = "_plain";
+
+ public static class OVREye implements StereoDeviceRenderer.Eye {
+ private final int eyeName;
+ private final int distortionBits;
+ private final int vertexCount;
+ private final int indexCount;
+ private final RectangleImmutable viewport;
+
+ private final GLUniformData eyeToSourceUVScale;
+ private final GLUniformData eyeToSourceUVOffset;
+ private final GLUniformData eyeRotationStart;
+ private final GLUniformData eyeRotationEnd;
+
+ /** 2+2+2+2+2: { vec2 position, vec2 color, vec2 texCoordR, vec2 texCoordG, vec2 texCoordB } */
+ private final GLArrayDataServer iVBO;
+ private final GLArrayData vboPos, vboParams, vboTexCoordsR, vboTexCoordsG, vboTexCoordsB;
+ private final GLArrayDataServer indices;
+
+ private final ovrEyeRenderDesc ovrEyeDesc;
+ private final ovrFovPort ovrEyeFov;
+ private final EyeParameter eyeParameter;
+
+ private ovrPosef ovrEyePose;
+ private final EyePose eyePose;
+
+ @Override
+ public final RectangleImmutable getViewport() { return viewport; }
+
+ @Override
+ public final EyeParameter getEyeParameter() { return eyeParameter; }
+
+ @Override
+ public final EyePose getLastEyePose() { return eyePose; }
+
+ private OVREye(final OvrHmdContext hmdCtx, final int distortionBits,
+ final float[] eyePositionOffset, final ovrEyeRenderDesc eyeDesc,
+ final ovrSizei ovrTextureSize, final RectangleImmutable eyeViewport) {
+ this.eyeName = eyeDesc.getEye();
+ this.distortionBits = distortionBits;
+ this.viewport = eyeViewport;
+
+ final boolean usesTimewarp = StereoUtil.usesTimewarpDistortion(distortionBits);
+ final FloatBuffer fstash = Buffers.newDirectFloatBuffer( 2 + 2 + ( usesTimewarp ? 16 + 16 : 0 ) ) ;
+
+ eyeToSourceUVScale = new GLUniformData("ovr_EyeToSourceUVScale", 2, Buffers.slice2Float(fstash, 0, 2));
+ eyeToSourceUVOffset = new GLUniformData("ovr_EyeToSourceUVOffset", 2, Buffers.slice2Float(fstash, 2, 2));
+
+ if( usesTimewarp ) {
+ eyeRotationStart = new GLUniformData("ovr_EyeRotationStart", 4, 4, Buffers.slice2Float(fstash, 4, 16));
+ eyeRotationEnd = new GLUniformData("ovr_EyeRotationEnd", 4, 4, Buffers.slice2Float(fstash, 20, 16));
+ } else {
+ eyeRotationStart = null;
+ eyeRotationEnd = null;
+ }
+
+ this.ovrEyeDesc = eyeDesc;
+ this.ovrEyeFov = eyeDesc.getFov();
+
+ final ovrVector3f eyeViewAdjust = eyeDesc.getViewAdjust();
+ this.eyeParameter = new EyeParameter(eyeName, eyePositionOffset, OVRUtil.getFovHV(ovrEyeFov),
+ eyeViewAdjust.getX(), eyeViewAdjust.getY(), eyeViewAdjust.getZ());
+
+ this.eyePose = new EyePose(eyeName);
+
+ updateEyePose(hmdCtx); // 1st init
+
+ final ovrDistortionMesh meshData = ovrDistortionMesh.create();
+ final ovrFovPort fov = eyeDesc.getFov();
+
+ final int ovrDistortionCaps = distBits2OVRDistCaps(distortionBits);
+ if( !OVR.ovrHmd_CreateDistortionMesh(hmdCtx, eyeName, fov, ovrDistortionCaps, meshData) ) {
+ throw new OVRException("Failed to create meshData for eye "+eyeName+", "+OVRUtil.toString(fov)+" and "+StereoUtil.distortionBitsToString(distortionBits));
+ }
+ vertexCount = meshData.getVertexCount();
+ indexCount = meshData.getIndexCount();
+
+ /** 2+2+2+2+2: { vec2 position, vec2 color, vec2 texCoordR, vec2 texCoordG, vec2 texCoordB } */
+ final boolean useChromatic = StereoUtil.usesChromaticDistortion(distortionBits);
+ final boolean useVignette = StereoUtil.usesVignetteDistortion(distortionBits);
+
+ final int compsPerElement = 2+2+2+( useChromatic ? 2+2 /* texCoordG + texCoordB */: 0 );
+ iVBO = GLArrayDataServer.createGLSLInterleaved(compsPerElement, GL.GL_FLOAT, false, vertexCount, GL.GL_STATIC_DRAW);
+ vboPos = iVBO.addGLSLSubArray("ovr_Position", 2, GL.GL_ARRAY_BUFFER);
+ vboParams = iVBO.addGLSLSubArray("ovr_Params", 2, GL.GL_ARRAY_BUFFER);
+ vboTexCoordsR = iVBO.addGLSLSubArray("ovr_TexCoordR", 2, GL.GL_ARRAY_BUFFER);
+ if( useChromatic ) {
+ vboTexCoordsG = iVBO.addGLSLSubArray("ovr_TexCoordG", 2, GL.GL_ARRAY_BUFFER);
+ vboTexCoordsB = iVBO.addGLSLSubArray("ovr_TexCoordB", 2, GL.GL_ARRAY_BUFFER);
+ } else {
+ vboTexCoordsG = null;
+ vboTexCoordsB = null;
+ }
+ indices = GLArrayDataServer.createData(1, GL.GL_SHORT, indexCount, GL.GL_STATIC_DRAW, GL.GL_ELEMENT_ARRAY_BUFFER);
+
+ // Setup: eyeToSourceUVScale, eyeToSourceUVOffset
+ {
+ final ovrVector2f[] uvScaleOffsetOut = new ovrVector2f[2];
+ uvScaleOffsetOut[0] = ovrVector2f.create(); // FIXME: remove ctor / double check
+ uvScaleOffsetOut[1] = ovrVector2f.create();
+
+ final ovrRecti ovrEyeRenderViewport = OVRUtil.createOVRRecti(eyeViewport);
+ OVR.ovrHmd_GetRenderScaleAndOffset(fov, ovrTextureSize, ovrEyeRenderViewport, uvScaleOffsetOut);
+ if( StereoDevice.DEBUG ) {
+ System.err.println("XXX."+eyeName+": fov "+OVRUtil.toString(fov));
+ System.err.println("XXX."+eyeName+": uvScale "+OVRUtil.toString(uvScaleOffsetOut[0]));
+ System.err.println("XXX."+eyeName+": uvOffset "+OVRUtil.toString(uvScaleOffsetOut[1]));
+ System.err.println("XXX."+eyeName+": textureSize "+OVRUtil.toString(ovrTextureSize));
+ System.err.println("XXX."+eyeName+": viewport "+OVRUtil.toString(ovrEyeRenderViewport));
+ }
+ final FloatBuffer eyeToSourceUVScaleFB = eyeToSourceUVScale.floatBufferValue();
+ eyeToSourceUVScaleFB.put(0, uvScaleOffsetOut[0].getX());
+ eyeToSourceUVScaleFB.put(1, uvScaleOffsetOut[0].getY());
+ final FloatBuffer eyeToSourceUVOffsetFB = eyeToSourceUVOffset.floatBufferValue();
+ eyeToSourceUVOffsetFB.put(0, uvScaleOffsetOut[1].getX());
+ eyeToSourceUVOffsetFB.put(1, uvScaleOffsetOut[1].getY());
+ }
+
+ /** 2+2+2+2+2: { vec2 position, vec2 color, vec2 texCoordR, vec2 texCoordG, vec2 texCoordB } */
+ final FloatBuffer iVBOFB = (FloatBuffer)iVBO.getBuffer();
+ final ovrDistortionVertex[] ovRes = new ovrDistortionVertex[1];
+ ovRes[0] = ovrDistortionVertex.create(); // FIXME: remove ctor / double check
+
+ for ( int vertNum = 0; vertNum < vertexCount; vertNum++ ) {
+ final ovrDistortionVertex ov = meshData.getPVertexData(vertNum, ovRes)[0];
+ ovrVector2f v;
+
+ // pos
+ v = ov.getPos();
+ iVBOFB.put(v.getX());
+ iVBOFB.put(v.getY());
+
+ // params
+ if( useVignette ) {
+ iVBOFB.put(ov.getVignetteFactor());
+ } else {
+ iVBOFB.put(1.0f);
+ }
+ iVBOFB.put(ov.getTimeWarpFactor());
+
+ // texCoordR
+ v = ov.getTexR();
+ iVBOFB.put(v.getX());
+ iVBOFB.put(v.getY());
+
+ if( useChromatic ) {
+ // texCoordG
+ v = ov.getTexG();
+ iVBOFB.put(v.getX());
+ iVBOFB.put(v.getY());
+
+ // texCoordB
+ v = ov.getTexB();
+ iVBOFB.put(v.getX());
+ iVBOFB.put(v.getY());
+ }
+ }
+ if( StereoDevice.DEBUG ) {
+ System.err.println("XXX."+eyeName+": iVBO "+iVBO);
+ }
+ {
+ final ShortBuffer in = meshData.getPIndexData();
+ final ShortBuffer out = (ShortBuffer) indices.getBuffer();
+ out.put(in);
+ }
+ if( StereoDevice.DEBUG ) {
+ System.err.println("XXX."+eyeName+": idx "+indices);
+ System.err.println("XXX."+eyeName+": "+this);
+ }
+ OVR.ovrHmd_DestroyDistortionMesh(meshData);
+ }
+
+ private void linkData(final GL2ES2 gl, final ShaderProgram sp) {
+ if( 0 > vboPos.setLocation(gl, sp.program()) ) {
+ throw new OVRException("Couldn't locate "+vboPos);
+ }
+ if( 0 > vboParams.setLocation(gl, sp.program()) ) {
+ throw new OVRException("Couldn't locate "+vboParams);
+ }
+ if( 0 > vboTexCoordsR.setLocation(gl, sp.program()) ) {
+ throw new OVRException("Couldn't locate "+vboTexCoordsR);
+ }
+ if( StereoUtil.usesChromaticDistortion(distortionBits) ) {
+ if( 0 > vboTexCoordsG.setLocation(gl, sp.program()) ) {
+ throw new OVRException("Couldn't locate "+vboTexCoordsG);
+ }
+ if( 0 > vboTexCoordsB.setLocation(gl, sp.program()) ) {
+ throw new OVRException("Couldn't locate "+vboTexCoordsB);
+ }
+ }
+ if( 0 > eyeToSourceUVScale.setLocation(gl, sp.program()) ) {
+ throw new OVRException("Couldn't locate "+eyeToSourceUVScale);
+ }
+ if( 0 > eyeToSourceUVOffset.setLocation(gl, sp.program()) ) {
+ throw new OVRException("Couldn't locate "+eyeToSourceUVOffset);
+ }
+ if( StereoUtil.usesTimewarpDistortion(distortionBits) ) {
+ if( 0 > eyeRotationStart.setLocation(gl, sp.program()) ) {
+ throw new OVRException("Couldn't locate "+eyeRotationStart);
+ }
+ if( 0 > eyeRotationEnd.setLocation(gl, sp.program()) ) {
+ throw new OVRException("Couldn't locate "+eyeRotationEnd);
+ }
+ }
+ iVBO.seal(gl, true);
+ iVBO.enableBuffer(gl, false);
+ indices.seal(gl, true);
+ indices.enableBuffer(gl, false);
+ }
+
+ private void dispose(final GL2ES2 gl) {
+ iVBO.destroy(gl);
+ indices.destroy(gl);
+ }
+ private void enableVBO(final GL2ES2 gl, final boolean enable) {
+ iVBO.enableBuffer(gl, enable);
+ indices.bindBuffer(gl, enable); // keeps VBO binding if enable:=true
+ }
+
+ private void updateUniform(final GL2ES2 gl, final ShaderProgram sp) {
+ gl.glUniform(eyeToSourceUVScale);
+ gl.glUniform(eyeToSourceUVOffset);
+ if( StereoUtil.usesTimewarpDistortion(distortionBits) ) {
+ gl.glUniform(eyeRotationStart);
+ gl.glUniform(eyeRotationEnd);
+ }
+ }
+
+ private void updateTimewarp(final OvrHmdContext hmdCtx, final ovrPosef eyeRenderPose, final float[] mat4Tmp1, final float[] mat4Tmp2) {
+ final ovrMatrix4f[] timeWarpMatrices = new ovrMatrix4f[2];
+ timeWarpMatrices[0] = ovrMatrix4f.create(); // FIXME: remove ctor / double check
+ timeWarpMatrices[1] = ovrMatrix4f.create();
+ OVR.ovrHmd_GetEyeTimewarpMatrices(hmdCtx, eyeName, eyeRenderPose, timeWarpMatrices);
+
+ final float[] eyeRotationStartM = FloatUtil.transposeMatrix(timeWarpMatrices[0].getM(0, mat4Tmp1), mat4Tmp2);
+ final FloatBuffer eyeRotationStartU = eyeRotationStart.floatBufferValue();
+ eyeRotationStartU.put(eyeRotationStartM);
+ eyeRotationStartU.rewind();
+
+ final float[] eyeRotationEndM = FloatUtil.transposeMatrix(timeWarpMatrices[1].getM(0, mat4Tmp1), mat4Tmp2);
+ final FloatBuffer eyeRotationEndU = eyeRotationEnd.floatBufferValue();
+ eyeRotationEndU.put(eyeRotationEndM);
+ eyeRotationEndU.rewind();
+ }
+
+ /**
+ * Updates {@link #ovrEyePose} and it's extracted
+ * {@link #eyeRenderPoseOrientation} and {@link #eyeRenderPosePosition}.
+ * @param hmdCtx used get the {@link #ovrEyePose} via {@link OVR#ovrHmd_GetEyePose(OvrHmdContext, int)}
+ */
+ private EyePose updateEyePose(final OvrHmdContext hmdCtx) {
+ ovrEyePose = OVR.ovrHmd_GetEyePose(hmdCtx, eyeName);
+ final ovrVector3f pos = ovrEyePose.getPosition();
+ eyePose.setPosition(pos.getX(), pos.getY(), pos.getZ());
+ OVRUtil.copyToQuaternion(ovrEyePose.getOrientation(), eyePose.orientation);
+ return eyePose;
+ }
+
+ @Override
+ public String toString() {
+ return "Eye["+eyeName+", viewport "+viewport+
+ ", "+eyeParameter+
+ ", vertices "+vertexCount+", indices "+indexCount+
+ ", uvScale["+eyeToSourceUVScale.floatBufferValue().get(0)+", "+eyeToSourceUVScale.floatBufferValue().get(1)+
+ "], uvOffset["+eyeToSourceUVOffset.floatBufferValue().get(0)+", "+eyeToSourceUVOffset.floatBufferValue().get(1)+
+ "], desc"+OVRUtil.toString(ovrEyeDesc)+", "+eyePose+"]";
+ }
+ }
+
+ private final OVRStereoDevice context;
+ private final OVREye[] eyes;
+ private final int distortionBits;
+ private final int textureCount;
+ private final DimensionImmutable singleTextureSize;
+ private final DimensionImmutable totalTextureSize;
+ private final GLUniformData texUnit0;
+
+
+ private final float[] mat4Tmp1 = new float[16];
+ private final float[] mat4Tmp2 = new float[16];
+
+ private ShaderProgram sp;
+ private ovrFrameTiming frameTiming;
+
+ @Override
+ public String toString() {
+ return "OVRDist[distortion["+StereoUtil.distortionBitsToString(distortionBits)+
+ "], singleSize "+singleTextureSize+
+ ", sbsSize "+totalTextureSize+
+ ", texCount "+textureCount+", texUnit "+getTextureUnit()+
+ ", "+PlatformPropsImpl.NEWLINE+" "+eyes[0]+", "+PlatformPropsImpl.NEWLINE+" "+eyes[1]+"]";
+ }
+
+
+ private static int distBits2OVRDistCaps(final int distortionBits) {
+ int bits = 0;
+ if( StereoUtil.usesTimewarpDistortion(distortionBits) ) {
+ bits |= OVR.ovrDistortionCap_TimeWarp;
+ }
+ if( StereoUtil.usesChromaticDistortion(distortionBits) ) {
+ bits |= OVR.ovrDistortionCap_Chromatic;
+ }
+ if( StereoUtil.usesVignetteDistortion(distortionBits) ) {
+ bits |= OVR.ovrDistortionCap_Vignette;
+ }
+ return bits;
+ }
+
+ /* pp */ OVRStereoDeviceRenderer(final OVRStereoDevice context, final int distortionBits,
+ final int textureCount, final float[] eyePositionOffset,
+ final ovrEyeRenderDesc[] eyeRenderDescs, final DimensionImmutable singleTextureSize, final DimensionImmutable totalTextureSize,
+ final RectangleImmutable[] eyeViewports, final int textureUnit) {
+ if( 1 > textureCount || 2 < textureCount ) {
+ throw new IllegalArgumentException("textureCount can only be 1 or 2, has "+textureCount);
+ }
+ this.context = context;
+ this.eyes = new OVREye[2];
+ this.distortionBits = distortionBits | StereoDeviceRenderer.DISTORTION_BARREL /* always */;
+ this.textureCount = textureCount;
+ this.singleTextureSize = singleTextureSize;
+ this.totalTextureSize = totalTextureSize;
+
+ texUnit0 = new GLUniformData("ovr_Texture0", textureUnit);
+
+ final ovrSizei ovrTextureSize = OVRUtil.createOVRSizei( 1 == textureCount ? totalTextureSize : singleTextureSize );
+ eyes[0] = new OVREye(context.handle, this.distortionBits, eyePositionOffset, eyeRenderDescs[0], ovrTextureSize, eyeViewports[0]);
+ eyes[1] = new OVREye(context.handle, this.distortionBits, eyePositionOffset, eyeRenderDescs[1], ovrTextureSize, eyeViewports[1]);
+ sp = null;
+ frameTiming = null;
+ }
+
+ @Override
+ public StereoDevice getDevice() {
+ return context;
+ }
+
+ @Override
+ public final int getDistortionBits() { return distortionBits; }
+
+ @Override
+ public final boolean usesSideBySideStereo() { return true; }
+
+ @Override
+ public final DimensionImmutable getSingleSurfaceSize() { return singleTextureSize; }
+
+ @Override
+ public final DimensionImmutable getTotalSurfaceSize() { return totalTextureSize; }
+
+ @Override
+ public final int getTextureCount() { return textureCount; }
+
+ @Override
+ public final int getTextureUnit() { return texUnit0.intValue(); }
+
+ @Override
+ public final boolean ppRequired() { return true; }
+
+ @Override
+ public final void init(final GL gl) {
+ if( StereoDevice.DEBUG ) {
+ System.err.println(JoglVersion.getGLInfo(gl, null).toString());
+ }
+ if( null != sp ) {
+ throw new IllegalStateException("Already initialized");
+ }
+ final GL2ES2 gl2es2 = gl.getGL2ES2();
+
+ final String vertexShaderBasename;
+ final String fragmentShaderBasename;
+ {
+ final boolean usesTimewarp = StereoUtil.usesTimewarpDistortion(distortionBits);
+ final boolean usesChromatic = StereoUtil.usesChromaticDistortion(distortionBits);
+
+ final StringBuilder sb = new StringBuilder();
+ sb.append(shaderPrefix01);
+ if( !usesChromatic && !usesTimewarp ) {
+ sb.append(shaderPlainSuffix);
+ } else if( usesChromatic && !usesTimewarp ) {
+ sb.append(shaderChromaSuffix);
+ } else if( usesTimewarp ) {
+ sb.append(shaderTimewarpSuffix);
+ if( usesChromatic ) {
+ sb.append(shaderChromaSuffix);
+ }
+ }
+ vertexShaderBasename = sb.toString();
+ sb.setLength(0);
+ sb.append(shaderPrefix01);
+ if( usesChromatic ) {
+ sb.append(shaderChromaSuffix);
+ } else {
+ sb.append(shaderPlainSuffix);
+ }
+ fragmentShaderBasename = sb.toString();
+ }
+ final ShaderCode vp0 = ShaderCode.create(gl2es2, GL2ES2.GL_VERTEX_SHADER, OVRStereoDeviceRenderer.class, "shader",
+ "shader/bin", vertexShaderBasename, true);
+ final ShaderCode fp0 = ShaderCode.create(gl2es2, GL2ES2.GL_FRAGMENT_SHADER, OVRStereoDeviceRenderer.class, "shader",
+ "shader/bin", fragmentShaderBasename, true);
+ vp0.defaultShaderCustomization(gl2es2, true, true);
+ fp0.defaultShaderCustomization(gl2es2, true, true);
+
+ sp = new ShaderProgram();
+ sp.add(gl2es2, vp0, System.err);
+ sp.add(gl2es2, fp0, System.err);
+ if(!sp.link(gl2es2, System.err)) {
+ throw new GLException("could not link program: "+sp);
+ }
+ sp.useProgram(gl2es2, true);
+ if( 0 > texUnit0.setLocation(gl2es2, sp.program()) ) {
+ throw new OVRException("Couldn't locate "+texUnit0);
+ }
+ eyes[0].linkData(gl2es2, sp);
+ eyes[1].linkData(gl2es2, sp);
+ sp.useProgram(gl2es2, false);
+ }
+
+ @Override
+ public final void dispose(final GL gl) {
+ final GL2ES2 gl2es2 = gl.getGL2ES2();
+ sp.useProgram(gl2es2, false);
+ eyes[0].dispose(gl2es2);
+ eyes[1].dispose(gl2es2);
+ sp.destroy(gl2es2);
+ frameTiming = null;
+ }
+
+ @Override
+ public final Eye getEye(final int eyeNum) {
+ return eyes[eyeNum];
+ }
+
+ @Override
+ public final EyePose updateEyePose(final int eyeNum) {
+ return eyes[eyeNum].updateEyePose(context.handle);
+ }
+
+ @Override
+ public final void beginFrame(final GL gl) {
+ frameTiming = OVR.ovrHmd_BeginFrameTiming(context.handle, 0);
+ }
+
+ @Override
+ public final void endFrame(final GL gl) {
+ if( null == frameTiming ) {
+ throw new IllegalStateException("beginFrame not called");
+ }
+ OVR.ovrHmd_EndFrameTiming(context.handle);
+ frameTiming = null;
+ }
+
+ @Override
+ public final void ppBegin(final GL gl) {
+ if( null == sp ) {
+ throw new IllegalStateException("Not initialized");
+ }
+ if( null == frameTiming ) {
+ throw new IllegalStateException("beginFrame not called");
+ }
+ if( StereoUtil.usesTimewarpDistortion(distortionBits) ) {
+ OVR.ovr_WaitTillTime(frameTiming.getTimewarpPointSeconds());
+ }
+ final GL2ES2 gl2es2 = gl.getGL2ES2();
+
+ gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ gl.glClear(GL.GL_COLOR_BUFFER_BIT);
+ gl.glActiveTexture(GL.GL_TEXTURE0 + getTextureUnit());
+
+ gl2es2.glDisable(GL.GL_CULL_FACE);
+ gl2es2.glDisable(GL.GL_DEPTH_TEST);
+ gl2es2.glDisable(GL.GL_BLEND);
+
+ if( !gl2es2.isGLcore() ) {
+ gl2es2.glEnable(GL.GL_TEXTURE_2D);
+ }
+
+ sp.useProgram(gl2es2, true);
+
+ gl2es2.glUniform(texUnit0);
+ }
+
+ @Override
+ public final void ppBothEyes(final GL gl) {
+ final GL2ES2 gl2es2 = gl.getGL2ES2();
+ for(int eyeNum=0; eyeNum<2; eyeNum++) {
+ final OVREye eye = eyes[eyeNum];
+ if( StereoUtil.usesTimewarpDistortion(distortionBits) ) {
+ eye.updateTimewarp(context.handle, eye.ovrEyePose, mat4Tmp1, mat4Tmp2);
+ }
+ eye.updateUniform(gl2es2, sp);
+ eye.enableVBO(gl2es2, true);
+ gl2es2.glDrawElements(GL.GL_TRIANGLES, eye.indexCount, GL.GL_UNSIGNED_SHORT, 0);
+ eyes[eyeNum].enableVBO(gl2es2, false);
+ }
+ }
+
+ @Override
+ public final void ppOneEye(final GL gl, final int eyeNum) {
+ final OVREye eye = eyes[eyeNum];
+ if( StereoUtil.usesTimewarpDistortion(distortionBits) ) {
+ eye.updateTimewarp(context.handle, eye.ovrEyePose, mat4Tmp1, mat4Tmp2);
+ }
+ final GL2ES2 gl2es2 = gl.getGL2ES2();
+
+ eye.updateUniform(gl2es2, sp);
+ eye.enableVBO(gl2es2, true);
+ gl2es2.glDrawElements(GL.GL_TRIANGLES, eye.indexCount, GL.GL_UNSIGNED_SHORT, 0);
+ eyes[eyeNum].enableVBO(gl2es2, false);
+ }
+
+ @Override
+ public final void ppEnd(final GL gl) {
+ sp.useProgram(gl.getGL2ES2(), false);
+ }
+}
diff --git a/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRUtil.java b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRUtil.java
new file mode 100644
index 000000000..48222ea97
--- /dev/null
+++ b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRUtil.java
@@ -0,0 +1,156 @@
+/**
+ * Copyright 2014 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+package jogamp.opengl.oculusvr;
+
+import javax.media.nativewindow.util.Dimension;
+import javax.media.nativewindow.util.DimensionImmutable;
+import javax.media.nativewindow.util.Point;
+import javax.media.nativewindow.util.PointImmutable;
+import javax.media.nativewindow.util.RectangleImmutable;
+
+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.FovHVHalves;
+import com.jogamp.opengl.math.Quaternion;
+
+/**
+ * OculusVR Data Conversion Helper Functions
+ */
+public class OVRUtil {
+ 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 createOVRRecti(final RectangleImmutable rect) {
+ final ovrRecti res = ovrRecti.create();
+ final ovrVector2i pos = res.getPos();
+ final ovrSizei size = res.getSize();
+ pos.setX(rect.getX());
+ pos.setY(rect.getY());
+ size.setW(rect.getWidth());
+ size.setH(rect.getHeight());
+ return res;
+ }
+ public static ovrRecti[] createOVRRectis(final int[][] rects) {
+ final ovrRecti[] res = new ovrRecti[rects.length];
+ for(int i=0; i<res.length; i++) {
+ 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 ovrSizei createOVRSizei(final DimensionImmutable size) {
+ final ovrSizei res = ovrSizei.create();
+ res.setW(size.getWidth());
+ res.setH(size.getHeight());
+ return res;
+ }
+ public static DimensionImmutable getOVRSizei(final ovrSizei v) {
+ return new Dimension(v.getW(), v.getH());
+ }
+ public static PointImmutable getVec2iAsPoint(final ovrVector2i v) {
+ return new Point(v.getX(), v.getY());
+ }
+ public static int[] getVec2i(final ovrVector2i v) {
+ return new int[] { v.getX(), v.getY() };
+ }
+ public static void copyVec2iToInt(final ovrVector2i v, final int[] res) {
+ res[0] = v.getX();
+ res[1] = v.getY();
+ }
+ public static float[] getVec3f(final ovrVector3f v) {
+ return new float[] { v.getX(), v.getY(), v.getZ() };
+ }
+ public static void copyVec3fToFloat(final ovrVector3f v, final float[] res) {
+ res[0] = v.getX();
+ res[1] = v.getY();
+ res[2] = v.getZ();
+ }
+ public static Quaternion getQuaternion(final ovrQuatf q) {
+ return new Quaternion(q.getX(), q.getY(), q.getZ(), q.getW());
+ }
+ public static void copyToQuaternion(final ovrQuatf in, final Quaternion out) {
+ out.set(in.getX(), in.getY(), in.getZ(), in.getW());
+ }
+
+ public static FovHVHalves getFovHV(final ovrFovPort tanHalfFov) {
+ return new FovHVHalves(tanHalfFov.getLeftTan(), tanHalfFov.getRightTan(),
+ tanHalfFov.getUpTan(), tanHalfFov.getDownTan(),
+ true);
+ }
+ public static ovrFovPort getOVRFovPort(final FovHVHalves fovHVHalves) {
+ final ovrFovPort tanHalfFov = ovrFovPort.create();
+ final FovHVHalves fovHVHalvesTan = fovHVHalves.getInTangents();
+ tanHalfFov.setLeftTan(fovHVHalvesTan.left);
+ tanHalfFov.setRightTan(fovHVHalvesTan.right);
+ tanHalfFov.setUpTan(fovHVHalvesTan.top);
+ tanHalfFov.setDownTan(fovHVHalvesTan.bottom);
+ return tanHalfFov;
+ }
+
+ 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;
+}