aboutsummaryrefslogtreecommitdiffstats
path: root/src/jogl/classes/jogamp/opengl/util/stereo/GenericStereoDeviceRenderer.java
diff options
context:
space:
mode:
authorSven Gothel <[email protected]>2014-07-07 23:46:19 +0200
committerSven Gothel <[email protected]>2014-07-07 23:46:19 +0200
commit38e51e4a5f6f35c658df10f6d48a33e3ffaea2f3 (patch)
tree259024b16429986ab48fd49a9bd2667dad2b85eb /src/jogl/classes/jogamp/opengl/util/stereo/GenericStereoDeviceRenderer.java
parent06fc570f70dc5ccfad7399d8426bdf224c239a5a (diff)
Bug 1021: Add GenericStereoDevice* Supporting custom configurations; Hook-in oculusvr-sdk java distortion-mesh calculation if available
StereoDeviceFactory support new GenericStereoDeviceFactory, with it's GenericStereoDevice and GenericStereoDeviceRenderer. GenericStereoDevice maintains different configurations, triggered either by passing a GenericStereoDevice.Config instance directly or by the device-index parameter: - 0: monoscopi device: No post-processing - 1: stereoscopic device SBS: No post-processing - 2: stereoscopic device SBS + Lenses: Distortion post-processing (only available w/ oculusvr-sdk sub-module) Producing a 'GenericStereoDevice.Config' instance is self containing and may extend if supporting more device types like top-bottom, interlaced etc. StereoDemo01 handles all use-cases and may be used as a test-bed to add and experiment with stereoscopy, devices and settings.
Diffstat (limited to 'src/jogl/classes/jogamp/opengl/util/stereo/GenericStereoDeviceRenderer.java')
-rw-r--r--src/jogl/classes/jogamp/opengl/util/stereo/GenericStereoDeviceRenderer.java605
1 files changed, 605 insertions, 0 deletions
diff --git a/src/jogl/classes/jogamp/opengl/util/stereo/GenericStereoDeviceRenderer.java b/src/jogl/classes/jogamp/opengl/util/stereo/GenericStereoDeviceRenderer.java
new file mode 100644
index 000000000..d957bd4e7
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/stereo/GenericStereoDeviceRenderer.java
@@ -0,0 +1,605 @@
+/**
+ * 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.util.stereo;
+
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+import javax.media.nativewindow.util.Dimension;
+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.common.os.Platform;
+import com.jogamp.opengl.JoglVersion;
+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;
+
+/**
+ * Generic Stereo Device Distortion and OpenGL Renderer Utility
+ */
+public class GenericStereoDeviceRenderer 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 GenericEye 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 EyeParameter eyeParameter;
+
+ 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 GenericEye(final GenericStereoDevice device, final int distortionBits,
+ final float[] eyePositionOffset, final EyeParameter eyeParam,
+ final DimensionImmutable textureSize, final RectangleImmutable eyeViewport) {
+ this.eyeName = eyeParam.number;
+ this.distortionBits = distortionBits;
+ this.viewport = eyeViewport;
+
+ final boolean usePP = null != device.config.distortionMeshProducer && 0 != distortionBits;
+
+ final boolean usesTimewarp = usePP && StereoUtil.usesTimewarpDistortion(distortionBits);
+ final FloatBuffer fstash = Buffers.newDirectFloatBuffer( 2 + 2 + ( usesTimewarp ? 16 + 16 : 0 ) ) ;
+
+ if( usePP ) {
+ eyeToSourceUVScale = new GLUniformData("svr_EyeToSourceUVScale", 2, Buffers.slice2Float(fstash, 0, 2));
+ eyeToSourceUVOffset = new GLUniformData("svr_EyeToSourceUVOffset", 2, Buffers.slice2Float(fstash, 2, 2));
+ } else {
+ eyeToSourceUVScale = null;
+ eyeToSourceUVOffset = null;
+ }
+
+ if( usesTimewarp ) {
+ eyeRotationStart = new GLUniformData("svr_EyeRotationStart", 4, 4, Buffers.slice2Float(fstash, 4, 16));
+ eyeRotationEnd = new GLUniformData("svr_EyeRotationEnd", 4, 4, Buffers.slice2Float(fstash, 20, 16));
+ } else {
+ eyeRotationStart = null;
+ eyeRotationEnd = null;
+ }
+
+ this.eyeParameter = eyeParam;
+
+ this.eyePose = new EyePose(eyeName);
+
+ updateEyePose(device); // 1st init
+
+ // Setup: eyeToSourceUVScale, eyeToSourceUVOffset
+ if( usePP ) {
+ final ScaleAndOffset2D textureScaleAndOffset = new ScaleAndOffset2D(eyeParam.fovhv, textureSize, eyeViewport);
+ if( StereoDevice.DEBUG ) {
+ System.err.println("XXX."+eyeName+": eyeParam "+eyeParam);
+ System.err.println("XXX."+eyeName+": uvScaleOffset "+textureScaleAndOffset);
+ System.err.println("XXX."+eyeName+": textureSize "+textureSize);
+ System.err.println("XXX."+eyeName+": viewport "+eyeViewport);
+ }
+ final FloatBuffer eyeToSourceUVScaleFB = eyeToSourceUVScale.floatBufferValue();
+ eyeToSourceUVScaleFB.put(0, textureScaleAndOffset.scale[0]);
+ eyeToSourceUVScaleFB.put(1, textureScaleAndOffset.scale[1]);
+ final FloatBuffer eyeToSourceUVOffsetFB = eyeToSourceUVOffset.floatBufferValue();
+ eyeToSourceUVOffsetFB.put(0, textureScaleAndOffset.offset[0]);
+ eyeToSourceUVOffsetFB.put(1, textureScaleAndOffset.offset[1]);
+ } else {
+ vertexCount = 0;
+ indexCount = 0;
+ iVBO = null;
+ vboPos = null;
+ vboParams = null;
+ vboTexCoordsR = null;
+ vboTexCoordsG = null;
+ vboTexCoordsB = null;
+ indices = null;
+ if( StereoDevice.DEBUG ) {
+ System.err.println("XXX."+eyeName+": "+this);
+ }
+ return;
+ }
+ final DistortionMesh meshData = device.config.distortionMeshProducer.create(eyeParam, distortionBits);
+ if( null == meshData ) {
+ throw new GLException("Failed to create meshData for eye "+eyeParam+", and "+StereoUtil.distortionBitsToString(distortionBits));
+ }
+
+ vertexCount = meshData.vertexCount;
+ indexCount = meshData.indexCount;
+
+ /** 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("svr_Position", 2, GL.GL_ARRAY_BUFFER);
+ vboParams = iVBO.addGLSLSubArray("svr_Params", 2, GL.GL_ARRAY_BUFFER);
+ vboTexCoordsR = iVBO.addGLSLSubArray("svr_TexCoordR", 2, GL.GL_ARRAY_BUFFER);
+ if( useChromatic ) {
+ vboTexCoordsG = iVBO.addGLSLSubArray("svr_TexCoordG", 2, GL.GL_ARRAY_BUFFER);
+ vboTexCoordsB = iVBO.addGLSLSubArray("svr_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);
+
+ /** 2+2+2+2+2: { vec2 position, vec2 color, vec2 texCoordR, vec2 texCoordG, vec2 texCoordB } */
+ final FloatBuffer iVBOFB = (FloatBuffer)iVBO.getBuffer();
+
+ for ( int vertNum = 0; vertNum < vertexCount; vertNum++ ) {
+ final DistortionMesh.DistortionVertex v = meshData.vertices[vertNum];
+ int dataIdx = 0;
+
+ if( StereoDevice.DUMP_DATA ) {
+ System.err.println("XXX."+eyeName+": START VERTEX "+vertNum+" / "+vertexCount);
+ }
+ // pos
+ if( v.pos_size >= 2 ) {
+ if( StereoDevice.DUMP_DATA ) {
+ System.err.println("XXX."+eyeName+": pos ["+v.data[dataIdx]+", "+v.data[dataIdx+1]+"]");
+ }
+ iVBOFB.put(v.data[dataIdx]);
+ iVBOFB.put(v.data[dataIdx+1]);
+ } else {
+ iVBOFB.put(0f);
+ iVBOFB.put(0f);
+ }
+ dataIdx += v.pos_size;
+
+ // params
+ if( v.vignetteFactor_size >= 1 && useVignette ) {
+ if( StereoDevice.DUMP_DATA ) {
+ System.err.println("XXX."+eyeName+": vignette "+v.data[dataIdx]);
+ }
+ iVBOFB.put(v.data[dataIdx]);
+ } else {
+ iVBOFB.put(1.0f);
+ }
+ dataIdx += v.vignetteFactor_size;
+
+ if( v.timewarpFactor_size >= 1 ) {
+ if( StereoDevice.DUMP_DATA ) {
+ System.err.println("XXX."+eyeName+": timewarp "+v.data[dataIdx]);
+ }
+ iVBOFB.put(v.data[dataIdx]);
+ } else {
+ iVBOFB.put(1.0f);
+ }
+ dataIdx += v.timewarpFactor_size;
+
+ // texCoordR
+ if( v.texR_size >= 2 ) {
+ if( StereoDevice.DUMP_DATA ) {
+ System.err.println("XXX."+eyeName+": texR ["+v.data[dataIdx]+", "+v.data[dataIdx+1]+"]");
+ }
+ iVBOFB.put(v.data[dataIdx]);
+ iVBOFB.put(v.data[dataIdx+1]);
+ } else {
+ iVBOFB.put(1f);
+ iVBOFB.put(1f);
+ }
+ dataIdx += v.texR_size;
+
+ if( useChromatic ) {
+ // texCoordG
+ if( v.texG_size >= 2 ) {
+ if( StereoDevice.DUMP_DATA ) {
+ System.err.println("XXX."+eyeName+": texG ["+v.data[dataIdx]+", "+v.data[dataIdx+1]+"]");
+ }
+ iVBOFB.put(v.data[dataIdx]);
+ iVBOFB.put(v.data[dataIdx+1]);
+ } else {
+ iVBOFB.put(1f);
+ iVBOFB.put(1f);
+ }
+ dataIdx += v.texG_size;
+
+ // texCoordB
+ if( v.texB_size >= 2 ) {
+ if( StereoDevice.DUMP_DATA ) {
+ System.err.println("XXX."+eyeName+": texB ["+v.data[dataIdx]+", "+v.data[dataIdx+1]+"]");
+ }
+ iVBOFB.put(v.data[dataIdx]);
+ iVBOFB.put(v.data[dataIdx+1]);
+ } else {
+ iVBOFB.put(1f);
+ iVBOFB.put(1f);
+ }
+ dataIdx += v.texB_size;
+ } else {
+ dataIdx += v.texG_size;
+ dataIdx += v.texB_size;
+ }
+ }
+ if( StereoDevice.DUMP_DATA ) {
+ System.err.println("XXX."+eyeName+": iVBO "+iVBO);
+ }
+ {
+ if( StereoDevice.DUMP_DATA ) {
+ System.err.println("XXX."+eyeName+": idx "+indices+", count "+indexCount);
+ for(int i=0; i< indexCount; i++) {
+ if( 0 == i % 16 ) {
+ System.err.printf("%n%5d: ", i);
+ }
+ System.err.printf("%5d, ", (int)meshData.indices[i]);
+ }
+ System.err.println();
+ }
+ final ShortBuffer out = (ShortBuffer) indices.getBuffer();
+ out.put(meshData.indices, 0, meshData.indexCount);
+ }
+ if( StereoDevice.DEBUG ) {
+ System.err.println("XXX."+eyeName+": "+this);
+ }
+ }
+
+ private void linkData(final GL2ES2 gl, final ShaderProgram sp) {
+ if( null == iVBO ) return;
+
+ if( 0 > vboPos.setLocation(gl, sp.program()) ) {
+ throw new GLException("Couldn't locate "+vboPos);
+ }
+ if( 0 > vboParams.setLocation(gl, sp.program()) ) {
+ throw new GLException("Couldn't locate "+vboParams);
+ }
+ if( 0 > vboTexCoordsR.setLocation(gl, sp.program()) ) {
+ throw new GLException("Couldn't locate "+vboTexCoordsR);
+ }
+ if( StereoUtil.usesChromaticDistortion(distortionBits) ) {
+ if( 0 > vboTexCoordsG.setLocation(gl, sp.program()) ) {
+ throw new GLException("Couldn't locate "+vboTexCoordsG);
+ }
+ if( 0 > vboTexCoordsB.setLocation(gl, sp.program()) ) {
+ throw new GLException("Couldn't locate "+vboTexCoordsB);
+ }
+ }
+ if( 0 > eyeToSourceUVScale.setLocation(gl, sp.program()) ) {
+ throw new GLException("Couldn't locate "+eyeToSourceUVScale);
+ }
+ if( 0 > eyeToSourceUVOffset.setLocation(gl, sp.program()) ) {
+ throw new GLException("Couldn't locate "+eyeToSourceUVOffset);
+ }
+ if( StereoUtil.usesTimewarpDistortion(distortionBits) ) {
+ if( 0 > eyeRotationStart.setLocation(gl, sp.program()) ) {
+ throw new GLException("Couldn't locate "+eyeRotationStart);
+ }
+ if( 0 > eyeRotationEnd.setLocation(gl, sp.program()) ) {
+ throw new GLException("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) {
+ if( null == iVBO ) return;
+ iVBO.destroy(gl);
+ indices.destroy(gl);
+ }
+ private void enableVBO(final GL2ES2 gl, final boolean enable) {
+ if( null == iVBO ) return;
+ iVBO.enableBuffer(gl, enable);
+ indices.bindBuffer(gl, enable); // keeps VBO binding if enable:=true
+ }
+
+ private void updateUniform(final GL2ES2 gl, final ShaderProgram sp) {
+ if( null == iVBO ) return;
+ gl.glUniform(eyeToSourceUVScale);
+ gl.glUniform(eyeToSourceUVOffset);
+ if( StereoUtil.usesTimewarpDistortion(distortionBits) ) {
+ gl.glUniform(eyeRotationStart);
+ gl.glUniform(eyeRotationEnd);
+ }
+ }
+
+ /**
+ * 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 GenericStereoDevice hmdCtx) {
+ return eyePose;
+ }
+
+ @Override
+ public String toString() {
+ final String ppTxt = null == iVBO ? ", no post-processing" :
+ ", uvScale["+eyeToSourceUVScale.floatBufferValue().get(0)+", "+eyeToSourceUVScale.floatBufferValue().get(1)+
+ "], uvOffset["+eyeToSourceUVOffset.floatBufferValue().get(0)+", "+eyeToSourceUVOffset.floatBufferValue().get(1)+"]";
+
+ return "Eye["+eyeName+", viewport "+viewport+
+ ", "+eyeParameter+
+ ", vertices "+vertexCount+", indices "+indexCount+
+ ppTxt+
+ ", desc"+eyeParameter+", "+eyePose+"]";
+ }
+ }
+
+ private final GenericStereoDevice device;
+ private final GenericEye[] eyes;
+ private final int distortionBits;
+ private final int textureCount;
+ private final DimensionImmutable singleTextureSize;
+ private final DimensionImmutable totalTextureSize;
+ /** if texUnit0 is null: no post-processing */
+ private final GLUniformData texUnit0;
+
+ private ShaderProgram sp;
+ private long frameStart = 0;
+
+ @Override
+ public String toString() {
+ return "GenericStereo[distortion["+StereoUtil.distortionBitsToString(distortionBits)+
+ "], singleSize "+singleTextureSize+
+ ", sbsSize "+totalTextureSize+
+ ", texCount "+textureCount+", texUnit "+(null != texUnit0 ? texUnit0.intValue() : "n/a")+
+ ", "+PlatformPropsImpl.NEWLINE+" "+(0 < eyes.length ? eyes[0] : "none")+
+ ", "+PlatformPropsImpl.NEWLINE+" "+(1 < eyes.length ? eyes[1] : "none")+"]";
+ }
+
+
+ private static final DimensionImmutable zeroSize = new Dimension(0, 0);
+
+ /* pp */ GenericStereoDeviceRenderer(final GenericStereoDevice context, final int distortionBits,
+ final int textureCount, final float[] eyePositionOffset,
+ final EyeParameter[] eyeParam, final float pixelsPerDisplayPixel, final int textureUnit,
+ final DimensionImmutable singleTextureSize, final DimensionImmutable totalTextureSize,
+ final RectangleImmutable[] eyeViewports) {
+ this.device = context;
+ this.eyes = new GenericEye[eyeParam.length];
+ this.distortionBits = ( distortionBits | context.getMinimumDistortionBits() ) & context.getSupportedDistortionBits();
+ final boolean usePP = null != device.config.distortionMeshProducer && 0 != this.distortionBits;
+ final DimensionImmutable textureSize;
+
+ if( usePP ) {
+ if( 1 > textureCount || 2 < textureCount ) {
+ this.textureCount = 2;
+ } else {
+ this.textureCount = textureCount;
+ }
+ this.singleTextureSize = singleTextureSize;
+ this.totalTextureSize = totalTextureSize;
+ textureSize = 1 == textureCount ? totalTextureSize : singleTextureSize;
+ texUnit0 = new GLUniformData("svr_Texture0", textureUnit);
+ } else {
+ this.textureCount = 0;
+ this.singleTextureSize = zeroSize;
+ this.totalTextureSize = zeroSize;
+ textureSize = zeroSize;
+ texUnit0 = null;
+ }
+ for(int i=0; i<eyeParam.length; i++) {
+ eyes[i] = new GenericEye(context, this.distortionBits, eyePositionOffset, eyeParam[i], textureSize, eyeViewports[i]);
+ }
+ sp = null;
+ }
+
+ @Override
+ public StereoDevice getDevice() { return device; }
+
+ @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 ppAvailable() ? texUnit0.intValue() : 0; }
+
+ @Override
+ public final boolean ppAvailable() { return null != texUnit0; }
+
+ @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");
+ }
+ if( !ppAvailable() ) {
+ return;
+ }
+ 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, GenericStereoDeviceRenderer.class, "shader",
+ "shader/bin", vertexShaderBasename, true);
+ final ShaderCode fp0 = ShaderCode.create(gl2es2, GL2ES2.GL_FRAGMENT_SHADER, GenericStereoDeviceRenderer.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 GLException("Couldn't locate "+texUnit0);
+ }
+ for(int i=0; i<eyes.length; i++) {
+ eyes[i].linkData(gl2es2, sp);
+ }
+ sp.useProgram(gl2es2, false);
+ }
+
+ @Override
+ public final void dispose(final GL gl) {
+ final GL2ES2 gl2es2 = gl.getGL2ES2();
+ if( null != sp ) {
+ sp.useProgram(gl2es2, false);
+ }
+ for(int i=0; i<eyes.length; i++) {
+ eyes[i].dispose(gl2es2);
+ }
+ if( null != sp ) {
+ sp.destroy(gl2es2);
+ }
+ }
+
+ @Override
+ public final Eye getEye(final int eyeNum) {
+ return eyes[eyeNum];
+ }
+
+ @Override
+ public final EyePose updateEyePose(final int eyeNum) {
+ return eyes[eyeNum].updateEyePose(device);
+ }
+
+ @Override
+ public final void beginFrame(final GL gl) {
+ frameStart = Platform.currentTimeMillis();
+ }
+
+ @Override
+ public final void endFrame(final GL gl) {
+ if( 0 == frameStart ) {
+ throw new IllegalStateException("beginFrame not called");
+ }
+ frameStart = 0;
+ }
+
+ @Override
+ public final void ppBegin(final GL gl) {
+ if( null == sp ) {
+ throw new IllegalStateException("Not initialized");
+ }
+ if( 0 == frameStart ) {
+ throw new IllegalStateException("beginFrame not called");
+ }
+ 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 ppOneEye(final GL gl, final int eyeNum) {
+ final GenericEye eye = eyes[eyeNum];
+ 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);
+ }
+}