// This code example is created for educational purpose
// by Thorsten Thormaehlen (contact: www.thormae.de).
// It is distributed without any warranty.

import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.nio.ByteBuffer;

import com.jogamp.opengl.GL2;
import com.jogamp.opengl.GLAutoDrawable;
import com.jogamp.opengl.GLCapabilities;
import com.jogamp.opengl.GLEventListener;
import com.jogamp.opengl.GLProfile;
import com.jogamp.opengl.awt.GLCanvas;
import com.jogamp.opengl.glu.GLU;
import javax.swing.JFrame;

import com.jogamp.opengl.util.FPSAnimator;

class Renderer {

  private GLU glu = new GLU();

  public float t = 0.0f;
  public int mode = 1;

  private int[] fboID = {0};
  private int[] depthID = {0};
  private int fboTexSize = 512;
  private int width = 0;
  private int height = 0;
  
  //private int[] vertBufID = new int[1];

  public void init(GLAutoDrawable d) {
    GL2 gl = d.getGL().getGL2(); // get the OpenGL graphics context
    gl.glEnable(GL2.GL_DEPTH_TEST);

    // create a frame buffer object
    gl.glGenFramebuffers(1, fboID, 0);
    
    // bind the frame buffer
    gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, fboID[0]);

    // Generate render texture
    // this is not used (but some drivers require it)
    int[] texID = {0};
    gl.glGenTextures (1, texID, 0);
    gl.glBindTexture(GL2.GL_TEXTURE_2D, texID[0]);

    gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL2.GL_LINEAR);
    gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER, GL2.GL_LINEAR);
    gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_S, GL2.GL_CLAMP_TO_EDGE);
    gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_T, GL2.GL_CLAMP_TO_EDGE);
	
    int level = 0;
    ByteBuffer fakeColorBuffer = ByteBuffer.allocateDirect(fboTexSize * fboTexSize * 4);
    gl.glTexImage2D(GL2.GL_TEXTURE_2D, level, GL2.GL_RGBA, fboTexSize, fboTexSize, 0, GL2.GL_RGBA, GL2.GL_UNSIGNED_BYTE, fakeColorBuffer);
    gl.glTexEnvf(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_REPLACE);

    // Attach the texture to the fbo
    gl.glFramebufferTexture2D(GL2.GL_FRAMEBUFFER, GL2.GL_COLOR_ATTACHMENT0, GL2.GL_TEXTURE_2D, texID[0], level);
    if(!checkFramebufferStatus(d)) System.exit(1);
	
    // Generate depth render texture
    gl.glGenTextures (1, depthID, 0);
    gl.glBindTexture(GL2.GL_TEXTURE_2D, depthID[0]);

    gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL2.GL_LINEAR);
    gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER, GL2.GL_LINEAR);
    gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_S, GL2.GL_CLAMP_TO_EDGE);
    gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_T, GL2.GL_CLAMP_TO_EDGE);
    
    ByteBuffer fakeDepthBuffer = ByteBuffer.allocateDirect(fboTexSize * fboTexSize);
    gl.glTexImage2D(GL2.GL_TEXTURE_2D, level, GL2.GL_DEPTH_COMPONENT, fboTexSize, fboTexSize, 0, GL2.GL_DEPTH_COMPONENT, GL2.GL_UNSIGNED_BYTE, fakeDepthBuffer);
    gl.glTexEnvf(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_REPLACE);

    // Attach the texture to the fbo
    gl.glFramebufferTexture2D(GL2.GL_FRAMEBUFFER, GL2.GL_DEPTH_ATTACHMENT, GL2.GL_TEXTURE_2D, depthID[0], level);
    if(!checkFramebufferStatus(d)) System.exit(1);

    // unbind texture
    gl.glBindTexture(GL2.GL_TEXTURE_2D, 0);
    //unbind fbo
    gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, 0);
  }

  public void resize(GLAutoDrawable d, int w, int h) {
    width = w;
    height = h;
    changeViewport(d, w, h);
  }

  private void changeViewport(GLAutoDrawable d, int w, int h) {
    GL2 gl = d.getGL().getGL2();  // get the OpenGL 2 graphics context
    gl.glViewport(0, 0, w, h);
    gl.glMatrixMode(GL2.GL_PROJECTION);
    gl.glLoadIdentity();
    glu.gluPerspective (30.0, (float)w/(float)h, 7.0, 12.0);
  }
  
  private void changeViewportOrtho(GLAutoDrawable d, int w, int h) {
    GL2 gl = d.getGL().getGL2();  // get the OpenGL 2 graphics context
    float aspect = (float)w/(float)h;
    gl.glViewport(0, 0, width, height);
    gl.glMatrixMode(GL2.GL_PROJECTION);
    gl.glLoadIdentity();
    gl.glOrtho(-1.0*aspect, 1.0*aspect, -1.0, 1.0, -1.0, 1.0);
    gl.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT);
    gl.glMatrixMode(GL2.GL_MODELVIEW);
    gl.glLoadIdentity();
  }
  
  public void display(GLAutoDrawable d) {
    GL2 gl = d.getGL().getGL2();  // get the OpenGL 2 graphics context
    
    if(mode > 0) {
      // bind the fbo
      gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, fboID[0]);
      changeViewport(d, fboTexSize, fboTexSize);
    }else{
      changeViewport(d, width, height);
    }

    gl.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    // render to attached fbo
    drawScene(d);

    if(mode == 1) {
      // unbind frame buffer
      gl.glBindFramebuffer(GL2.GL_FRAMEBUFFER, 0);

      changeViewportOrtho(d, width, height);

      // using first render pass result as texture
      gl.glEnable(GL2.GL_TEXTURE_2D);
      gl.glBindTexture(GL2.GL_TEXTURE_2D, depthID[0]);

      //draw quad with depth texture
      gl.glColor3f(1.0f,0.0f,0.0f);
      gl.glBegin(GL2.GL_POLYGON);
      gl.glTexCoord2f(0.00f,0.00f); gl.glVertex3f(-1.0f,-1.0f, 0.0f);
      gl.glTexCoord2f(1.00f,0.00f); gl.glVertex3f( 1.0f,-1.0f, 0.0f);
      gl.glTexCoord2f(1.00f,1.00f); gl.glVertex3f( 1.0f, 1.0f, 0.0f);
      gl.glTexCoord2f(0.00f,1.00f); gl.glVertex3f(-1.0f, 1.0f, 0.0f);
      gl.glEnd();

      gl.glDisable(GL2.GL_TEXTURE_2D);
    }  
    
    gl.glFlush();
  }
  
  // returns a valid textureID on success, otherwise 0
  private void drawScene(GLAutoDrawable d) {
    GL2 gl = d.getGL().getGL2(); // get the OpenGL 2 graphics context

    gl.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT);
    gl.glMatrixMode(GL2.GL_MODELVIEW);
    gl.glLoadIdentity();
    // set camera
    glu.gluLookAt(8.0, -2.0, 5.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0);
    // draw scene
    gl.glRotatef(t, 0.0f, 0.0f, 1.0f);
    drawTexturedCube(d);
  }
  
  private boolean checkFramebufferStatus(GLAutoDrawable d) {

    GL2 gl = d.getGL().getGL2(); // get the OpenGL 2 graphics context
    
    int status = gl.glCheckFramebufferStatus(GL2.GL_FRAMEBUFFER);
    switch(status) {
    case GL2.GL_FRAMEBUFFER_COMPLETE:
      return true;
    case GL2.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: 
      throw new RuntimeException("Framebuffer incomplete, incomplete attachment");
    case GL2.GL_FRAMEBUFFER_UNSUPPORTED:
      throw new RuntimeException("Unsupported framebuffer format");
    case GL2.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
      throw new RuntimeException("Framebuffer incomplete, missing attachment");
    case GL2.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
      throw new RuntimeException("Framebuffer incomplete, missing draw buffer");
    case GL2.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: 
      throw new RuntimeException("Framebuffer incomplete, missing read buffer");
    }
    return false;
  }
  
  private void drawTexturedCube(GLAutoDrawable d) {
    GL2 gl = d.getGL().getGL2(); // get the OpenGL 2 graphics context
    gl.glColor3f(1.0f,0.0f,0.0f);
    gl.glBegin(GL2.GL_POLYGON);
    gl.glTexCoord2f(0.00f,0.00f); gl.glVertex3f(-1.0f, 1.0f, 1.0f);
    gl.glTexCoord2f(1.00f,0.00f); gl.glVertex3f(-1.0f,-1.0f, 1.0f);
    gl.glTexCoord2f(1.00f,1.00f); gl.glVertex3f( 1.0f,-1.0f, 1.0f);
    gl.glTexCoord2f(0.00f,1.00f); gl.glVertex3f( 1.0f, 1.0f, 1.0f);
    gl.glEnd();
    gl.glColor3f(1.0f,1.0f,0.0f);
    gl.glBegin(GL2.GL_POLYGON);
    gl.glTexCoord2f(0.00f,0.00f); gl.glVertex3f(-1.0f, 1.0f,-1.0f);
    gl.glTexCoord2f(1.00f,0.00f); gl.glVertex3f(-1.0f,-1.0f,-1.0f);
    gl.glTexCoord2f(1.00f,1.00f); gl.glVertex3f(-1.0f,-1.0f, 1.0f);
    gl.glTexCoord2f(0.00f,1.00f); gl.glVertex3f(-1.0f, 1.0f, 1.0f);
    gl.glEnd();
    gl.glColor3f(0.0f,0.0f,1.0f);
    gl.glBegin(GL2.GL_POLYGON);
    gl.glTexCoord2f(0.00f,0.00f); gl.glVertex3f( 1.0f, 1.0f, 1.0f);
    gl.glTexCoord2f(1.00f,0.00f); gl.glVertex3f( 1.0f,-1.0f, 1.0f);
    gl.glTexCoord2f(1.00f,1.00f); gl.glVertex3f( 1.0f,-1.0f,-1.0f);
    gl.glTexCoord2f(0.00f,1.00f); gl.glVertex3f( 1.0f, 1.0f,-1.0f);
    gl.glEnd();
    gl.glColor3f(1.0f,0.0f,1.0f);
    gl.glBegin(GL2.GL_POLYGON);
    gl.glTexCoord2f(0.00f,0.00f); gl.glVertex3f(-1.0f, 1.0f,-1.0f);
    gl.glTexCoord2f(1.00f,0.00f); gl.glVertex3f(-1.0f, 1.0f, 1.0f);
    gl.glTexCoord2f(1.00f,1.00f); gl.glVertex3f( 1.0f, 1.0f, 1.0f);
    gl.glTexCoord2f(0.00f,1.00f); gl.glVertex3f( 1.0f, 1.0f,-1.0f);
    gl.glEnd();
    gl.glColor3f(0.0f,1.0f,1.0f);
    gl.glBegin(GL2.GL_POLYGON);
    gl.glTexCoord2f(0.00f,0.00f); gl.glVertex3f(-1.0f, -1.0f, 1.0f);
    gl.glTexCoord2f(1.00f,0.00f); gl.glVertex3f(-1.0f, -1.0f,-1.0f);
    gl.glTexCoord2f(1.00f,1.00f); gl.glVertex3f( 1.0f, -1.0f,-1.0f);
    gl.glTexCoord2f(0.00f,1.00f); gl.glVertex3f( 1.0f, -1.0f, 1.0f);
    gl.glEnd();
    gl.glColor3f(0.0f,1.0f,0.0f);
    gl.glBegin(GL2.GL_POLYGON);
    gl.glTexCoord2f(0.00f,0.00f); gl.glVertex3f( 1.0f, 1.0f,-1.0f);
    gl.glTexCoord2f(1.00f,0.00f); gl.glVertex3f( 1.0f,-1.0f,-1.0f);
    gl.glTexCoord2f(1.00f,1.00f); gl.glVertex3f(-1.0f,-1.0f,-1.0f);
    gl.glTexCoord2f(0.00f,1.00f); gl.glVertex3f(-1.0f, 1.0f,-1.0f);
    gl.glEnd();
  }
}

class MyGui extends JFrame implements GLEventListener {

  private Renderer renderer;

  public void createGUI() {
    setTitle("Press 1 to toggle between first/second render pass");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    GLProfile glp = GLProfile.getDefault();
    GLCapabilities caps = new GLCapabilities(glp);
    GLCanvas canvas = new GLCanvas(caps);
    setSize(320, 320);

    getContentPane().add(canvas);
    final FPSAnimator ani = new FPSAnimator(canvas, 60, true);
    canvas.addGLEventListener(this);
    setVisible(true);
    renderer = new Renderer();
    
    canvas.addKeyListener(new KeyAdapter() {
      public void keyPressed(KeyEvent event) {
        boolean redraw = false;
        String modeStr = "";
        switch(event.getKeyCode()) {
          case '1':
            if(renderer.mode == 1) {
              renderer.mode = 0;
              modeStr = "first render pass result";
            } else {
              renderer.mode = 1;
              modeStr = "second render pass result";
            }
            redraw = true;
            break;
        }
        if(redraw) { // done automatically
          setTitle(modeStr);
        }
      }
    });
    
    
    ani.start();
  }

  @Override
  public void init(GLAutoDrawable d) {
    renderer.init(d);
  }

  @Override
  public void reshape(GLAutoDrawable d, int x, int y, int width, int height) {
    renderer.resize(d, width, height);
  }

  @Override
  public void display(GLAutoDrawable d) {
    float offset = 1.0f;
    renderer.t += offset;
    renderer.display(d);
  }

  @Override
  public void dispose(GLAutoDrawable d) {
  }
}

public class FboDepth {
  public static void main(String[] args) {
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        MyGui myGUI = new MyGui();
        myGUI.createGUI();
      }
    });
  }
}
