/** * 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 java.util.Arrays; import com.jogamp.nativewindow.util.Dimension; import com.jogamp.nativewindow.util.DimensionImmutable; import com.jogamp.nativewindow.util.RectangleImmutable; import com.jogamp.opengl.GL; import com.jogamp.opengl.GL2ES2; import com.jogamp.opengl.GLArrayData; import com.jogamp.opengl.GLException; import com.jogamp.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[] eyeTextureSizes; 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)+ "], eyeTexSize "+Arrays.toString(eyeTextureSizes)+ ", 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[] eyeTextureSizes, final DimensionImmutable totalTextureSize, final RectangleImmutable[] eyeViewports) { if( eyeParam.length != eyeTextureSizes.length || eyeParam.length != eyeViewports.length ) { throw new IllegalArgumentException("eye arrays of different length"); } 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[] textureSizes; if( usePP ) { if( 1 > textureCount || 2 < textureCount ) { this.textureCount = 2; } else { this.textureCount = textureCount; } this.eyeTextureSizes = eyeTextureSizes; this.totalTextureSize = totalTextureSize; if( 1 == textureCount ) { textureSizes = new DimensionImmutable[eyeParam.length]; for(int i=0; i texUnit0.setLocation(gl2es2, sp.program()) ) { throw new GLException("Couldn't locate "+texUnit0); } for(int i=0; i