src/gl/fbo.c

    #include <stdlib.h>
    #include <stdio.h>
    #include <stdbool.h>
    #include <assert.h>
    #include "system-gl.h"
    
    #include "fbo.h"
    
    fbo_t * fbo_new()
    {
      fbo_t * fbo = malloc (sizeof(fbo_t));
      fbo->fbo_id = 0;
      fbo->old_fbo_id = 0;
      fbo->renderbuf_id = 0;
      fbo->depthbuf_id = 0;
      return fbo;
    }
    
    bool use_ext()
    {
      // do we need to use the EXT or ARB version?
      return (!glewIsSupported("GL_ARB_framebuffer_object") &&
    	  glewIsSupported("GL_EXT_framebuffer_object"));
    }
    
    bool check_fbo_status()
    {
      /* This code is based on user V-man code from
         http://www.opengl.org/wiki/GL_EXT_framebuffer_multisample
         See also: http://www.songho.ca/opengl/gl_fbo.html */
      GLenum status;
      bool result = false;
      if (use_ext()) {
        status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
      }
      else {
        status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
      }
    
      if (report_glerror("checking framebuffer status")) return false;
    
      if (status == GL_FRAMEBUFFER_COMPLETE) {
        result = true;
      }
      else if (status == GL_FRAMEBUFFER_UNSUPPORTED) {
        fprintf (stderr, "GL_FRAMEBUFFER_UNSUPPORTED\n");
      }
      else if (status == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) {
        fprintf (stderr, "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT\n");
      }
      else if (status == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) {
        fprintf (stderr, "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT\n");
      }
      else if (status == GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT) {
        fprintf (stderr, "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT\n");
      }
      else if (status == GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT) {
        fprintf (stderr, "GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT\n");
      }
      else if (status == GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT) {
        fprintf (stderr, "GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT\n");
      }
      else if (status == GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT) {
        fprintf (stderr, "GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT\n");
      }
      else if (status == GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT) {
        fprintf (stderr, "GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT\n");
      }
      else {
        fprintf (stderr, "Unknown Code: glCheckFramebufferStatusEXT returned: %d\n",
    	     status);
      }
      return result;
    }
    
    bool fbo_ext_init(fbo_t *fbo, size_t width, size_t height)
    {
      // Generate and bind FBO
      glGenFramebuffersEXT(1, &fbo->fbo_id);
      if (report_glerror("glGenFramebuffersEXT")) return false;
      glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo->fbo_id);
      if (report_glerror("glBindFramebufferEXT")) return false;
    
      // Generate depth and render buffers
      glGenRenderbuffersEXT(1, &fbo->depthbuf_id);
      glGenRenderbuffersEXT(1, &fbo->renderbuf_id);
    
      // Create buffers with correct size
      if (!fbo_resize(fbo, width, height)) return false;
    
      // Attach render and depth buffers
      glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, 
                                   GL_RENDERBUFFER_EXT, fbo->renderbuf_id);
      if (report_glerror("specifying color render buffer EXT")) return false;
    
    
      if (!check_fbo_status()) {
        fprintf (stderr, "Problem with OpenGL EXT framebuffer"
    	     " after specifying color render buffer.\n");
        return false;
      }
    
      if (glewIsSupported("GL_EXT_packed_depth_stencil")) {
        glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
    				 GL_RENDERBUFFER_EXT, fbo->depthbuf_id);
        if (report_glerror("specifying depth render buffer EXT")) return false;
    
        glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT,
    				 GL_RENDERBUFFER_EXT, fbo->depthbuf_id);
        if (report_glerror("specifying stencil render buffer EXT")) return false;
    		
        if (!check_fbo_status()) {
          fprintf (stderr,
    	       "Problem with OpenGL EXT framebuffer after"
    	       " specifying depth render buffer.\n");
          return false;
        }
      }
      else {
        fprintf (stderr,
    	     "Warning: Cannot create stencil buffer "
    	     "(GL_EXT_packed_depth_stencil not supported)\n");
        glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, 
    				 GL_RENDERBUFFER_EXT, fbo->depthbuf_id);
        if (report_glerror("specifying depth render buffer EXT")) return false;
    		
        if (!check_fbo_status()) {
          fprintf (stderr,
    	       "Problem with OpenGL EXT framebuffer "
    	       "after specifying depth stencil render buffer.\n");
          return false;
        }
      }
    
      return true;
    }
    
    bool fbo_arb_init(fbo_t *fbo, size_t width, size_t height)
    {
      // Generate and bind FBO
      glGenFramebuffers(1, &fbo->fbo_id);
      if (report_glerror("glGenFramebuffers")) return false;
      glBindFramebuffer(GL_FRAMEBUFFER, fbo->fbo_id);
      if (report_glerror("glBindFramebuffer")) return false;
    
      // Generate depth and render buffers
      glGenRenderbuffers(1, &fbo->depthbuf_id);
      glGenRenderbuffers(1, &fbo->renderbuf_id);
    
      // Create buffers with correct size
      if (!fbo_resize(fbo, width, height)) return false;
    
      // Attach render and depth buffers
      glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 
    			    GL_RENDERBUFFER, fbo->renderbuf_id);
      if (report_glerror("specifying color render buffer")) return false;
    
      if (!check_fbo_status()) {
        fprintf (stderr, "Problem with OpenGL framebuffer after "
    	     "specifying color render buffer.\n");
        return false;
      }
    
      //glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, 
      // to prevent Mesa's software renderer from crashing, do this in two stages. 
      // ie. instead of using GL_DEPTH_STENCIL_ATTACHMENT, do DEPTH then STENCIL. 
      glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
    			    GL_RENDERBUFFER, fbo->depthbuf_id);
      glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, 
    			    GL_RENDERBUFFER, fbo->depthbuf_id);
      if (report_glerror("specifying depth stencil render buffer")) return false;
    
      if (!check_fbo_status()) {
        fprintf (stderr, "Problem with OpenGL framebuffer after "
    	     "specifying depth render buffer.\n");
        return false;
      }
    
      return true;
    }
    
    
    bool fbo_init(fbo_t *fbo, size_t width, size_t height)
    {
      /*
        Some OpenGL drivers include the framebuffer functions but not with
        core or ARB names, only with the EXT name. This has been worked-around
        by deciding at runtime, using GLEW, which version needs to be used. See also:
      
        http://www.opengl.org/wiki/Framebuffer_Object
        http://stackoverflow.com/questions/6912988/glgenframebuffers-or-glgenframebuffersex
        http://www.devmaster.net/forums/showthread.php?t=10967
      */
    
      bool result = false;
      if (glewIsSupported("GL_ARB_framebuffer_object")) {
        result = fbo_arb_init(fbo, width, height);
      }
      else if (use_ext()) {
        result = fbo_ext_init(fbo, width, height);
      }
      else {
        fprintf (stderr, "Framebuffer Object extension not found by GLEW\n");
      }
      return result;
    }
    
    bool fbo_resize(fbo_t *fbo, size_t width, size_t height)
    {
      if (use_ext()) {
        glBindRenderbufferEXT(GL_RENDERBUFFER, fbo->depthbuf_id);
        if (glewIsSupported("GL_EXT_packed_depth_stencil")) {
          glRenderbufferStorageEXT(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
          if (report_glerror("creating EXT depth stencil render buffer")) return false;
        }
        else {
          glRenderbufferStorageEXT(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height);
          if (report_glerror("creating EXT depth render buffer")) return false;
        }
    
        glBindRenderbufferEXT(GL_RENDERBUFFER, fbo->renderbuf_id);
        glRenderbufferStorageEXT(GL_RENDERBUFFER, GL_RGBA8, width, height);
        if (report_glerror("creating EXT color render buffer")) return false;
      } else {
        glBindRenderbuffer(GL_RENDERBUFFER, fbo->renderbuf_id);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height);
        if (report_glerror("creating color render buffer")) return false;
    
        glBindRenderbuffer(GL_RENDERBUFFER, fbo->depthbuf_id);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
        if (report_glerror("creating depth stencil render buffer")) return false;
    
      }
    
      return true;
    }
    
    void fbo_delete(fbo_t *fbo)
    {
      free (fbo);
    }
    
    GLuint fbo_bind(fbo_t *fbo)
    {
      glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *)(&fbo->old_fbo_id));
      if (use_ext()) {
        glBindFramebufferEXT(GL_FRAMEBUFFER, fbo->fbo_id);
      }
      else {
        glBindFramebuffer(GL_FRAMEBUFFER, fbo->fbo_id);
      }
      return fbo->old_fbo_id;
    }
    
    void fbo_unbind(fbo_t *fbo)
    {
      if (use_ext()) {
        glBindFramebufferEXT(GL_FRAMEBUFFER, fbo->old_fbo_id);
      }
      else {
        glBindFramebuffer(GL_FRAMEBUFFER, fbo->old_fbo_id);
      }
    }
    
    bool report_glerror(const char * function)
    {
      GLenum tGLErr = glGetError();
      if (tGLErr != GL_NO_ERROR) {
        fprintf (stderr, "OpenGL error %04x after %s\n", tGLErr, function);
        return true;
      }
      return false;
    }