/*
 * Cogl
 *
 * An object oriented GL/GLES Abstraction/Utility Layer
 *
 * Copyright (C) 2007,2008,2009 Intel Corporation.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 *
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "cogl.h"
#include "cogl-debug.h"
#include "cogl-internal.h"
#include "cogl-context.h"
#include "cogl-handle.h"
#include "cogl-object-private.h"
#include "cogl-util.h"
#include "cogl-texture-private.h"
#include "cogl-framebuffer-private.h"
#include "cogl-clip-stack.h"
#include "cogl-journal-private.h"

#ifdef HAVE_COGL_GLES2

#include "../gles/cogl-gles2-wrapper.h"

#else

#define glGenRenderbuffers                ctx->drv.pf_glGenRenderbuffers
#define glDeleteRenderbuffers             ctx->drv.pf_glDeleteRenderbuffers
#define glBindRenderbuffer                ctx->drv.pf_glBindRenderbuffer
#define glRenderbufferStorage             ctx->drv.pf_glRenderbufferStorage
#define glGenFramebuffers                 ctx->drv.pf_glGenFramebuffers
#define glBindFramebuffer                 ctx->drv.pf_glBindFramebuffer
#define glFramebufferTexture2D            ctx->drv.pf_glFramebufferTexture2D
#define glFramebufferRenderbuffer         ctx->drv.pf_glFramebufferRenderbuffer
#define glCheckFramebufferStatus          ctx->drv.pf_glCheckFramebufferStatus
#define glDeleteFramebuffers              ctx->drv.pf_glDeleteFramebuffers
#define glGetFramebufferAttachmentParameteriv \
                                          ctx->drv.pf_glGetFramebufferAttachmentParameteriv

#endif

#ifndef GL_FRAMEBUFFER
#define GL_FRAMEBUFFER		0x8D40
#endif
#ifndef GL_RENDERBUFFER
#define GL_RENDERBUFFER		0x8D41
#endif
#ifndef GL_STENCIL_ATTACHMENT
#define GL_STENCIL_ATTACHMENT	0x8D00
#endif
#ifndef GL_COLOR_ATTACHMENT0
#define GL_COLOR_ATTACHMENT0	0x8CE0
#endif
#ifndef GL_FRAMEBUFFER_COMPLETE
#define GL_FRAMEBUFFER_COMPLETE 0x8CD5
#endif
#ifndef GL_STENCIL_INDEX8
#define GL_STENCIL_INDEX8       0x8D48
#endif
#ifndef GL_DEPTH_STENCIL
#define GL_DEPTH_STENCIL        0x84F9
#endif
#ifndef GL_DEPTH_ATTACHMENT
#define GL_DEPTH_ATTACHMENT     0x8D00
#endif
#ifndef GL_DEPTH_COMPONENT16
#define GL_DEPTH_COMPONENT16    0x81A5
#endif
#ifndef GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE
#define GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE      0x8212
#endif
#ifndef GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE
#define GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE    0x8213
#endif
#ifndef GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE
#define GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE     0x8214
#endif
#ifndef GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE
#define GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE    0x8215
#endif
#ifndef GL_FRAMEBUFFER_ATTCHMENT_DEPTH_SIZE
#define GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE    0x8216
#endif
#ifndef GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE
#define GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE  0x8217
#endif

typedef enum {
  _TRY_DEPTH_STENCIL = 1L<<0,
  _TRY_DEPTH         = 1L<<1,
  _TRY_STENCIL       = 1L<<2
} TryFBOFlags;

static void _cogl_framebuffer_free (CoglFramebuffer *framebuffer);
static void _cogl_onscreen_free (CoglOnscreen *onscreen);
static void _cogl_offscreen_free (CoglOffscreen *offscreen);

COGL_OBJECT_INTERNAL_DEFINE (Onscreen, onscreen);
COGL_OBJECT_DEFINE (Offscreen, offscreen);
COGL_OBJECT_DEFINE_DEPRECATED_REF_COUNTING (offscreen);

/* XXX:
 * The CoglObject macros don't support any form of inheritance, so for
 * now we implement the CoglObject support for the CoglFramebuffer
 * abstract class manually.
 */

gboolean
_cogl_is_framebuffer (void *object)
{
  CoglHandleObject *obj = object;

  if (obj == NULL)
    return FALSE;

  return obj->klass->type == _cogl_handle_onscreen_get_type ()
    || obj->klass->type == _cogl_handle_offscreen_get_type ();
}

static void
_cogl_framebuffer_init (CoglFramebuffer *framebuffer,
                        CoglFramebufferType type,
                        int width,
                        int height)
{
  framebuffer->type             = type;
  framebuffer->width            = width;
  framebuffer->height           = height;
  framebuffer->viewport_x       = 0;
  framebuffer->viewport_y       = 0;
  framebuffer->viewport_width   = width;
  framebuffer->viewport_height  = height;

  framebuffer->modelview_stack  = _cogl_matrix_stack_new ();
  framebuffer->projection_stack = _cogl_matrix_stack_new ();

  framebuffer->dirty_bitmasks   = TRUE;

  /* Initialise the clip stack */
  _cogl_clip_state_init (&framebuffer->clip_state);
}

void
_cogl_framebuffer_free (CoglFramebuffer *framebuffer)
{
  _cogl_clip_state_destroy (&framebuffer->clip_state);

  _cogl_matrix_stack_destroy (framebuffer->modelview_stack);
  framebuffer->modelview_stack = NULL;

  _cogl_matrix_stack_destroy (framebuffer->projection_stack);
  framebuffer->projection_stack = NULL;
}

int
_cogl_framebuffer_get_width (CoglFramebuffer *framebuffer)
{
  return framebuffer->width;
}

int
_cogl_framebuffer_get_height (CoglFramebuffer *framebuffer)
{
  return framebuffer->height;
}

CoglClipState *
_cogl_framebuffer_get_clip_state (CoglFramebuffer *framebuffer)
{
  return &framebuffer->clip_state;
}

void
_cogl_framebuffer_set_viewport (CoglFramebuffer *framebuffer,
                                int x,
                                int y,
                                int width,
                                int height)
{
  _COGL_GET_CONTEXT (ctx, NO_RETVAL);

  if (framebuffer->viewport_x == x &&
      framebuffer->viewport_y == y &&
      framebuffer->viewport_width == width &&
      framebuffer->viewport_height == height)
    return;

  _cogl_journal_flush ();

  framebuffer->viewport_x = x;
  framebuffer->viewport_y = y;
  framebuffer->viewport_width = width;
  framebuffer->viewport_height = height;

  if (_cogl_get_framebuffer () == framebuffer)
    ctx->dirty_gl_viewport = TRUE;
}

int
_cogl_framebuffer_get_viewport_x (CoglFramebuffer *framebuffer)
{
  return framebuffer->viewport_x;
}

int
_cogl_framebuffer_get_viewport_y (CoglFramebuffer *framebuffer)
{
  return framebuffer->viewport_y;
}

int
_cogl_framebuffer_get_viewport_width (CoglFramebuffer *framebuffer)
{
  return framebuffer->viewport_width;
}

int
_cogl_framebuffer_get_viewport_height (CoglFramebuffer *framebuffer)
{
  return framebuffer->viewport_height;
}

void
_cogl_framebuffer_get_viewport4fv (CoglFramebuffer *framebuffer, int *viewport)
{
  viewport[0] = framebuffer->viewport_x;
  viewport[1] = framebuffer->viewport_y;
  viewport[2] = framebuffer->viewport_width;
  viewport[3] = framebuffer->viewport_height;
}

CoglMatrixStack *
_cogl_framebuffer_get_modelview_stack (CoglFramebuffer *framebuffer)
{
  return framebuffer->modelview_stack;
}

CoglMatrixStack *
_cogl_framebuffer_get_projection_stack (CoglFramebuffer *framebuffer)
{
  return framebuffer->projection_stack;
}

static inline void
_cogl_framebuffer_init_bits (CoglFramebuffer *framebuffer)
{
  _COGL_GET_CONTEXT (ctx, NO_RETVAL);

  if (G_LIKELY (!framebuffer->dirty_bitmasks))
    return;

#ifdef HAVE_COGL_GL
  if (cogl_features_available (COGL_FEATURE_OFFSCREEN)
      && framebuffer->type == COGL_FRAMEBUFFER_TYPE_OFFSCREEN)
    {
      GLenum attachment, pname;

      attachment = GL_COLOR_ATTACHMENT0;

      pname = GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE;
      GE( glGetFramebufferAttachmentParameteriv (GL_FRAMEBUFFER,
                                                 attachment,
                                                 pname,
                                                 &framebuffer->red_bits) );

      pname = GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE;
      GE( glGetFramebufferAttachmentParameteriv (GL_FRAMEBUFFER,
                                                 attachment,
                                                 pname,
                                                 &framebuffer->green_bits) );

      pname = GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE;
      GE( glGetFramebufferAttachmentParameteriv (GL_FRAMEBUFFER,
                                                 attachment,
                                                 pname,
                                                 &framebuffer->blue_bits) );

      pname = GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE;
      GE( glGetFramebufferAttachmentParameteriv (GL_FRAMEBUFFER,
                                                 attachment,
                                                 pname,
                                                 &framebuffer->alpha_bits) );
    }
  else
#endif /* HAVE_COGL_GL */
    {
      GE( glGetIntegerv (GL_RED_BITS,   &framebuffer->red_bits)   );
      GE( glGetIntegerv (GL_GREEN_BITS, &framebuffer->green_bits) );
      GE( glGetIntegerv (GL_BLUE_BITS,  &framebuffer->blue_bits)  );
      GE( glGetIntegerv (GL_ALPHA_BITS, &framebuffer->alpha_bits) );
    }


  COGL_NOTE (OFFSCREEN,
             "RGBA Bits for framebuffer[%p, %s]: %d, %d, %d, %d",
             framebuffer,
             framebuffer->type == COGL_FRAMEBUFFER_TYPE_OFFSCREEN
               ? "offscreen"
               : "onscreen",
             framebuffer->red_bits,
             framebuffer->blue_bits,
             framebuffer->green_bits,
             framebuffer->alpha_bits);

  framebuffer->dirty_bitmasks = FALSE;
}

static gboolean
try_creating_fbo (CoglOffscreen *offscreen,
                  TryFBOFlags flags,
                  CoglHandle texture)
{
  GLuint gl_depth_stencil_handle;
  GLuint gl_depth_handle;
  GLuint gl_stencil_handle;
  GLuint tex_gl_handle;
  GLenum tex_gl_target;
  GLuint fbo_gl_handle;
  GLenum status;

  _COGL_GET_CONTEXT (ctx, FALSE);

  if (!cogl_texture_get_gl_texture (texture, &tex_gl_handle, &tex_gl_target))
    return FALSE;

  if (tex_gl_target != GL_TEXTURE_2D
#ifdef HAVE_COGL_GL
      && tex_gl_target != GL_TEXTURE_RECTANGLE_ARB
#endif
      )
    return FALSE;

  /* We are about to generate and bind a new fbo, so we pretend to change framebuffer
   * state so that the old framebuffer will be rebound again before drawing.
   * The framebuffer state can't be changed while their are active entries, so flush
   * first. */
  _cogl_journal_flush ();
  ctx->dirty_bound_framebuffer = 1;

  /* Generate framebuffer */
  glGenFramebuffers (1, &fbo_gl_handle);
  GE (glBindFramebuffer (GL_FRAMEBUFFER, fbo_gl_handle));
  offscreen->fbo_handle = fbo_gl_handle;

  GE (glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
			      tex_gl_target, tex_gl_handle, 0));

  if (flags & _TRY_DEPTH_STENCIL)
    {
      /* Create a renderbuffer for depth and stenciling */
      GE (glGenRenderbuffers (1, &gl_depth_stencil_handle));
      GE (glBindRenderbuffer (GL_RENDERBUFFER, gl_depth_stencil_handle));
      GE (glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH_STENCIL,
                                 cogl_texture_get_width (texture),
                                 cogl_texture_get_height (texture)));
      GE (glBindRenderbuffer (GL_RENDERBUFFER, 0));
      GE (glFramebufferRenderbuffer (GL_FRAMEBUFFER,
                                     GL_STENCIL_ATTACHMENT,
                                     GL_RENDERBUFFER, gl_depth_stencil_handle));
      GE (glFramebufferRenderbuffer (GL_FRAMEBUFFER,
                                     GL_DEPTH_ATTACHMENT,
                                     GL_RENDERBUFFER, gl_depth_stencil_handle));
      offscreen->renderbuffers =
        g_slist_prepend (offscreen->renderbuffers,
                         GUINT_TO_POINTER (gl_depth_stencil_handle));
    }

  if (flags & _TRY_DEPTH)
    {
      GE (glGenRenderbuffers (1, &gl_depth_handle));
      GE (glBindRenderbuffer (GL_RENDERBUFFER, gl_depth_handle));
      /* For now we just ask for GL_DEPTH_COMPONENT16 since this is all that's
       * available under GLES */
      GE (glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH_COMPONENT16,
                                 cogl_texture_get_width (texture),
                                 cogl_texture_get_height (texture)));
      GE (glBindRenderbuffer (GL_RENDERBUFFER, 0));
      GE (glFramebufferRenderbuffer (GL_FRAMEBUFFER,
                                     GL_DEPTH_ATTACHMENT,
                                     GL_RENDERBUFFER, gl_depth_handle));
      offscreen->renderbuffers =
        g_slist_prepend (offscreen->renderbuffers,
                         GUINT_TO_POINTER (gl_depth_handle));
    }

  if (flags & _TRY_STENCIL)
    {
      GE (glGenRenderbuffers (1, &gl_stencil_handle));
      GE (glBindRenderbuffer (GL_RENDERBUFFER, gl_stencil_handle));
      GE (glRenderbufferStorage (GL_RENDERBUFFER, GL_STENCIL_INDEX8,
                                 cogl_texture_get_width (texture),
                                 cogl_texture_get_height (texture)));
      GE (glBindRenderbuffer (GL_RENDERBUFFER, 0));
      GE (glFramebufferRenderbuffer (GL_FRAMEBUFFER,
                                     GL_STENCIL_ATTACHMENT,
                                     GL_RENDERBUFFER, gl_stencil_handle));
      offscreen->renderbuffers =
        g_slist_prepend (offscreen->renderbuffers,
                         GUINT_TO_POINTER (gl_stencil_handle));
    }

  /* Make sure it's complete */
  status = glCheckFramebufferStatus (GL_FRAMEBUFFER);

  if (status != GL_FRAMEBUFFER_COMPLETE)
    {
      GSList *l;

      GE (glDeleteFramebuffers (1, &fbo_gl_handle));

      for (l = offscreen->renderbuffers; l; l = l->next)
        {
          GLuint renderbuffer = GPOINTER_TO_UINT (l->data);
          GE (glDeleteRenderbuffers (1, &renderbuffer));
        }

      g_slist_free (offscreen->renderbuffers);
      offscreen->renderbuffers = NULL;

      return FALSE;
    }

  return TRUE;
}

CoglHandle
cogl_offscreen_new_to_texture (CoglHandle texhandle)
{
  CoglOffscreen      *offscreen;
  static TryFBOFlags  flags;
  static gboolean     have_working_flags = FALSE;

  _COGL_GET_CONTEXT (ctx, COGL_INVALID_HANDLE);

  if (!cogl_features_available (COGL_FEATURE_OFFSCREEN))
    return COGL_INVALID_HANDLE;

  /* Make texhandle is a valid texture object */
  if (!cogl_is_texture (texhandle))
    return COGL_INVALID_HANDLE;

  /* The texture must not be sliced */
  if (cogl_texture_is_sliced (texhandle))
    return COGL_INVALID_HANDLE;

  /* XXX: The framebuffer_object spec isn't clear in defining whether attaching
   * a texture as a renderbuffer with mipmap filtering enabled while the
   * mipmaps have not been uploaded should result in an incomplete framebuffer
   * object. (different drivers make different decisions)
   *
   * To avoid an error with drivers that do consider this a problem we
   * explicitly set non mipmapped filters here. These will later be reset when
   * the texture is actually used for rendering according to the filters set on
   * the corresponding CoglMaterial.
   */
  _cogl_texture_set_filters (texhandle, GL_NEAREST, GL_NEAREST);

  offscreen = g_new0 (CoglOffscreen, 1);
  offscreen->texture = cogl_handle_ref (texhandle);

  if ((have_working_flags &&
       try_creating_fbo (offscreen, flags, texhandle)) ||
#ifdef HAVE_COGL_GL
      try_creating_fbo (offscreen, flags = _TRY_DEPTH_STENCIL, texhandle) ||
#endif
      try_creating_fbo (offscreen, flags = _TRY_DEPTH | _TRY_STENCIL,
                        texhandle) ||
      try_creating_fbo (offscreen, flags = _TRY_STENCIL, texhandle) ||
      try_creating_fbo (offscreen, flags = _TRY_DEPTH, texhandle) ||
      try_creating_fbo (offscreen, flags = 0, texhandle))
    {
      /* Record that the last set of flags succeeded so that we can
         try that set first next time */
      have_working_flags = TRUE;

      _cogl_framebuffer_init (COGL_FRAMEBUFFER (offscreen),
                              COGL_FRAMEBUFFER_TYPE_OFFSCREEN,
                              cogl_texture_get_width (texhandle),
                              cogl_texture_get_height (texhandle));

      return _cogl_offscreen_object_new (offscreen);
    }
  else
    {
      g_free (offscreen);
      /* XXX: This API should probably have been defined to take a GError */
      g_warning ("%s: Failed to create an OpenGL framebuffer", G_STRLOC);
      return COGL_INVALID_HANDLE;
    }
}

static void
_cogl_offscreen_free (CoglOffscreen *offscreen)
{
  GSList *l;

  _COGL_GET_CONTEXT (ctx, NO_RETVAL);

  /* Chain up to parent */
  _cogl_framebuffer_free (COGL_FRAMEBUFFER (offscreen));

  for (l = offscreen->renderbuffers; l; l = l->next)
    {
      GLuint renderbuffer = GPOINTER_TO_UINT (l->data);
      GE (glDeleteRenderbuffers (1, &renderbuffer));
    }
  g_slist_free (offscreen->renderbuffers);

  GE (glDeleteFramebuffers (1, &offscreen->fbo_handle));

  if (offscreen->texture != COGL_INVALID_HANDLE)
    cogl_handle_unref (offscreen->texture);

  g_free (offscreen);
}

CoglHandle
_cogl_onscreen_new (void)
{
  CoglOnscreen *onscreen;

  /* XXX: Until we have full winsys support in Cogl then we can't fully
   * implement CoglOnscreen framebuffers, since we can't, e.g. keep track of
   * the window size. */

  onscreen = g_new0 (CoglOnscreen, 1);
  _cogl_framebuffer_init (COGL_FRAMEBUFFER (onscreen),
                          COGL_FRAMEBUFFER_TYPE_ONSCREEN,
                          0xdeadbeef, /* width */
                          0xdeadbeef); /* height */

  return _cogl_onscreen_object_new (onscreen);
}

static void
_cogl_onscreen_free (CoglOnscreen *onscreen)
{
  _COGL_GET_CONTEXT (ctx, NO_RETVAL);

  /* Chain up to parent */
  _cogl_framebuffer_free (COGL_FRAMEBUFFER (onscreen));

  g_free (onscreen);
}

void
_cogl_onscreen_clutter_backend_set_size (int width, int height)
{
  CoglFramebuffer *framebuffer;

  _COGL_GET_CONTEXT (ctx, NO_RETVAL);

  framebuffer = COGL_FRAMEBUFFER (ctx->window_buffer);

  if (framebuffer->width == width && framebuffer->height == height)
    return;

  framebuffer->width = width;
  framebuffer->height = height;

  /* We'll need to recalculate the GL viewport state derived
   * from the Cogl viewport */
  ctx->dirty_gl_viewport = 1;
}

GSList *
_cogl_create_framebuffer_stack (void)
{
  GSList *stack = NULL;

  return g_slist_prepend (stack, COGL_INVALID_HANDLE);
}

void
_cogl_free_framebuffer_stack (GSList *stack)
{
  GSList *l;

  for (l = stack; l != NULL; l = l->next)
    {
      CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (l->data);
      if (framebuffer->type == COGL_FRAMEBUFFER_TYPE_OFFSCREEN)
        _cogl_offscreen_free (COGL_OFFSCREEN (framebuffer));
      else
        _cogl_onscreen_free (COGL_ONSCREEN (framebuffer));
    }
  g_slist_free (stack);
}

/* Set the current framebuffer without checking if it's already the
 * current framebuffer. This is used by cogl_pop_framebuffer while
 * the top of the stack is currently not up to date. */
static void
_cogl_set_framebuffer_real (CoglFramebuffer *framebuffer)
{
  CoglFramebuffer **entry;

  _COGL_GET_CONTEXT (ctx, NO_RETVAL);

  cogl_flush ();

  entry = (CoglFramebuffer **)&ctx->framebuffer_stack->data;

  ctx->dirty_bound_framebuffer = 1;
  ctx->dirty_gl_viewport = 1;

  if (framebuffer != COGL_INVALID_HANDLE)
    cogl_object_ref (framebuffer);
  if (*entry != COGL_INVALID_HANDLE)
    cogl_object_unref (*entry);

  *entry = framebuffer;

  /* We've effectively just switched the current modelview and
   * projection matrix stacks and clip state so we need to dirty
   * them to ensure they get flushed for the next batch of geometry
   * we flush */
  _cogl_matrix_stack_dirty (framebuffer->modelview_stack);
  _cogl_matrix_stack_dirty (framebuffer->projection_stack);
  _cogl_clip_state_dirty (&framebuffer->clip_state);
}

void
cogl_set_framebuffer (CoglFramebuffer *framebuffer)
{
  g_return_if_fail (_cogl_is_framebuffer (framebuffer));

  if (_cogl_get_framebuffer () != framebuffer)
    _cogl_set_framebuffer_real (framebuffer);
}

/* XXX: deprecated API */
void
cogl_set_draw_buffer (CoglBufferTarget target, CoglHandle handle)
{
  _COGL_GET_CONTEXT (ctx, NO_RETVAL);

  if (target == COGL_WINDOW_BUFFER)
    handle = ctx->window_buffer;

  cogl_set_framebuffer (handle);
}

CoglFramebuffer *
_cogl_get_framebuffer (void)
{
  _COGL_GET_CONTEXT (ctx, NULL);

  g_assert (ctx->framebuffer_stack);

  return ctx->framebuffer_stack->data;
}

void
cogl_push_framebuffer (CoglFramebuffer *buffer)
{
  _COGL_GET_CONTEXT (ctx, NO_RETVAL);

  g_return_if_fail (_cogl_is_framebuffer (buffer));
  g_assert (ctx->framebuffer_stack);

  cogl_flush ();

  ctx->framebuffer_stack =
    g_slist_prepend (ctx->framebuffer_stack, COGL_INVALID_HANDLE);

  cogl_set_framebuffer (buffer);
}

/* XXX: deprecated API */
void
cogl_push_draw_buffer (void)
{
  cogl_push_framebuffer (_cogl_get_framebuffer ());
}

void
cogl_pop_framebuffer (void)
{
  CoglFramebuffer *to_pop;
  CoglFramebuffer *to_restore;

  _COGL_GET_CONTEXT (ctx, NO_RETVAL);

  g_assert (ctx->framebuffer_stack != NULL);
  g_assert (ctx->framebuffer_stack->next != NULL);

  to_pop = ctx->framebuffer_stack->data;
  to_restore = ctx->framebuffer_stack->next->data;

  cogl_flush ();

  cogl_object_unref (to_pop);
  ctx->framebuffer_stack =
    g_slist_delete_link (ctx->framebuffer_stack,
                         ctx->framebuffer_stack);

  /* If the framebuffer has changed as a result of popping the top
   * then re-assert the current buffer so as to dirty state as
   * necessary. */
  if (to_pop != to_restore)
    _cogl_set_framebuffer_real (to_restore);
}

/* XXX: deprecated API */
void
cogl_pop_draw_buffer (void)
{
  cogl_pop_framebuffer ();
}

void
_cogl_framebuffer_flush_state (CoglFramebuffer *framebuffer,
                               CoglFramebufferFlushFlags flags)
{
  _COGL_GET_CONTEXT (ctx, NO_RETVAL);

  if (cogl_features_available (COGL_FEATURE_OFFSCREEN) &&
      ctx->dirty_bound_framebuffer)
    {
      if (framebuffer->type == COGL_FRAMEBUFFER_TYPE_OFFSCREEN)
        {
          GE (glBindFramebuffer (GL_FRAMEBUFFER,
                                 COGL_OFFSCREEN (framebuffer)->fbo_handle));
        }
      else
        GE (glBindFramebuffer (GL_FRAMEBUFFER, 0));
      ctx->dirty_bound_framebuffer = FALSE;
    }

  if (ctx->dirty_gl_viewport)
    {
      int gl_viewport_y;

      /* Convert the Cogl viewport y offset to an OpenGL viewport y offset
       * NB: OpenGL defines its window and viewport origins to be bottom
       * left, while Cogl defines them to be top left.
       * NB: We render upside down to offscreen framebuffers so we don't
       * need to convert the y offset in this case. */
      if (cogl_is_offscreen (framebuffer))
        gl_viewport_y = framebuffer->viewport_y;
      else
        gl_viewport_y = framebuffer->height -
          (framebuffer->viewport_y + framebuffer->viewport_height);

      COGL_NOTE (OPENGL, "Calling glViewport(%d, %d, %d, %d)",
                 framebuffer->viewport_x,
                 gl_viewport_y,
                 framebuffer->viewport_width,
                 framebuffer->viewport_height);

      GE (glViewport (framebuffer->viewport_x,
                      gl_viewport_y,
                      framebuffer->viewport_width,
                      framebuffer->viewport_height));
      ctx->dirty_gl_viewport = FALSE;
    }

  /* since we might have changed the framebuffer, we should initialize
   * the bits; this is a no-op if they have already been initialized
   */
  _cogl_framebuffer_init_bits (framebuffer);

  /* XXX: Flushing clip state may trash the modelview and projection
   * matrices so we must do it before flushing the matrices...
   */
  _cogl_clip_state_flush (&framebuffer->clip_state);

  if (!(flags & COGL_FRAMEBUFFER_FLUSH_SKIP_MODELVIEW))
    _cogl_matrix_stack_flush_to_gl (framebuffer->modelview_stack,
                                    COGL_MATRIX_MODELVIEW);

  _cogl_matrix_stack_flush_to_gl (framebuffer->projection_stack,
                                  COGL_MATRIX_PROJECTION);
}

int
_cogl_framebuffer_get_red_bits (CoglFramebuffer *framebuffer)
{
  _cogl_framebuffer_init_bits (framebuffer);

  return framebuffer->red_bits;
}

int
_cogl_framebuffer_get_green_bits (CoglFramebuffer *framebuffer)
{
  _cogl_framebuffer_init_bits (framebuffer);

  return framebuffer->green_bits;
}

int
_cogl_framebuffer_get_blue_bits (CoglFramebuffer *framebuffer)
{
  _cogl_framebuffer_init_bits (framebuffer);

  return framebuffer->blue_bits;
}

int
_cogl_framebuffer_get_alpha_bits (CoglFramebuffer *framebuffer)
{
  _cogl_framebuffer_init_bits (framebuffer);

  return framebuffer->alpha_bits;
}

