/**
   * Puts the input texture through the Program chain, returning the processed output Texture which
   * can then be placed on a Geom, etc.
   *
   * @param gl
   * @return A Texture that's been processed by the attached shader Programs
   */
  public Texture apply() {
    GL2 gl = getGL();
    // sanity check
    if (programs.size() == 0) {
      return inputTexture;
    }

    // if first time, generate FBO
    if (fboId < 0) {
      System.out.println("GENERATE...");
      if (!generateFBO(gl)) {
        System.err.println("couldn't generate FBO!");
        return null;
      }
    }

    // if first time, install shaders
    for (Program program : this.programs) {
      if (program.programId <= 0) {
        program.install();
      }
    }

    writeAttachment = GL_COLOR_ATTACHMENT0;
    readAttachment = GL_COLOR_ATTACHMENT1;

    readTextureId = texture2.getTextureObject();
    writeTextureId = texture1.getTextureObject();
    // readTexture = texture2;
    // writeTexture = texture1;

    // bind fbo
    gl.glBindFramebuffer(GL_FRAMEBUFFER, fboId);
    gl.glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
    gl.glViewport(0, 0, fboWidth, fboHeight);

    // use first program to process inputTexture into texture2
    drawTextureToOffScreenTextureUsingShader(
        inputTexture.getTextureObject(), writeAttachment, programs.get(0));

    swapPingPong(); // make the write texture our new *read* texture

    // loop through the rest of our shaders
    for (int i = 1; i < programs.size(); i++) {
      drawTextureToOffScreenTextureUsingShader(readTextureId, writeAttachment, programs.get(i));
      swapPingPong();
    }

    gl.glBindFramebuffer(GL_FRAMEBUFFER, 0);
    int www = RenderUtils.getViewport()[2];
    int hhh = RenderUtils.getViewport()[3];

    gl.glViewport(0, 0, www, hhh);

    if (readTextureId == texture1.getTextureObject()) {
      return texture1;
    } else {
      return texture2;
    }
  }
  public void drawTextureToOffScreenTextureUsingShader(int texId, int attachment, Program program)
        // public void drawTextureToOffScreenTextureUsingShader(Texture tex, int attachment, Program
        // program)
      {
    GL2 gl = getGL();

    program.bind();

    gl.glBindTexture(GL_TEXTURE_2D, texId);
    // gl.glBindTexture(GL_TEXTURE_2D, tex.getTextureObject());
    gl.glDrawBuffer(attachment);
    gl.glEnable(GL_TEXTURE_2D);
    // gl.glActiveTexture(GL_TEXTURE0);
    gl.glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

    gl.glViewport(0, 0, fboWidth, fboHeight);

    gl.glUniform1i(program.uniform("theTexture"), 0);
    // set projection to ortho
    gl.glMatrixMode(gl.GL_PROJECTION);

    gl.glPushMatrix();
    {
      gl.glLoadIdentity();
      Renderer.getInstance().glu.gluOrtho2D(0, fboWidth, fboHeight, 0);

      gl.glMatrixMode(gl.GL_MODELVIEW);

      gl.glPushMatrix();
      {
        gl.glLoadIdentity();

        gl.glColor4f(1f, 1f, 1f, 1f);
        drawSquare(gl, 0, 0, fboWidth, fboHeight);
      }
      gl.glPopMatrix();

      gl.glMatrixMode(gl.GL_PROJECTION);
    }
    gl.glPopMatrix();

    gl.glMatrixMode(gl.GL_MODELVIEW);

    gl.glDisable(GL_TEXTURE_2D);
    program.unbind();
  }