From 7c69ca2997a7efa9b7c94e962790dcae3fa2790c Mon Sep 17 00:00:00 2001 From: Neil Roberts Date: Mon, 23 Jun 2008 14:57:36 +0000 Subject: [PATCH] Bug 918 - Group doesn't clip if it's children are clipped * clutter/cogl/common/cogl-clip-stack.h: * clutter/cogl/common/cogl-clip-stack.c: Added functions to maintain a stack of clipping rectangles. * clutter/cogl/gles/cogl.c: * clutter/cogl/gl/cogl.c: The cogl_clip_set and unset functions have moved into cogl-clip-stack.c which calls back to cogl.c to set the actual rectangles. Multiple clip rectangles are combined by merging the stencil buffers. * clutter/cogl/gles/cogl-primitives.c (_cogl_path_fill_nodes): * clutter/cogl/gl/cogl-primitives.c (_cogl_path_fill_nodes): Merge the stencil buffer with the contents of the clipping stack after drawing the path. * clutter/cogl/gles/cogl-context.h (CoglContext): * clutter/cogl/gl/cogl-context.h (CoglContext): Store the number of available stencil bits. * clutter/cogl/common/Makefile.am (libclutter_cogl_common_la_SOURCES): Added cogl-clip-stack.c --- common/Makefile.am | 3 +- common/cogl-clip-stack.c | 180 +++++++++++++++++++++ common/cogl-clip-stack.h | 37 +++++ gl/cogl-context.h | 1 + gl/cogl-primitives.c | 19 ++- gl/cogl.c | 338 ++++++++++++++++++++++++++++++--------- gles/cogl-context.h | 1 + gles/cogl-primitives.c | 17 +- gles/cogl.c | 300 ++++++++++++++++++++++++++-------- 9 files changed, 745 insertions(+), 151 deletions(-) create mode 100644 common/cogl-clip-stack.c create mode 100644 common/cogl-clip-stack.h diff --git a/common/Makefile.am b/common/Makefile.am index d76724bd6..ed7a5e5b4 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -23,4 +23,5 @@ libclutter_cogl_common_la_SOURCES = \ cogl-bitmap-fallback.c \ cogl-primitives.c \ cogl-primitives.h \ - cogl-bitmap-pixbuf.c + cogl-bitmap-pixbuf.c \ + cogl-clip-stack.c diff --git a/common/cogl-clip-stack.c b/common/cogl-clip-stack.c new file mode 100644 index 000000000..b34dd976a --- /dev/null +++ b/common/cogl-clip-stack.c @@ -0,0 +1,180 @@ +/* + * Clutter COGL + * + * A basic GL/GLES Abstraction/Utility Layer + * + * Authored By Matthew Allum + * + * Copyright (C) 2008 OpenedHand + * + * 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "cogl.h" +#include "cogl-clip-stack.h" + +/* These are defined in the particular backend (float in GL vs fixed + in GL ES) */ +void _cogl_set_clip_planes (ClutterFixed x, + ClutterFixed y, + ClutterFixed width, + ClutterFixed height); +void _cogl_init_stencil_buffer (void); +void _cogl_add_stencil_clip (ClutterFixed x, + ClutterFixed y, + ClutterFixed width, + ClutterFixed height, + gboolean first); +void _cogl_disable_clip_planes (void); +void _cogl_disable_stencil_buffer (void); +void _cogl_set_matrix (const ClutterFixed *matrix); + +typedef struct _CoglClipStackEntry CoglClipStackEntry; + +struct _CoglClipStackEntry +{ + /* The rectangle for this clip */ + ClutterFixed x_offset; + ClutterFixed y_offset; + ClutterFixed width; + ClutterFixed height; + + /* The matrix that was current when the clip was set */ + ClutterFixed matrix[16]; +}; + +static GList *cogl_clip_stack_top = NULL; +static GList *cogl_clip_stack_bottom = NULL; +static int cogl_clip_stack_depth = 0; + +static void +_cogl_clip_stack_add (const CoglClipStackEntry *entry, int depth) +{ + int has_clip_planes = cogl_features_available (COGL_FEATURE_FOUR_CLIP_PLANES); + + /* If this is the first entry and we support clip planes then use + that instead */ + if (depth == 1 && has_clip_planes) + _cogl_set_clip_planes (entry->x_offset, + entry->y_offset, + entry->width, + entry->height); + else + _cogl_add_stencil_clip (entry->x_offset, + entry->y_offset, + entry->width, + entry->height, + depth == (has_clip_planes ? 2 : 1)); +} + +void +cogl_clip_set (ClutterFixed x_offset, + ClutterFixed y_offset, + ClutterFixed width, + ClutterFixed height) +{ + CoglClipStackEntry *entry = g_slice_new (CoglClipStackEntry); + + /* Make a new entry */ + entry->x_offset = x_offset; + entry->y_offset = y_offset; + entry->width = width; + entry->height = height; + + cogl_get_modelview_matrix (entry->matrix); + + /* Add the entry to the current clip */ + _cogl_clip_stack_add (entry, ++cogl_clip_stack_depth); + + /* Store it in the stack */ + cogl_clip_stack_top = g_list_prepend (cogl_clip_stack_top, entry); + if (cogl_clip_stack_bottom == NULL) + cogl_clip_stack_bottom = cogl_clip_stack_top; +} + +void +cogl_clip_unset (void) +{ + g_return_if_fail (cogl_clip_stack_top != NULL); + + /* Remove the top entry from the stack */ + g_slice_free (CoglClipStackEntry, cogl_clip_stack_top->data); + cogl_clip_stack_top = g_list_delete_link (cogl_clip_stack_top, + cogl_clip_stack_top); + if (cogl_clip_stack_top == NULL) + cogl_clip_stack_bottom = NULL; + cogl_clip_stack_depth--; + + /* Rebuild the clip */ + _cogl_clip_stack_rebuild (FALSE); +} + +void +_cogl_clip_stack_rebuild (gboolean just_stencil) +{ + int has_clip_planes = cogl_features_available (COGL_FEATURE_FOUR_CLIP_PLANES); + GList *node; + int depth = 1; + + /* Disable clip planes if the stack is empty */ + if (has_clip_planes && cogl_clip_stack_depth < 1) + _cogl_disable_clip_planes (); + + /* Disable the stencil buffer if there isn't enough entries */ + if (cogl_clip_stack_depth < (has_clip_planes ? 2 : 1)) + _cogl_disable_stencil_buffer (); + + /* Re-add every entry from the bottom of the stack up */ + for (node = cogl_clip_stack_bottom; node; node = node->prev, depth++) + if (!just_stencil || !has_clip_planes || depth > 1) + { + const CoglClipStackEntry *entry = (CoglClipStackEntry *) node->data; + cogl_push_matrix (); + _cogl_set_matrix (entry->matrix); + _cogl_clip_stack_add (entry, depth); + cogl_pop_matrix (); + } +} + +void +_cogl_clip_stack_merge (void) +{ + GList *node = cogl_clip_stack_bottom; + + /* Merge the current clip stack on top of whatever is in the stencil + buffer */ + if (node) + { + /* Skip the first entry if we have clipping planes */ + if (cogl_features_available (COGL_FEATURE_FOUR_CLIP_PLANES)) + node = node->prev; + + while (node) + { + const CoglClipStackEntry *entry = (CoglClipStackEntry *) node->data; + cogl_push_matrix (); + _cogl_set_matrix (entry->matrix); + _cogl_clip_stack_add (entry, 3); + cogl_pop_matrix (); + + node = node->prev; + } + } +} diff --git a/common/cogl-clip-stack.h b/common/cogl-clip-stack.h new file mode 100644 index 000000000..3f3158198 --- /dev/null +++ b/common/cogl-clip-stack.h @@ -0,0 +1,37 @@ +/* + * Clutter COGL + * + * A basic GL/GLES Abstraction/Utility Layer + * + * Authored By Matthew Allum + * + * Copyright (C) 2008 OpenedHand + * + * 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __COGL_CLIP_STACK_H +#define __COGL_CLIP_STACK_H + +void cogl_clip_set (ClutterFixed x_offset, + ClutterFixed y_offset, + ClutterFixed width, + ClutterFixed height); +void cogl_clip_unset (void); +void _cogl_clip_stack_rebuild (gboolean just_stencil); +void _cogl_clip_stack_merge (void); + +#endif /* __COGL_CLIP_STACK_H */ diff --git a/gl/cogl-context.h b/gl/cogl-context.h index 3280a6ac9..694147c23 100644 --- a/gl/cogl-context.h +++ b/gl/cogl-context.h @@ -33,6 +33,7 @@ typedef struct /* Features cache */ CoglFeatureFlags feature_flags; gboolean features_cached; + GLint num_stencil_bits; /* Enable cache */ gulong enable_flags; diff --git a/gl/cogl-primitives.c b/gl/cogl-primitives.c index 4e4423991..b2659f4fb 100644 --- a/gl/cogl-primitives.c +++ b/gl/cogl-primitives.c @@ -30,6 +30,7 @@ #include "cogl.h" #include "cogl-internal.h" #include "cogl-context.h" +#include "cogl-clip-stack.h" #include #include @@ -145,9 +146,10 @@ _cogl_path_fill_nodes () GE( glClear (GL_STENCIL_BUFFER_BIT) ); GE( glEnable (GL_STENCIL_TEST) ); - GE( glStencilFunc (GL_ALWAYS, 0x0, 0x0) ); + GE( glStencilFunc (GL_NEVER, 0x0, 0x1) ); GE( glStencilOp (GL_INVERT, GL_INVERT, GL_INVERT) ); - GE( glColorMask (GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE) ); + + GE( glStencilMask (1) ); cogl_enable (COGL_ENABLE_VERTEX_ARRAY | (ctx->color_alpha < 255 ? COGL_ENABLE_BLEND : 0)); @@ -155,10 +157,14 @@ _cogl_path_fill_nodes () GE( glVertexPointer (2, GL_FLOAT, 0, ctx->path_nodes) ); GE( glDrawArrays (GL_TRIANGLE_FAN, 0, ctx->path_nodes_size) ); - GE( glStencilFunc (GL_EQUAL, 0x1, 0x1) ); - GE( glStencilOp (GL_ZERO, GL_ZERO, GL_ZERO) ); - GE( glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE) ); + GE( glStencilMask (~(GLuint) 0) ); + /* Merge the stencil buffer with any clipping rectangles */ + _cogl_clip_stack_merge (); + + GE( glStencilFunc (GL_EQUAL, 0x1, 0x1) ); + GE( glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP) ); + bounds_x = CLUTTER_FIXED_FLOOR (ctx->path_nodes_min.x); bounds_y = CLUTTER_FIXED_FLOOR (ctx->path_nodes_min.y); bounds_w = CLUTTER_FIXED_CEIL (ctx->path_nodes_max.x - ctx->path_nodes_min.x); @@ -166,5 +172,6 @@ _cogl_path_fill_nodes () cogl_rectangle (bounds_x, bounds_y, bounds_w, bounds_h); - GE( glDisable (GL_STENCIL_TEST) ); + /* Rebuild the stencil clip */ + _cogl_clip_stack_rebuild (TRUE); } diff --git a/gl/cogl.c b/gl/cogl.c index 338cf26ed..e41a3d4c9 100644 --- a/gl/cogl.c +++ b/gl/cogl.c @@ -32,6 +32,7 @@ #include #include #include +#include #ifdef HAVE_CLUTTER_GLX #include @@ -44,7 +45,6 @@ typedef CoglFuncPtr (*GLXGetProcAddressProc) (const guint8 *procName); #include "cogl-util.h" #include "cogl-context.h" - /* GL error to string conversion */ #if COGL_DEBUG struct token_string @@ -447,94 +447,280 @@ set_clip_plane (GLint plane_num, } void -cogl_clip_set (ClutterFixed x_offset, - ClutterFixed y_offset, - ClutterFixed width, - ClutterFixed height) +_cogl_set_clip_planes (ClutterFixed x_offset, + ClutterFixed y_offset, + ClutterFixed width, + ClutterFixed height) { - if (cogl_features_available (COGL_FEATURE_FOUR_CLIP_PLANES)) + GLfloat modelview[16], projection[16]; + + GLfloat vertex_tl[4] = { CLUTTER_FIXED_TO_FLOAT (x_offset), + CLUTTER_FIXED_TO_FLOAT (y_offset), + 0.0f, 1.0f }; + GLfloat vertex_tr[4] = { CLUTTER_FIXED_TO_FLOAT (x_offset + width), + CLUTTER_FIXED_TO_FLOAT (y_offset), + 0.0f, 1.0f }; + GLfloat vertex_bl[4] = { CLUTTER_FIXED_TO_FLOAT (x_offset), + CLUTTER_FIXED_TO_FLOAT (y_offset + height), + 0.0f, 1.0f }; + GLfloat vertex_br[4] = { CLUTTER_FIXED_TO_FLOAT (x_offset + width), + CLUTTER_FIXED_TO_FLOAT (y_offset + height), + 0.0f, 1.0f }; + + GE( glGetFloatv (GL_MODELVIEW_MATRIX, modelview) ); + GE( glGetFloatv (GL_PROJECTION_MATRIX, projection) ); + + project_vertex (modelview, projection, vertex_tl); + project_vertex (modelview, projection, vertex_tr); + project_vertex (modelview, projection, vertex_bl); + project_vertex (modelview, projection, vertex_br); + + /* If the order of the top and bottom lines is different from the + order of the left and right lines then the clip rect must have + been transformed so that the back is visible. We therefore need + to swap one pair of vertices otherwise all of the planes will be + the wrong way around */ + if ((vertex_tl[0] < vertex_tr[0] ? 1 : 0) + != (vertex_bl[1] < vertex_tl[1] ? 1 : 0)) { - GLfloat modelview[16], projection[16]; - - GLfloat vertex_tl[4] = { CLUTTER_FIXED_TO_FLOAT (x_offset), - CLUTTER_FIXED_TO_FLOAT (y_offset), - 0.0f, 1.0f }; - GLfloat vertex_tr[4] = { CLUTTER_FIXED_TO_FLOAT (x_offset + width), - CLUTTER_FIXED_TO_FLOAT (y_offset), - 0.0f, 1.0f }; - GLfloat vertex_bl[4] = { CLUTTER_FIXED_TO_FLOAT (x_offset), - CLUTTER_FIXED_TO_FLOAT (y_offset + height), - 0.0f, 1.0f }; - GLfloat vertex_br[4] = { CLUTTER_FIXED_TO_FLOAT (x_offset + width), - CLUTTER_FIXED_TO_FLOAT (y_offset + height), - 0.0f, 1.0f }; - - GE( glGetFloatv (GL_MODELVIEW_MATRIX, modelview) ); - GE( glGetFloatv (GL_PROJECTION_MATRIX, projection) ); - - project_vertex (modelview, projection, vertex_tl); - project_vertex (modelview, projection, vertex_tr); - project_vertex (modelview, projection, vertex_bl); - project_vertex (modelview, projection, vertex_br); - - /* If the order of the top and bottom lines is different from - the order of the left and right lines then the clip rect must - have been transformed so that the back is visible. We - therefore need to swap one pair of vertices otherwise all of - the planes will be the wrong way around */ - if ((vertex_tl[0] < vertex_tr[0] ? 1 : 0) - != (vertex_bl[1] < vertex_tl[1] ? 1 : 0)) - { - GLfloat temp[4]; - memcpy (temp, vertex_tl, sizeof (temp)); - memcpy (vertex_tl, vertex_tr, sizeof (temp)); - memcpy (vertex_tr, temp, sizeof (temp)); - memcpy (temp, vertex_bl, sizeof (temp)); - memcpy (vertex_bl, vertex_br, sizeof (temp)); - memcpy (vertex_br, temp, sizeof (temp)); - } - - set_clip_plane (GL_CLIP_PLANE0, vertex_tl, vertex_tr); - set_clip_plane (GL_CLIP_PLANE1, vertex_tr, vertex_br); - set_clip_plane (GL_CLIP_PLANE2, vertex_br, vertex_bl); - set_clip_plane (GL_CLIP_PLANE3, vertex_bl, vertex_tl); + GLfloat temp[4]; + memcpy (temp, vertex_tl, sizeof (temp)); + memcpy (vertex_tl, vertex_tr, sizeof (temp)); + memcpy (vertex_tr, temp, sizeof (temp)); + memcpy (temp, vertex_bl, sizeof (temp)); + memcpy (vertex_bl, vertex_br, sizeof (temp)); + memcpy (vertex_br, temp, sizeof (temp)); } - else if (cogl_features_available (COGL_FEATURE_STENCIL_BUFFER)) - { - GE( glEnable (GL_STENCIL_TEST) ); - GE( glClearStencil (0.0f) ); - GE( glClear (GL_STENCIL_BUFFER_BIT) ); + set_clip_plane (GL_CLIP_PLANE0, vertex_tl, vertex_tr); + set_clip_plane (GL_CLIP_PLANE1, vertex_tr, vertex_br); + set_clip_plane (GL_CLIP_PLANE2, vertex_br, vertex_bl); + set_clip_plane (GL_CLIP_PLANE3, vertex_bl, vertex_tl); +} - GE( glStencilFunc (GL_NEVER, 0x1, 0x1) ); - GE( glStencilOp (GL_INCR, GL_INCR, GL_INCR) ); - - GE( glColor3f (1.0f, 1.0f, 1.0f) ); - - GE( glRectf (CLUTTER_FIXED_TO_FLOAT (x_offset), - CLUTTER_FIXED_TO_FLOAT (y_offset), - CLUTTER_FIXED_TO_FLOAT (x_offset + width), - CLUTTER_FIXED_TO_FLOAT (y_offset + height)) ); - - GE( glStencilFunc (GL_EQUAL, 0x1, 0x1) ); - GE( glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP) ); - } +static int +compare_y_coordinate (const void *a, const void *b) +{ + GLfloat ay = ((const GLfloat *) a)[1]; + GLfloat by = ((const GLfloat *) b)[1]; + + return ay < by ? -1 : ay > by ? 1 : 0; } void -cogl_clip_unset (void) +_cogl_add_stencil_clip (ClutterFixed x_offset, + ClutterFixed y_offset, + ClutterFixed width, + ClutterFixed height, + gboolean first) { - if (cogl_features_available (COGL_FEATURE_FOUR_CLIP_PLANES)) + gboolean has_clip_planes + = cogl_features_available (COGL_FEATURE_FOUR_CLIP_PLANES); + + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + if (has_clip_planes) { GE( glDisable (GL_CLIP_PLANE3) ); GE( glDisable (GL_CLIP_PLANE2) ); GE( glDisable (GL_CLIP_PLANE1) ); GE( glDisable (GL_CLIP_PLANE0) ); } - else if (cogl_features_available (COGL_FEATURE_STENCIL_BUFFER)) + + if (first) { - GE( glDisable (GL_STENCIL_TEST) ); + GE( glEnable (GL_STENCIL_TEST) ); + + /* Initially disallow everything */ + GE( glClearStencil (0) ); + GE( glClear (GL_STENCIL_BUFFER_BIT) ); + + /* Punch out a hole to allow the rectangle */ + GE( glStencilFunc (GL_NEVER, 0x1, 0x1) ); + GE( glStencilOp (GL_REPLACE, GL_REPLACE, GL_REPLACE) ); + GE( glRectf (CLUTTER_FIXED_TO_FLOAT (x_offset), + CLUTTER_FIXED_TO_FLOAT (y_offset), + CLUTTER_FIXED_TO_FLOAT (x_offset + width), + CLUTTER_FIXED_TO_FLOAT (y_offset + height)) ); } + else if (ctx->num_stencil_bits > 1) + { + /* Add one to every pixel of the stencil buffer in the + rectangle */ + GE( glStencilFunc (GL_NEVER, 0x1, 0x3) ); + GE( glStencilOp (GL_INCR, GL_INCR, GL_INCR) ); + GE( glRectf (CLUTTER_FIXED_TO_FLOAT (x_offset), + CLUTTER_FIXED_TO_FLOAT (y_offset), + CLUTTER_FIXED_TO_FLOAT (x_offset + width), + CLUTTER_FIXED_TO_FLOAT (y_offset + height)) ); + + /* Subtract one from all pixels in the stencil buffer so that + only pixels where both the original stencil buffer and the + rectangle are set will be valid */ + GE( glStencilOp (GL_DECR, GL_DECR, GL_DECR) ); + GE( glPushMatrix () ); + GE( glLoadIdentity () ); + GE( glMatrixMode (GL_PROJECTION) ); + GE( glPushMatrix () ); + GE( glLoadIdentity () ); + GE( glRecti (-1, 1, 1, -1) ); + GE( glPopMatrix () ); + GE( glMatrixMode (GL_MODELVIEW) ); + GE( glPopMatrix () ); + } + else + { + /* Slower fallback if there is exactly one stencil bit. This + tries to draw enough triangles to tessalate around the + rectangle so that it can subtract from the stencil buffer for + every pixel in the screen except those in the rectangle */ + GLfloat modelview[16], projection[16]; + GLfloat temp_point[4]; + GLfloat left_edge, right_edge, bottom_edge, top_edge; + int i; + GLfloat points[16] = + { + CLUTTER_FIXED_TO_FLOAT (x_offset), + CLUTTER_FIXED_TO_FLOAT (y_offset), + 0, 1, + CLUTTER_FIXED_TO_FLOAT (x_offset + width), + CLUTTER_FIXED_TO_FLOAT (y_offset), + 0, 1, + CLUTTER_FIXED_TO_FLOAT (x_offset), + CLUTTER_FIXED_TO_FLOAT (y_offset + height), + 0, 1, + CLUTTER_FIXED_TO_FLOAT (x_offset + width), + CLUTTER_FIXED_TO_FLOAT (y_offset + height), + 0, 1 + }; + + GE( glGetFloatv (GL_MODELVIEW_MATRIX, modelview) ); + GE( glGetFloatv (GL_PROJECTION_MATRIX, projection) ); + + /* Project all of the vertices into screen coordinates */ + for (i = 0; i < 4; i++) + project_vertex (modelview, projection, points + i * 4); + + /* Sort the points by y coordinate */ + qsort (points, 4, sizeof (GLfloat) * 4, compare_y_coordinate); + + /* Put the bottom two pairs and the top two pairs in + left-right order */ + if (points[0] > points[4]) + { + memcpy (temp_point, points, sizeof (GLfloat) * 4); + memcpy (points, points + 4, sizeof (GLfloat) * 4); + memcpy (points + 4, temp_point, sizeof (GLfloat) * 4); + } + if (points[8] > points[12]) + { + memcpy (temp_point, points + 8, sizeof (GLfloat) * 4); + memcpy (points + 8, points + 12, sizeof (GLfloat) * 4); + memcpy (points + 12, temp_point, sizeof (GLfloat) * 4); + } + + /* If the clip rect goes outside of the screen then use the + extents of the rect instead */ + left_edge = MIN (-1.0f, MIN (points[0], points[8])); + right_edge = MAX ( 1.0f, MAX (points[4], points[12])); + bottom_edge = MIN (-1.0f, MIN (points[1], points[5])); + top_edge = MAX ( 1.0f, MAX (points[9], points[13])); + + /* Using the identity matrix for the projection and + modelview matrix, draw the triangles around the inner + rectangle */ + GE( glStencilFunc (GL_NEVER, 0x1, 0x1) ); + GE( glStencilOp (GL_ZERO, GL_ZERO, GL_ZERO) ); + GE( glPushMatrix () ); + GE( glLoadIdentity () ); + GE( glMatrixMode (GL_PROJECTION) ); + GE( glPushMatrix () ); + GE( glLoadIdentity () ); + + /* Clear the left side */ + glBegin (GL_TRIANGLE_STRIP); + glVertex2f (left_edge, bottom_edge); + glVertex2fv (points); + glVertex2f (left_edge, points[1]); + glVertex2fv (points + 8); + glVertex2f (left_edge, points[9]); + glVertex2f (left_edge, top_edge); + glEnd (); + + /* Clear the right side */ + glBegin (GL_TRIANGLE_STRIP); + glVertex2f (right_edge, top_edge); + glVertex2fv (points + 12); + glVertex2f (right_edge, points[13]); + glVertex2fv (points + 4); + glVertex2f (right_edge, points[5]); + glVertex2f (right_edge, bottom_edge); + glEnd (); + + /* Clear the top side */ + glBegin (GL_TRIANGLE_STRIP); + glVertex2f (left_edge, top_edge); + glVertex2fv (points + 8); + glVertex2f (points[8], top_edge); + glVertex2fv (points + 12); + glVertex2f (points[12], top_edge); + glVertex2f (right_edge, top_edge); + glEnd (); + + /* Clear the bottom side */ + glBegin (GL_TRIANGLE_STRIP); + glVertex2f (left_edge, bottom_edge); + glVertex2fv (points); + glVertex2f (points[0], bottom_edge); + glVertex2fv (points + 4); + glVertex2f (points[4], bottom_edge); + glVertex2f (right_edge, bottom_edge); + glEnd (); + + GE( glPopMatrix () ); + GE( glMatrixMode (GL_MODELVIEW) ); + GE( glPopMatrix () ); + } + + if (has_clip_planes) + { + GE( glEnable (GL_CLIP_PLANE0) ); + GE( glEnable (GL_CLIP_PLANE1) ); + GE( glEnable (GL_CLIP_PLANE2) ); + GE( glEnable (GL_CLIP_PLANE3) ); + } + + /* Restore the stencil mode */ + GE( glStencilFunc (GL_EQUAL, 0x1, 0x1) ); + GE( glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP) ); +} + +void +_cogl_set_matrix (const ClutterFixed *matrix) +{ + float float_matrix[16]; + int i; + + for (i = 0; i < 16; i++) + float_matrix[i] = CLUTTER_FIXED_TO_FLOAT (matrix[i]); + + GE( glLoadIdentity () ); + GE( glMultMatrixf (float_matrix) ); +} + +void +_cogl_disable_stencil_buffer (void) +{ + GE( glDisable (GL_STENCIL_TEST) ); +} + +void +_cogl_disable_clip_planes (void) +{ + GE( glDisable (GL_CLIP_PLANE3) ); + GE( glDisable (GL_CLIP_PLANE2) ); + GE( glDisable (GL_CLIP_PLANE1) ); + GE( glDisable (GL_CLIP_PLANE0) ); } void @@ -660,7 +846,6 @@ _cogl_features_init () ClutterFeatureFlags flags = 0; const gchar *gl_extensions; GLint max_clip_planes = 0; - GLint stencil_bits = 0; _COGL_GET_CONTEXT (ctx, NO_RETVAL); @@ -819,14 +1004,15 @@ _cogl_features_init () flags |= COGL_FEATURE_OFFSCREEN_MULTISAMPLE; } - GE( glGetIntegerv (GL_STENCIL_BITS, &stencil_bits) ); - if (stencil_bits > 0) + ctx->num_stencil_bits = 0; + GE( glGetIntegerv (GL_STENCIL_BITS, &ctx->num_stencil_bits) ); + if (ctx->num_stencil_bits > 0) flags |= COGL_FEATURE_STENCIL_BUFFER; GE( glGetIntegerv (GL_MAX_CLIP_PLANES, &max_clip_planes) ); if (max_clip_planes >= 4) flags |= COGL_FEATURE_FOUR_CLIP_PLANES; - + /* Cache features */ ctx->feature_flags = flags; ctx->features_cached = TRUE; diff --git a/gles/cogl-context.h b/gles/cogl-context.h index 1f7b3aac1..cc2e4cfed 100644 --- a/gles/cogl-context.h +++ b/gles/cogl-context.h @@ -42,6 +42,7 @@ typedef struct /* Features cache */ CoglFeatureFlags feature_flags; gboolean features_cached; + GLint num_stencil_bits; /* Enable cache */ gulong enable_flags; diff --git a/gles/cogl-primitives.c b/gles/cogl-primitives.c index cfeeec2f5..c559ca11b 100644 --- a/gles/cogl-primitives.c +++ b/gles/cogl-primitives.c @@ -30,6 +30,7 @@ #include "cogl.h" #include "cogl-internal.h" #include "cogl-context.h" +#include "cogl-clip-stack.h" #include #include @@ -177,23 +178,29 @@ _cogl_path_fill_nodes () GE( glClear (GL_STENCIL_BUFFER_BIT) ); GE( cogl_wrap_glEnable (GL_STENCIL_TEST) ); - GE( glStencilFunc (GL_ALWAYS, 0x0, 0x0) ); + GE( glStencilFunc (GL_NEVER, 0x0, 0x1) ); GE( glStencilOp (GL_INVERT, GL_INVERT, GL_INVERT) ); - GE( glColorMask (GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE) ); + GE( glStencilMask (1) ); + cogl_enable (COGL_ENABLE_VERTEX_ARRAY | (ctx->color_alpha < 255 ? COGL_ENABLE_BLEND : 0)); GE( cogl_wrap_glVertexPointer (2, GL_FIXED, 0, ctx->path_nodes) ); GE( cogl_wrap_glDrawArrays (GL_TRIANGLE_FAN, 0, ctx->path_nodes_size) ); + GE( glStencilMask (~(GLuint) 0) ); + + /* Merge the stencil buffer with any clipping rectangles */ + _cogl_clip_stack_merge (); + GE( glStencilFunc (GL_EQUAL, 0x1, 0x1) ); - GE( glStencilOp (GL_ZERO, GL_ZERO, GL_ZERO) ); - GE( glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE) ); + GE( glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP) ); cogl_rectangle (bounds_x, bounds_y, bounds_w, bounds_h); - GE( cogl_wrap_glDisable (GL_STENCIL_TEST) ); + /* Rebuild the stencil clip */ + _cogl_clip_stack_rebuild (TRUE); } else { diff --git a/gles/cogl.c b/gles/cogl.c index ffedc7e7a..a4ee269c3 100644 --- a/gles/cogl.c +++ b/gles/cogl.c @@ -30,6 +30,7 @@ #include "cogl.h" #include +#include #include "cogl-internal.h" #include "cogl-util.h" @@ -381,84 +382,257 @@ set_clip_plane (GLint plane_num, } void -cogl_clip_set (ClutterFixed x_offset, - ClutterFixed y_offset, - ClutterFixed width, - ClutterFixed height) +_cogl_set_clip_planes (ClutterFixed x_offset, + ClutterFixed y_offset, + ClutterFixed width, + ClutterFixed height) { - if (cogl_features_available (COGL_FEATURE_FOUR_CLIP_PLANES)) + GLfixed modelview[16], projection[16]; + + ClutterFixed vertex_tl[4] = { x_offset, y_offset, 0, CFX_ONE }; + ClutterFixed vertex_tr[4] = { x_offset + width, y_offset, 0, CFX_ONE }; + ClutterFixed vertex_bl[4] = { x_offset, y_offset + height, 0, CFX_ONE }; + ClutterFixed vertex_br[4] = { x_offset + width, y_offset + height, + 0, CFX_ONE }; + + GE( cogl_wrap_glGetFixedv (GL_MODELVIEW_MATRIX, modelview) ); + GE( cogl_wrap_glGetFixedv (GL_PROJECTION_MATRIX, projection) ); + + project_vertex (modelview, projection, vertex_tl); + project_vertex (modelview, projection, vertex_tr); + project_vertex (modelview, projection, vertex_bl); + project_vertex (modelview, projection, vertex_br); + + /* If the order of the top and bottom lines is different from + the order of the left and right lines then the clip rect must + have been transformed so that the back is visible. We + therefore need to swap one pair of vertices otherwise all of + the planes will be the wrong way around */ + if ((vertex_tl[0] < vertex_tr[0] ? 1 : 0) + != (vertex_bl[1] < vertex_tl[1] ? 1 : 0)) { - GLfixed modelview[16], projection[16]; - - ClutterFixed vertex_tl[4] = { x_offset, y_offset, 0, CFX_ONE }; - ClutterFixed vertex_tr[4] = { x_offset + width, y_offset, 0, CFX_ONE }; - ClutterFixed vertex_bl[4] = { x_offset, y_offset + height, 0, CFX_ONE }; - ClutterFixed vertex_br[4] = { x_offset + width, y_offset + height, - 0, CFX_ONE }; - - GE( cogl_wrap_glGetFixedv (GL_MODELVIEW_MATRIX, modelview) ); - GE( cogl_wrap_glGetFixedv (GL_PROJECTION_MATRIX, projection) ); - - project_vertex (modelview, projection, vertex_tl); - project_vertex (modelview, projection, vertex_tr); - project_vertex (modelview, projection, vertex_bl); - project_vertex (modelview, projection, vertex_br); - - /* If the order of the top and bottom lines is different from - the order of the left and right lines then the clip rect must - have been transformed so that the back is visible. We - therefore need to swap one pair of vertices otherwise all of - the planes will be the wrong way around */ - if ((vertex_tl[0] < vertex_tr[0] ? 1 : 0) - != (vertex_bl[1] < vertex_tl[1] ? 1 : 0)) - { - ClutterFixed temp[4]; - memcpy (temp, vertex_tl, sizeof (temp)); - memcpy (vertex_tl, vertex_tr, sizeof (temp)); - memcpy (vertex_tr, temp, sizeof (temp)); - memcpy (temp, vertex_bl, sizeof (temp)); - memcpy (vertex_bl, vertex_br, sizeof (temp)); - memcpy (vertex_br, temp, sizeof (temp)); - } - - set_clip_plane (GL_CLIP_PLANE0, vertex_tl, vertex_tr); - set_clip_plane (GL_CLIP_PLANE1, vertex_tr, vertex_br); - set_clip_plane (GL_CLIP_PLANE2, vertex_br, vertex_bl); - set_clip_plane (GL_CLIP_PLANE3, vertex_bl, vertex_tl); + ClutterFixed temp[4]; + memcpy (temp, vertex_tl, sizeof (temp)); + memcpy (vertex_tl, vertex_tr, sizeof (temp)); + memcpy (vertex_tr, temp, sizeof (temp)); + memcpy (temp, vertex_bl, sizeof (temp)); + memcpy (vertex_bl, vertex_br, sizeof (temp)); + memcpy (vertex_br, temp, sizeof (temp)); } - else if (cogl_features_available (COGL_FEATURE_STENCIL_BUFFER)) - { - GE( cogl_wrap_glEnable (GL_STENCIL_TEST) ); - GE( glClearStencil (0) ); - GE( glClear (GL_STENCIL_BUFFER_BIT) ); + set_clip_plane (GL_CLIP_PLANE0, vertex_tl, vertex_tr); + set_clip_plane (GL_CLIP_PLANE1, vertex_tr, vertex_br); + set_clip_plane (GL_CLIP_PLANE2, vertex_br, vertex_bl); + set_clip_plane (GL_CLIP_PLANE3, vertex_bl, vertex_tl); +} - GE( glStencilFunc (GL_NEVER, 0x1, 0x1) ); - GE( glStencilOp (GL_INCR, GL_INCR, GL_INCR) ); +static int +compare_y_coordinate (const void *a, const void *b) +{ + GLfixed ay = ((const GLfixed *) a)[1]; + GLfixed by = ((const GLfixed *) b)[1]; - GE( cogl_wrap_glColor4x (CFX_ONE, CFX_ONE, CFX_ONE, CFX_ONE ) ); - - cogl_rectanglex (x_offset, y_offset, width, height); - - GE( glStencilFunc (GL_EQUAL, 0x1, 0x1) ); - GE( glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP) ); - } + return ay < by ? -1 : ay > by ? 1 : 0; } void -cogl_clip_unset (void) +_cogl_add_stencil_clip (ClutterFixed x_offset, + ClutterFixed y_offset, + ClutterFixed width, + ClutterFixed height, + gboolean first) { - if (cogl_features_available (COGL_FEATURE_FOUR_CLIP_PLANES)) + gboolean has_clip_planes + = cogl_features_available (COGL_FEATURE_FOUR_CLIP_PLANES); + + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + if (has_clip_planes) { GE( cogl_wrap_glDisable (GL_CLIP_PLANE3) ); GE( cogl_wrap_glDisable (GL_CLIP_PLANE2) ); GE( cogl_wrap_glDisable (GL_CLIP_PLANE1) ); GE( cogl_wrap_glDisable (GL_CLIP_PLANE0) ); } - else if (cogl_features_available (COGL_FEATURE_STENCIL_BUFFER)) + + if (first) { - GE( cogl_wrap_glDisable (GL_STENCIL_TEST) ); + GE( cogl_wrap_glEnable (GL_STENCIL_TEST) ); + + /* Initially disallow everything */ + GE( glClearStencil (0) ); + GE( glClear (GL_STENCIL_BUFFER_BIT) ); + + /* Punch out a hole to allow the rectangle */ + GE( glStencilFunc (GL_NEVER, 0x1, 0x1) ); + GE( glStencilOp (GL_REPLACE, GL_REPLACE, GL_REPLACE) ); + + cogl_rectanglex (x_offset, y_offset, width, height); } + else if (ctx->num_stencil_bits > 1) + { + /* Add one to every pixel of the stencil buffer in the + rectangle */ + GE( glStencilFunc (GL_NEVER, 0x1, 0x3) ); + GE( glStencilOp (GL_INCR, GL_INCR, GL_INCR) ); + cogl_rectanglex (x_offset, y_offset, width, height); + + /* Subtract one from all pixels in the stencil buffer so that + only pixels where both the original stencil buffer and the + rectangle are set will be valid */ + GE( glStencilOp (GL_DECR, GL_DECR, GL_DECR) ); + GE( cogl_wrap_glPushMatrix () ); + GE( cogl_wrap_glLoadIdentity () ); + GE( cogl_wrap_glMatrixMode (GL_PROJECTION) ); + GE( cogl_wrap_glPushMatrix () ); + GE( cogl_wrap_glLoadIdentity () ); + cogl_rectanglex (-CFX_ONE, -CFX_ONE, + CLUTTER_INT_TO_FIXED (2), + CLUTTER_INT_TO_FIXED (2)); + GE( cogl_wrap_glPopMatrix () ); + GE( cogl_wrap_glMatrixMode (GL_MODELVIEW) ); + GE( cogl_wrap_glPopMatrix () ); + } + else + { + /* Slower fallback if there is exactly one stencil bit. This + tries to draw enough triangles to tessalate around the + rectangle so that it can subtract from the stencil buffer for + every pixel in the screen except those in the rectangle */ + GLfixed modelview[16], projection[16]; + GLfixed temp_point[4]; + GLfixed left_edge, right_edge, bottom_edge, top_edge; + int i; + GLfixed points[16] = + { + x_offset, y_offset, 0, CFX_ONE, + x_offset + width, y_offset, 0, CFX_ONE, + x_offset, y_offset + height, 0, CFX_ONE, + x_offset + width, y_offset + height, 0, CFX_ONE + }; + GLfixed draw_points[12]; + + GE( cogl_wrap_glGetFixedv (GL_MODELVIEW_MATRIX, modelview) ); + GE( cogl_wrap_glGetFixedv (GL_PROJECTION_MATRIX, projection) ); + + /* Project all of the vertices into screen coordinates */ + for (i = 0; i < 4; i++) + project_vertex (modelview, projection, points + i * 4); + + /* Sort the points by y coordinate */ + qsort (points, 4, sizeof (GLfixed) * 4, compare_y_coordinate); + + /* Put the bottom two pairs and the top two pairs in + left-right order */ + if (points[0] > points[4]) + { + memcpy (temp_point, points, sizeof (GLfixed) * 4); + memcpy (points, points + 4, sizeof (GLfixed) * 4); + memcpy (points + 4, temp_point, sizeof (GLfixed) * 4); + } + if (points[8] > points[12]) + { + memcpy (temp_point, points + 8, sizeof (GLfixed) * 4); + memcpy (points + 8, points + 12, sizeof (GLfixed) * 4); + memcpy (points + 12, temp_point, sizeof (GLfixed) * 4); + } + + /* If the clip rect goes outside of the screen then use the + extents of the rect instead */ + left_edge = MIN (-CFX_ONE, MIN (points[0], points[8])); + right_edge = MAX ( CFX_ONE, MAX (points[4], points[12])); + bottom_edge = MIN (-CFX_ONE, MIN (points[1], points[5])); + top_edge = MAX ( CFX_ONE, MAX (points[9], points[13])); + + /* Using the identity matrix for the projection and + modelview matrix, draw the triangles around the inner + rectangle */ + GE( glStencilFunc (GL_NEVER, 0x1, 0x1) ); + GE( glStencilOp (GL_ZERO, GL_ZERO, GL_ZERO) ); + GE( cogl_wrap_glPushMatrix () ); + GE( cogl_wrap_glLoadIdentity () ); + GE( cogl_wrap_glMatrixMode (GL_PROJECTION) ); + GE( cogl_wrap_glPushMatrix () ); + GE( cogl_wrap_glLoadIdentity () ); + + cogl_enable (COGL_ENABLE_VERTEX_ARRAY + | (ctx->color_alpha < 255 ? COGL_ENABLE_BLEND : 0)); + GE( cogl_wrap_glVertexPointer (2, GL_FIXED, 0, draw_points) ); + + /* Clear the left side */ + draw_points[0] = left_edge; draw_points[1] = bottom_edge; + draw_points[2] = points[0]; draw_points[3] = points[1]; + draw_points[4] = left_edge; draw_points[5] = points[1]; + draw_points[6] = points[8]; draw_points[7] = points[9]; + draw_points[8] = left_edge; draw_points[9] = points[9]; + draw_points[10] = left_edge; draw_points[11] = top_edge; + GE( cogl_wrap_glDrawArrays (GL_TRIANGLE_STRIP, 0, 6) ); + + /* Clear the right side */ + draw_points[0] = right_edge; draw_points[1] = top_edge; + draw_points[2] = points[12]; draw_points[3] = points[13]; + draw_points[4] = right_edge; draw_points[5] = points[13]; + draw_points[6] = points[4]; draw_points[7] = points[5]; + draw_points[8] = right_edge; draw_points[9] = points[5]; + draw_points[10] = right_edge; draw_points[11] = bottom_edge; + GE( cogl_wrap_glDrawArrays (GL_TRIANGLE_STRIP, 0, 6) ); + + /* Clear the top side */ + draw_points[0] = left_edge; draw_points[1] = top_edge; + draw_points[2] = points[8]; draw_points[3] = points[9]; + draw_points[4] = points[8]; draw_points[5] = top_edge; + draw_points[6] = points[12]; draw_points[7] = points[13]; + draw_points[8] = points[12]; draw_points[9] = top_edge; + draw_points[10] = right_edge; draw_points[11] = top_edge; + GE( cogl_wrap_glDrawArrays (GL_TRIANGLE_STRIP, 0, 6) ); + + /* Clear the bottom side */ + draw_points[0] = left_edge; draw_points[1] = bottom_edge; + draw_points[2] = points[0]; draw_points[3] = points[1]; + draw_points[4] = points[0]; draw_points[5] = bottom_edge; + draw_points[6] = points[4]; draw_points[7] = points[5]; + draw_points[8] = points[4]; draw_points[9] = bottom_edge; + draw_points[10] = right_edge; draw_points[11] = bottom_edge; + GE( cogl_wrap_glDrawArrays (GL_TRIANGLE_STRIP, 0, 6) ); + + GE( cogl_wrap_glPopMatrix () ); + GE( cogl_wrap_glMatrixMode (GL_MODELVIEW) ); + GE( cogl_wrap_glPopMatrix () ); + } + + if (has_clip_planes) + { + GE( cogl_wrap_glEnable (GL_CLIP_PLANE0) ); + GE( cogl_wrap_glEnable (GL_CLIP_PLANE1) ); + GE( cogl_wrap_glEnable (GL_CLIP_PLANE2) ); + GE( cogl_wrap_glEnable (GL_CLIP_PLANE3) ); + } + + /* Restore the stencil mode */ + GE( glStencilFunc (GL_EQUAL, 0x1, 0x1) ); + GE( glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP) ); +} + +void +_cogl_set_matrix (const ClutterFixed *matrix) +{ + GE( cogl_wrap_glLoadIdentity () ); + GE( cogl_wrap_glMultMatrixx (matrix) ); +} + +void +_cogl_disable_stencil_buffer (void) +{ + GE( cogl_wrap_glDisable (GL_STENCIL_TEST) ); +} + +void +_cogl_disable_clip_planes (void) +{ + GE( cogl_wrap_glDisable (GL_CLIP_PLANE3) ); + GE( cogl_wrap_glDisable (GL_CLIP_PLANE2) ); + GE( cogl_wrap_glDisable (GL_CLIP_PLANE1) ); + GE( cogl_wrap_glDisable (GL_CLIP_PLANE0) ); } void @@ -583,13 +757,13 @@ static void _cogl_features_init () { ClutterFeatureFlags flags = 0; - int stencil_bits = 0; int max_clip_planes = 0; _COGL_GET_CONTEXT (ctx, NO_RETVAL); - GE( cogl_wrap_glGetIntegerv (GL_STENCIL_BITS, &stencil_bits) ); - if (stencil_bits > 0) + ctx->num_stencil_bits = 0; + GE( cogl_wrap_glGetIntegerv (GL_STENCIL_BITS, &ctx->num_stencil_bits) ); + if (ctx->num_stencil_bits > 0) flags |= COGL_FEATURE_STENCIL_BUFFER; GE( cogl_wrap_glGetIntegerv (GL_MAX_CLIP_PLANES, &max_clip_planes) );