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);
    }
  @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);
  }