From e47b19822510344e4cae854e99a5ef0a76304980 Mon Sep 17 00:00:00 2001
From: Havoc Pennington <hp@pobox.com>
Date: Thu, 19 Feb 2009 22:37:08 -0500
Subject: [PATCH] add cogl_push_draw_buffer() and cogl_pop_draw_buffer()

These are necessary if nesting redirections to an fbo,
otherwise there's no way to know how to restore
previous state.

glPushAttrib(GL_COLOR_BUFFER_BIT) would save draw buffer
state, but also saves a lot of other stuff, and
cogl_draw_buffer() relies on knowing about all draw
buffer state changes. So we have to implement a
draw buffer stack ourselves.

Signed-off-by: Robert Bragg <robert@linux.intel.com>
---
 clutter/cogl/cogl-offscreen.h    | 14 ++++++
 clutter/cogl/gl/cogl-context.c   |  7 ++-
 clutter/cogl/gl/cogl-context.h   |  8 +++-
 clutter/cogl/gl/cogl-fbo.c       | 75 ++++++++++++++++++++++++++++++--
 clutter/cogl/gles/cogl-context.c |  7 ++-
 clutter/cogl/gles/cogl-context.h |  8 +++-
 clutter/cogl/gles/cogl-fbo.c     | 75 ++++++++++++++++++++++++++++++--
 7 files changed, 184 insertions(+), 10 deletions(-)

diff --git a/clutter/cogl/cogl-offscreen.h b/clutter/cogl/cogl-offscreen.h
index 17d2e54d6..6be6cf0f2 100644
--- a/clutter/cogl/cogl-offscreen.h
+++ b/clutter/cogl/cogl-offscreen.h
@@ -128,6 +128,20 @@ void            cogl_offscreen_blit_region    (CoglHandle          src_buffer,
 void            cogl_draw_buffer              (CoglBufferTarget    target,
                                                CoglHandle          offscreen);
 
+/**
+ * cogl_push_draw_buffer:
+ *
+ * Save cogl_draw_buffer() state.
+ */
+void            cogl_push_draw_buffer         (void);
+
+/**
+ * cogl_pop_draw_buffer:
+ *
+ * Restore cogl_draw_buffer() state.
+ */
+void            cogl_pop_draw_buffer          (void);
+
 G_END_DECLS
 
 #endif /* __COGL_OFFSCREEN_H__ */
diff --git a/clutter/cogl/gl/cogl-context.c b/clutter/cogl/gl/cogl-context.c
index 7f757f45e..986da8aa8 100644
--- a/clutter/cogl/gl/cogl-context.c
+++ b/clutter/cogl/gl/cogl-context.c
@@ -42,6 +42,7 @@ cogl_create_context ()
 {
   GLubyte default_texture_data[] = { 0xff, 0xff, 0xff, 0x0 };
   gulong  enable_flags = 0;
+  CoglDrawBufferState *draw_buffer;
 
   if (_context != NULL)
     return FALSE;
@@ -78,7 +79,11 @@ cogl_create_context ()
                                           sizeof (CoglLayerInfo));
   _context->n_texcoord_arrays_enabled = 0;
 
-  _context->draw_buffer = COGL_WINDOW_BUFFER;
+  draw_buffer = g_slice_new0 (CoglDrawBufferState);
+  draw_buffer->target = COGL_WINDOW_BUFFER;
+  draw_buffer->offscreen = COGL_INVALID_HANDLE;
+  _context->draw_buffer_stack =
+    g_slist_prepend (NULL, draw_buffer);
 
   _context->path_nodes = g_array_new (FALSE, FALSE, sizeof (CoglPathNode));
   _context->last_path = 0;
diff --git a/clutter/cogl/gl/cogl-context.h b/clutter/cogl/gl/cogl-context.h
index 602240793..2660ed812 100644
--- a/clutter/cogl/gl/cogl-context.h
+++ b/clutter/cogl/gl/cogl-context.h
@@ -36,6 +36,12 @@ typedef struct
   GLubyte c[4];
 } CoglTextureGLVertex;
 
+typedef struct
+{
+  CoglBufferTarget target;
+  CoglHandle offscreen;
+} CoglDrawBufferState;
+
 typedef struct
 {
   /* Features cache */
@@ -82,7 +88,7 @@ typedef struct
   guint             n_texcoord_arrays_enabled;
 
   /* Framebuffer objects */
-  CoglBufferTarget  draw_buffer;
+  GSList           *draw_buffer_stack;
 
   /* Clip stack */
   CoglClipStackState clip;
diff --git a/clutter/cogl/gl/cogl-fbo.c b/clutter/cogl/gl/cogl-fbo.c
index ce703d9a6..a5f6e22fd 100644
--- a/clutter/cogl/gl/cogl-fbo.c
+++ b/clutter/cogl/gl/cogl-fbo.c
@@ -242,9 +242,13 @@ void
 cogl_draw_buffer (CoglBufferTarget target, CoglHandle offscreen)
 {
   CoglFbo *fbo = NULL;
+  CoglDrawBufferState *draw_buffer;
 
   _COGL_GET_CONTEXT (ctx, NO_RETVAL);
 
+  g_assert (ctx->draw_buffer_stack != NULL);
+  draw_buffer = ctx->draw_buffer_stack->data;
+
   if (target == COGL_OFFSCREEN_BUFFER)
     {
       /* Make sure it is a valid fbo handle */
@@ -254,7 +258,7 @@ cogl_draw_buffer (CoglBufferTarget target, CoglHandle offscreen)
       fbo = _cogl_offscreen_pointer_from_handle (offscreen);
 
       /* Check current draw buffer target */
-      if (ctx->draw_buffer != COGL_OFFSCREEN_BUFFER)
+      if (draw_buffer->target != COGL_OFFSCREEN_BUFFER)
 	{
 	  /* Push the viewport and matrix setup if redirecting
              from a non-screen buffer */
@@ -303,7 +307,7 @@ cogl_draw_buffer (CoglBufferTarget target, CoglHandle offscreen)
 	   (target & COGL_MASK_BUFFER))
     {
       /* Check current draw buffer target */
-      if (ctx->draw_buffer == COGL_OFFSCREEN_BUFFER)
+      if (draw_buffer->target == COGL_OFFSCREEN_BUFFER)
 	{
 	  /* Pop viewport and matrices if redirecting back
              from an offscreen buffer */
@@ -338,5 +342,70 @@ cogl_draw_buffer (CoglBufferTarget target, CoglHandle offscreen)
     }
 
   /* Store new target */
-  ctx->draw_buffer = target;
+  draw_buffer->target = target;
+  if (draw_buffer->offscreen != offscreen)
+    {
+      if (draw_buffer->offscreen != COGL_INVALID_HANDLE)
+        cogl_handle_unref (draw_buffer->offscreen);
+      if (offscreen != COGL_INVALID_HANDLE)
+        cogl_handle_ref (offscreen);
+      draw_buffer->offscreen = offscreen;
+    }
+}
+
+void
+cogl_push_draw_buffer(void)
+{
+  CoglDrawBufferState *old;
+  CoglDrawBufferState *draw_buffer;
+
+  _COGL_GET_CONTEXT (ctx, NO_RETVAL);
+
+  g_assert (ctx->draw_buffer_stack != NULL);
+  old = ctx->draw_buffer_stack->data;
+
+  draw_buffer = g_slice_new0 (CoglDrawBufferState);
+  *draw_buffer = *old;
+
+  ctx->draw_buffer_stack =
+    g_slist_prepend (ctx->draw_buffer_stack, draw_buffer);
+}
+
+void
+cogl_pop_draw_buffer(void)
+{
+  CoglDrawBufferState *to_pop;
+  CoglDrawBufferState *to_restore;
+
+  _COGL_GET_CONTEXT (ctx, NO_RETVAL);
+
+  g_assert (ctx->draw_buffer_stack != NULL);
+  if (ctx->draw_buffer_stack->next == NULL)
+    {
+      g_warning ("1 more cogl_pop_draw_buffer() than cogl_push_draw_buffer()");
+      return;
+    }
+
+  to_pop = ctx->draw_buffer_stack->data;
+  to_restore = ctx->draw_buffer_stack->next->data;
+
+  /* the logic in cogl_draw_buffer() only works if
+   * to_pop is still on top of the stack, because
+   * cogl_draw_buffer() needs to know the previous
+   * state.
+   */
+  cogl_draw_buffer (to_restore->target, to_restore->offscreen);
+
+  /* cogl_draw_buffer() should have set top of stack
+   * to to_restore
+   */
+  g_assert (to_restore->target == to_pop->target);
+  g_assert (to_restore->offscreen == to_pop->offscreen);
+
+  g_assert (ctx->draw_buffer_stack->data == to_pop);
+  ctx->draw_buffer_stack =
+    g_slist_remove_link (ctx->draw_buffer_stack,
+                         ctx->draw_buffer_stack);
+
+  g_slice_free (CoglDrawBufferState, to_pop);
 }
diff --git a/clutter/cogl/gles/cogl-context.c b/clutter/cogl/gles/cogl-context.c
index c5bc2e3c6..d70d59d35 100644
--- a/clutter/cogl/gles/cogl-context.c
+++ b/clutter/cogl/gles/cogl-context.c
@@ -44,6 +44,7 @@ cogl_create_context ()
 {
   GLubyte default_texture_data[] = { 0xff, 0xff, 0xff, 0x0 };
   gulong  enable_flags = 0;
+  CoglDrawBufferState *draw_buffer;
 
   if (_context != NULL)
     return FALSE;
@@ -81,7 +82,11 @@ cogl_create_context ()
                                           sizeof (CoglLayerInfo));
   _context->n_texcoord_arrays_enabled = 0;
 
-  _context->draw_buffer = COGL_WINDOW_BUFFER;
+  draw_buffer = g_slice_new0 (CoglDrawBufferState);
+  draw_buffer->target = COGL_WINDOW_BUFFER;
+  draw_buffer->offscreen = COGL_INVALID_HANDLE;
+  _context->draw_buffer_stack =
+    g_slist_prepend (NULL, draw_buffer);
 
   _context->path_nodes = g_array_new (FALSE, FALSE, sizeof (CoglPathNode));
   _context->last_path = 0;
diff --git a/clutter/cogl/gles/cogl-context.h b/clutter/cogl/gles/cogl-context.h
index 994b100ff..3542e038d 100644
--- a/clutter/cogl/gles/cogl-context.h
+++ b/clutter/cogl/gles/cogl-context.h
@@ -38,6 +38,12 @@ typedef struct
   GLubyte c[4];
 } CoglTextureGLVertex;
 
+typedef struct
+{
+  CoglBufferTarget target;
+  CoglHandle offscreen;
+} CoglDrawBufferState;
+
 typedef struct
 {
   /* Features cache */
@@ -84,7 +90,7 @@ typedef struct
   guint             n_texcoord_arrays_enabled;
 
   /* Framebuffer objects */
-  CoglBufferTarget  draw_buffer;
+  GSList           *draw_buffer_stack;
 
   /* Clip stack */
   CoglClipStackState clip;
diff --git a/clutter/cogl/gles/cogl-fbo.c b/clutter/cogl/gles/cogl-fbo.c
index 6f2aef569..2b8aa46fa 100644
--- a/clutter/cogl/gles/cogl-fbo.c
+++ b/clutter/cogl/gles/cogl-fbo.c
@@ -180,9 +180,13 @@ void
 cogl_draw_buffer (CoglBufferTarget target, CoglHandle offscreen)
 {
   CoglFbo *fbo = NULL;
+  CoglDrawBufferState *draw_buffer;
 
   _COGL_GET_CONTEXT (ctx, NO_RETVAL);
 
+  g_assert (ctx->draw_buffer_stack != NULL);
+  draw_buffer = ctx->draw_buffer_stack->data;
+
   if (target == COGL_OFFSCREEN_BUFFER)
     {
       GLboolean scissor_enabled;
@@ -195,7 +199,7 @@ cogl_draw_buffer (CoglBufferTarget target, CoglHandle offscreen)
       fbo = _cogl_offscreen_pointer_from_handle (offscreen);
 
       /* Check current draw buffer target */
-      if (ctx->draw_buffer != COGL_OFFSCREEN_BUFFER)
+      if (draw_buffer->target != COGL_OFFSCREEN_BUFFER)
 	{
 	  /* Push the viewport and matrix setup if redirecting
              from a non-screen buffer */
@@ -249,7 +253,7 @@ cogl_draw_buffer (CoglBufferTarget target, CoglHandle offscreen)
 	   (target & COGL_MASK_BUFFER))
     {
       /* Check current draw buffer target */
-      if (ctx->draw_buffer == COGL_OFFSCREEN_BUFFER)
+      if (draw_buffer->target == COGL_OFFSCREEN_BUFFER)
 	{
 	  /* Pop viewport and matrices if redirecting back
              from an offscreen buffer */
@@ -285,7 +289,72 @@ cogl_draw_buffer (CoglBufferTarget target, CoglHandle offscreen)
     }
 
   /* Store new target */
-  ctx->draw_buffer = target;
+  draw_buffer->target = target;
+  if (draw_buffer->offscreen != offscreen)
+    {
+      if (draw_buffer->offscreen != COGL_INVALID_HANDLE)
+        cogl_handle_unref (draw_buffer->offscreen);
+      if (offscreen != COGL_INVALID_HANDLE)
+        cogl_handle_ref (offscreen);
+      draw_buffer->offscreen = offscreen;
+    }
+}
+
+void
+cogl_push_draw_buffer(void)
+{
+  CoglDrawBufferState *old;
+  CoglDrawBufferState *draw_buffer;
+
+  _COGL_GET_CONTEXT (ctx, NO_RETVAL);
+
+  g_assert (ctx->draw_buffer_stack != NULL);
+  old = ctx->draw_buffer_stack->data;
+
+  draw_buffer = g_slice_new0 (CoglDrawBufferState);
+  *draw_buffer = *old;
+
+  ctx->draw_buffer_stack =
+    g_slist_prepend (ctx->draw_buffer_stack, draw_buffer);
+}
+
+void
+cogl_pop_draw_buffer(void)
+{
+  CoglDrawBufferState *to_pop;
+  CoglDrawBufferState *to_restore;
+
+  _COGL_GET_CONTEXT (ctx, NO_RETVAL);
+
+  g_assert (ctx->draw_buffer_stack != NULL);
+  if (ctx->draw_buffer_stack->next == NULL)
+    {
+      g_warning ("1 more cogl_pop_draw_buffer() than cogl_push_draw_buffer()");
+      return;
+    }
+
+  to_pop = ctx->draw_buffer_stack->data;
+  to_restore = ctx->draw_buffer_stack->next->data;
+
+  /* the logic in cogl_draw_buffer() only works if
+   * to_pop is still on top of the stack, because
+   * cogl_draw_buffer() needs to know the previous
+   * state.
+   */
+  cogl_draw_buffer (to_restore->target, to_restore->offscreen);
+
+  /* cogl_draw_buffer() should have set top of stack
+   * to to_restore
+   */
+  g_assert (to_restore->target == to_pop->target);
+  g_assert (to_restore->offscreen == to_pop->offscreen);
+
+  g_assert (ctx->draw_buffer_stack->data == to_pop);
+  ctx->draw_buffer_stack =
+    g_slist_remove_link (ctx->draw_buffer_stack,
+                         ctx->draw_buffer_stack);
+
+  g_slice_free (CoglDrawBufferState, to_pop);
 }
 
 #else /* HAVE_COGL_GLES2 */