diff options
author | Robert Bragg <robert@linux.intel.com> | 2012-09-19 20:37:32 +0100 |
---|---|---|
committer | Robert Bragg <robert@linux.intel.com> | 2012-09-25 20:50:45 +0100 |
commit | acf989f1bb628282c53d1249b2e3fc6f6579f1e9 (patch) | |
tree | e8d5d3031179ba08a81e91ba182fbf78691ad744 | |
parent | 90587418233b6438290741d80aedf193ae660cad (diff) | |
download | cogl-acf989f1bb628282c53d1249b2e3fc6f6579f1e9.tar.gz |
clip-stack: Splits out opengl specific code
As part of an on-going effort to enable non-opengl drivers for Cogl this
splits out the opengl specific code in cogl-clip-stack.c into
cogl/driver/cogl-clip-stack-gl.c
Reviewed-by: Neil Roberts <neil@linux.intel.com>
-rw-r--r-- | cogl/Makefile.am | 2 | ||||
-rw-r--r-- | cogl/cogl-clip-stack.c | 604 | ||||
-rw-r--r-- | cogl/cogl-driver.h | 7 | ||||
-rw-r--r-- | cogl/driver/gl/cogl-clip-stack-gl-private.h | 39 | ||||
-rw-r--r-- | cogl/driver/gl/cogl-clip-stack-gl.c | 633 | ||||
-rw-r--r-- | cogl/driver/gl/gl/cogl-driver-gl.c | 2 | ||||
-rw-r--r-- | cogl/driver/gl/gles/cogl-driver-gles.c | 2 |
7 files changed, 688 insertions, 601 deletions
diff --git a/cogl/Makefile.am b/cogl/Makefile.am index 9387b0d8..7b078152 100644 --- a/cogl/Makefile.am +++ b/cogl/Makefile.am @@ -146,6 +146,8 @@ cogl_driver_sources += \ $(srcdir)/driver/gl/cogl-texture-2d-gl.c \ $(srcdir)/driver/gl/cogl-attribute-gl-private.h \ $(srcdir)/driver/gl/cogl-attribute-gl.c \ + $(srcdir)/driver/gl/cogl-clip-stack-gl-private.h \ + $(srcdir)/driver/gl/cogl-clip-stack-gl.c \ $(srcdir)/driver/gl/cogl-pipeline-opengl.c \ $(srcdir)/driver/gl/cogl-pipeline-opengl-private.h \ $(srcdir)/driver/gl/cogl-pipeline-fragend-glsl.c \ diff --git a/cogl/cogl-clip-stack.c b/cogl/cogl-clip-stack.c index d8dbb3dc..aad74817 100644 --- a/cogl/cogl-clip-stack.c +++ b/cogl/cogl-clip-stack.c @@ -16,7 +16,8 @@ * 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/>. + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. * * */ @@ -47,429 +48,7 @@ #include "cogl-offscreen.h" #include "cogl-matrix-stack.h" -#ifndef GL_CLIP_PLANE0 -#define GL_CLIP_PLANE0 0x3000 -#define GL_CLIP_PLANE1 0x3001 -#define GL_CLIP_PLANE2 0x3002 -#define GL_CLIP_PLANE3 0x3003 -#define GL_CLIP_PLANE4 0x3004 -#define GL_CLIP_PLANE5 0x3005 -#endif - -static void -project_vertex (const CoglMatrix *modelview_projection, - float *vertex) -{ - int i; - - cogl_matrix_transform_point (modelview_projection, - &vertex[0], &vertex[1], - &vertex[2], &vertex[3]); - - /* Convert from homogenized coordinates */ - for (i = 0; i < 4; i++) - vertex[i] /= vertex[3]; -} - -static void -set_clip_plane (CoglFramebuffer *framebuffer, - GLint plane_num, - const float *vertex_a, - const float *vertex_b) -{ - GLfloat planef[4]; - double planed[4]; - GLfloat angle; - CoglMatrixStack *modelview_stack = - _cogl_framebuffer_get_modelview_stack (framebuffer); - CoglMatrixStack *projection_stack = - _cogl_framebuffer_get_projection_stack (framebuffer); - CoglMatrix inverse_projection; - - _COGL_GET_CONTEXT (ctx, NO_RETVAL); - - _cogl_matrix_stack_get_inverse (projection_stack, &inverse_projection); - - /* Calculate the angle between the axes and the line crossing the - two points */ - angle = atan2f (vertex_b[1] - vertex_a[1], - vertex_b[0] - vertex_a[0]) * (180.0/G_PI); - - _cogl_matrix_stack_push (modelview_stack); - - /* Load the inverse of the projection matrix so we can specify the plane - * in screen coordinates */ - _cogl_matrix_stack_set (modelview_stack, &inverse_projection); - - /* Rotate about point a */ - _cogl_matrix_stack_translate (modelview_stack, - vertex_a[0], vertex_a[1], vertex_a[2]); - /* Rotate the plane by the calculated angle so that it will connect - the two points */ - _cogl_matrix_stack_rotate (modelview_stack, angle, 0.0f, 0.0f, 1.0f); - _cogl_matrix_stack_translate (modelview_stack, - -vertex_a[0], -vertex_a[1], -vertex_a[2]); - - /* Clip planes can only be used when a fixed function backend is in - use so we know we can directly push this matrix to the builtin - state */ - _cogl_matrix_entry_flush_to_gl_builtins (ctx, - modelview_stack->last_entry, - COGL_MATRIX_MODELVIEW, - framebuffer, - FALSE /* don't disable flip */); - - planef[0] = 0; - planef[1] = -1.0; - planef[2] = 0; - planef[3] = vertex_a[1]; - - switch (ctx->driver) - { - default: - g_assert_not_reached (); - break; - - case COGL_DRIVER_GLES1: - GE( ctx, glClipPlanef (plane_num, planef) ); - break; - - case COGL_DRIVER_GL: - planed[0] = planef[0]; - planed[1] = planef[1]; - planed[2] = planef[2]; - planed[3] = planef[3]; - GE( ctx, glClipPlane (plane_num, planed) ); - break; - } - - _cogl_matrix_stack_pop (modelview_stack); -} - -static void -set_clip_planes (CoglFramebuffer *framebuffer, - CoglMatrixEntry *modelview_entry, - float x_1, - float y_1, - float x_2, - float y_2) -{ - CoglMatrix modelview_matrix; - CoglMatrixStack *projection_stack = - _cogl_framebuffer_get_projection_stack (framebuffer); - CoglMatrix projection_matrix; - CoglMatrix modelview_projection; - float signed_area; - - float vertex_tl[4] = { x_1, y_1, 0, 1.0 }; - float vertex_tr[4] = { x_2, y_1, 0, 1.0 }; - float vertex_bl[4] = { x_1, y_2, 0, 1.0 }; - float vertex_br[4] = { x_2, y_2, 0, 1.0 }; - - _cogl_matrix_stack_get (projection_stack, &projection_matrix); - _cogl_matrix_entry_get (modelview_entry, &modelview_matrix); - - cogl_matrix_multiply (&modelview_projection, - &projection_matrix, - &modelview_matrix); - - project_vertex (&modelview_projection, vertex_tl); - project_vertex (&modelview_projection, vertex_tr); - project_vertex (&modelview_projection, vertex_bl); - project_vertex (&modelview_projection, vertex_br); - - /* Calculate the signed area of the polygon formed by the four - vertices so that we can know its orientation */ - signed_area = (vertex_tl[0] * (vertex_tr[1] - vertex_bl[1]) - + vertex_tr[0] * (vertex_br[1] - vertex_tl[1]) - + vertex_br[0] * (vertex_bl[1] - vertex_tr[1]) - + vertex_bl[0] * (vertex_tl[1] - vertex_br[1])); - - /* Set the clip planes to form lines between all of the vertices - using the same orientation as we calculated */ - if (signed_area > 0.0f) - { - /* counter-clockwise */ - set_clip_plane (framebuffer, GL_CLIP_PLANE0, vertex_tl, vertex_bl); - set_clip_plane (framebuffer, GL_CLIP_PLANE1, vertex_bl, vertex_br); - set_clip_plane (framebuffer, GL_CLIP_PLANE2, vertex_br, vertex_tr); - set_clip_plane (framebuffer, GL_CLIP_PLANE3, vertex_tr, vertex_tl); - } - else - { - /* clockwise */ - set_clip_plane (framebuffer, GL_CLIP_PLANE0, vertex_tl, vertex_tr); - set_clip_plane (framebuffer, GL_CLIP_PLANE1, vertex_tr, vertex_br); - set_clip_plane (framebuffer, GL_CLIP_PLANE2, vertex_br, vertex_bl); - set_clip_plane (framebuffer, GL_CLIP_PLANE3, vertex_bl, vertex_tl); - } -} - -static void -add_stencil_clip_rectangle (CoglFramebuffer *framebuffer, - CoglMatrixEntry *modelview_entry, - float x_1, - float y_1, - float x_2, - float y_2, - CoglBool first) -{ - CoglMatrixStack *projection_stack = - _cogl_framebuffer_get_projection_stack (framebuffer); - CoglContext *ctx = cogl_framebuffer_get_context (framebuffer); - - /* NB: This can be called while flushing the journal so we need - * to be very conservative with what state we change. - */ - - _cogl_context_set_current_projection_entry (ctx, - projection_stack->last_entry); - _cogl_context_set_current_modelview_entry (ctx, modelview_entry); - - if (first) - { - GE( ctx, glEnable (GL_STENCIL_TEST) ); - - /* Initially disallow everything */ - GE( ctx, glClearStencil (0) ); - GE( ctx, glClear (GL_STENCIL_BUFFER_BIT) ); - - /* Punch out a hole to allow the rectangle */ - GE( ctx, glStencilFunc (GL_NEVER, 0x1, 0x1) ); - GE( ctx, glStencilOp (GL_REPLACE, GL_REPLACE, GL_REPLACE) ); - - _cogl_rectangle_immediate (framebuffer, - ctx->stencil_pipeline, - x_1, y_1, x_2, y_2); - } - else - { - /* Add one to every pixel of the stencil buffer in the - rectangle */ - GE( ctx, glStencilFunc (GL_NEVER, 0x1, 0x3) ); - GE( ctx, glStencilOp (GL_INCR, GL_INCR, GL_INCR) ); - _cogl_rectangle_immediate (framebuffer, - ctx->stencil_pipeline, - x_1, y_1, x_2, y_2); - - /* 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( ctx, glStencilOp (GL_DECR, GL_DECR, GL_DECR) ); - - _cogl_context_set_current_projection_entry (ctx, &ctx->identity_entry); - _cogl_context_set_current_modelview_entry (ctx, &ctx->identity_entry); - - _cogl_rectangle_immediate (framebuffer, - ctx->stencil_pipeline, - -1.0, -1.0, 1.0, 1.0); - } - - /* Restore the stencil mode */ - GE( ctx, glStencilFunc (GL_EQUAL, 0x1, 0x1) ); - GE( ctx, glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP) ); -} - -typedef void (*SilhouettePaintCallback) (CoglFramebuffer *framebuffer, - CoglPipeline *pipeline, - void *user_data); - -static void -add_stencil_clip_silhouette (CoglFramebuffer *framebuffer, - SilhouettePaintCallback silhouette_callback, - CoglMatrixEntry *modelview_entry, - float bounds_x1, - float bounds_y1, - float bounds_x2, - float bounds_y2, - CoglBool merge, - CoglBool need_clear, - void *user_data) -{ - CoglMatrixStack *projection_stack = - _cogl_framebuffer_get_projection_stack (framebuffer); - CoglContext *ctx = cogl_framebuffer_get_context (framebuffer); - - /* NB: This can be called while flushing the journal so we need - * to be very conservative with what state we change. - */ - - _cogl_context_set_current_projection_entry (ctx, - projection_stack->last_entry); - _cogl_context_set_current_modelview_entry (ctx, modelview_entry); - - _cogl_pipeline_flush_gl_state (ctx->stencil_pipeline, framebuffer, FALSE, 0); - - GE( ctx, glEnable (GL_STENCIL_TEST) ); - - GE( ctx, glColorMask (FALSE, FALSE, FALSE, FALSE) ); - GE( ctx, glDepthMask (FALSE) ); - - if (merge) - { - GE (ctx, glStencilMask (2)); - GE (ctx, glStencilFunc (GL_LEQUAL, 0x2, 0x6)); - } - else - { - /* If we're not using the stencil buffer for clipping then we - don't need to clear the whole stencil buffer, just the area - that will be drawn */ - if (need_clear) - /* If this is being called from the clip stack code then it - will have set up a scissor for the minimum bounding box of - all of the clips. That box will likely mean that this - _cogl_clear won't need to clear the entire - buffer. _cogl_framebuffer_clear_without_flush4f is used instead - of cogl_clear because it won't try to flush the journal */ - _cogl_framebuffer_clear_without_flush4f (framebuffer, - COGL_BUFFER_BIT_STENCIL, - 0, 0, 0, 0); - else - { - /* Just clear the bounding box */ - GE( ctx, glStencilMask (~(GLuint) 0) ); - GE( ctx, glStencilOp (GL_ZERO, GL_ZERO, GL_ZERO) ); - _cogl_rectangle_immediate (framebuffer, - ctx->stencil_pipeline, - bounds_x1, bounds_y1, - bounds_x2, bounds_y2); - } - GE (ctx, glStencilMask (1)); - GE (ctx, glStencilFunc (GL_LEQUAL, 0x1, 0x3)); - } - - GE (ctx, glStencilOp (GL_INVERT, GL_INVERT, GL_INVERT)); - - silhouette_callback (framebuffer, ctx->stencil_pipeline, user_data); - - if (merge) - { - /* Now we have the new stencil buffer in bit 1 and the old - stencil buffer in bit 0 so we need to intersect them */ - GE (ctx, glStencilMask (3)); - GE (ctx, glStencilFunc (GL_NEVER, 0x2, 0x3)); - GE (ctx, glStencilOp (GL_DECR, GL_DECR, GL_DECR)); - /* Decrement all of the bits twice so that only pixels where the - value is 3 will remain */ - - _cogl_context_set_current_projection_entry (ctx, &ctx->identity_entry); - _cogl_context_set_current_modelview_entry (ctx, &ctx->identity_entry); - - _cogl_rectangle_immediate (framebuffer, ctx->stencil_pipeline, - -1.0, -1.0, 1.0, 1.0); - _cogl_rectangle_immediate (framebuffer, ctx->stencil_pipeline, - -1.0, -1.0, 1.0, 1.0); - } - GE (ctx, glStencilMask (~(GLuint) 0)); - GE (ctx, glDepthMask (TRUE)); - GE (ctx, glColorMask (TRUE, TRUE, TRUE, TRUE)); - - GE (ctx, glStencilFunc (GL_EQUAL, 0x1, 0x1)); - GE (ctx, glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP)); -} - -static void -paint_path_silhouette (CoglFramebuffer *framebuffer, - CoglPipeline *pipeline, - void *user_data) -{ - CoglPath *path = user_data; - if (path->data->path_nodes->len >= 3) - _cogl_path_fill_nodes (path, - framebuffer, - pipeline, - COGL_DRAW_SKIP_JOURNAL_FLUSH | - COGL_DRAW_SKIP_PIPELINE_VALIDATION | - COGL_DRAW_SKIP_FRAMEBUFFER_FLUSH); -} - -static void -add_stencil_clip_path (CoglFramebuffer *framebuffer, - CoglMatrixEntry *modelview_entry, - CoglPath *path, - CoglBool merge, - CoglBool need_clear) -{ - CoglPathData *data = path->data; - add_stencil_clip_silhouette (framebuffer, - paint_path_silhouette, - modelview_entry, - data->path_nodes_min.x, - data->path_nodes_min.y, - data->path_nodes_max.x, - data->path_nodes_max.y, - merge, - need_clear, - path); -} - -static void -paint_primitive_silhouette (CoglFramebuffer *framebuffer, - CoglPipeline *pipeline, - void *user_data) -{ - _cogl_framebuffer_draw_primitive (framebuffer, - pipeline, - user_data, - COGL_DRAW_SKIP_JOURNAL_FLUSH | - COGL_DRAW_SKIP_PIPELINE_VALIDATION | - COGL_DRAW_SKIP_FRAMEBUFFER_FLUSH); -} - -static void -add_stencil_clip_primitive (CoglFramebuffer *framebuffer, - CoglMatrixEntry *modelview_entry, - CoglPrimitive *primitive, - float bounds_x1, - float bounds_y1, - float bounds_x2, - float bounds_y2, - CoglBool merge, - CoglBool need_clear) -{ - add_stencil_clip_silhouette (framebuffer, - paint_primitive_silhouette, - modelview_entry, - bounds_x1, - bounds_y1, - bounds_x2, - bounds_y2, - merge, - need_clear, - primitive); -} - -static void -disable_stencil_buffer (void) -{ - _COGL_GET_CONTEXT (ctx, NO_RETVAL); - - GE( ctx, glDisable (GL_STENCIL_TEST) ); -} - -static void -enable_clip_planes (void) -{ - _COGL_GET_CONTEXT (ctx, NO_RETVAL); - - GE( ctx, glEnable (GL_CLIP_PLANE0) ); - GE( ctx, glEnable (GL_CLIP_PLANE1) ); - GE( ctx, glEnable (GL_CLIP_PLANE2) ); - GE( ctx, glEnable (GL_CLIP_PLANE3) ); -} - -static void -disable_clip_planes (void) -{ - _COGL_GET_CONTEXT (ctx, NO_RETVAL); - - GE( ctx, glDisable (GL_CLIP_PLANE3) ); - GE( ctx, glDisable (GL_CLIP_PLANE2) ); - GE( ctx, glDisable (GL_CLIP_PLANE1) ); - GE( ctx, glDisable (GL_CLIP_PLANE0) ); -} static void * _cogl_clip_stack_push_entry (CoglClipStack *clip_stack, @@ -879,183 +458,6 @@ _cogl_clip_stack_flush (CoglClipStack *stack, CoglFramebuffer *framebuffer) { CoglContext *ctx = framebuffer->context; - int has_clip_planes; - CoglBool using_clip_planes = FALSE; - CoglBool using_stencil_buffer = FALSE; - int scissor_x0; - int scissor_y0; - int scissor_x1; - int scissor_y1; - CoglClipStack *entry; - int scissor_y_start; - - /* If we have already flushed this state then we don't need to do - anything */ - if (ctx->current_clip_stack_valid) - { - if (ctx->current_clip_stack == stack) - return; - - _cogl_clip_stack_unref (ctx->current_clip_stack); - } - - ctx->current_clip_stack_valid = TRUE; - ctx->current_clip_stack = _cogl_clip_stack_ref (stack); - - has_clip_planes = - ctx->private_feature_flags & COGL_PRIVATE_FEATURE_FOUR_CLIP_PLANES; - - if (has_clip_planes) - disable_clip_planes (); - disable_stencil_buffer (); - - /* If the stack is empty then there's nothing else to do */ - if (stack == NULL) - { - COGL_NOTE (CLIPPING, "Flushed empty clip stack"); - - ctx->current_clip_stack_uses_stencil = FALSE; - GE (ctx, glDisable (GL_SCISSOR_TEST)); - return; - } - - /* Calculate the scissor rect first so that if we eventually have to - clear the stencil buffer then the clear will be clipped to the - intersection of all of the bounding boxes. This saves having to - clear the whole stencil buffer */ - _cogl_clip_stack_get_bounds (stack, - &scissor_x0, &scissor_y0, - &scissor_x1, &scissor_y1); - - /* Enable scissoring as soon as possible */ - if (scissor_x0 >= scissor_x1 || scissor_y0 >= scissor_y1) - scissor_x0 = scissor_y0 = scissor_x1 = scissor_y1 = scissor_y_start = 0; - else - { - /* We store the entry coordinates in Cogl coordinate space - * but OpenGL requires the window origin to be the bottom - * left so we may need to convert the incoming coordinates. - * - * NB: Cogl forces all offscreen rendering to be done upside - * down so in this case no conversion is needed. - */ - - if (cogl_is_offscreen (framebuffer)) - scissor_y_start = scissor_y0; - else - { - int framebuffer_height = - cogl_framebuffer_get_height (framebuffer); - - scissor_y_start = framebuffer_height - scissor_y1; - } - } - - COGL_NOTE (CLIPPING, "Flushing scissor to (%i, %i, %i, %i)", - scissor_x0, scissor_y0, - scissor_x1, scissor_y1); - - GE (ctx, glEnable (GL_SCISSOR_TEST)); - GE (ctx, glScissor (scissor_x0, scissor_y_start, - scissor_x1 - scissor_x0, - scissor_y1 - scissor_y0)); - - /* Add all of the entries. This will end up adding them in the - reverse order that they were specified but as all of the clips - are intersecting it should work out the same regardless of the - order */ - for (entry = stack; entry; entry = entry->parent) - { - switch (entry->type) - { - case COGL_CLIP_STACK_PATH: - { - CoglClipStackPath *path_entry = (CoglClipStackPath *) entry; - - COGL_NOTE (CLIPPING, "Adding stencil clip for path"); - - add_stencil_clip_path (framebuffer, - path_entry->matrix_entry, - path_entry->path, - using_stencil_buffer, - TRUE); - - using_stencil_buffer = TRUE; - break; - } - case COGL_CLIP_STACK_PRIMITIVE: - { - CoglClipStackPrimitive *primitive_entry = - (CoglClipStackPrimitive *) entry; - - COGL_NOTE (CLIPPING, "Adding stencil clip for primitive"); - - add_stencil_clip_primitive (framebuffer, - primitive_entry->matrix_entry, - primitive_entry->primitive, - primitive_entry->bounds_x1, - primitive_entry->bounds_y1, - primitive_entry->bounds_x2, - primitive_entry->bounds_y2, - using_stencil_buffer, - TRUE); - - using_stencil_buffer = TRUE; - break; - } - case COGL_CLIP_STACK_RECT: - { - CoglClipStackRect *rect = (CoglClipStackRect *) entry; - - /* We don't need to do anything extra if the clip for this - rectangle was entirely described by its scissor bounds */ - if (!rect->can_be_scissor) - { - /* If we support clip planes and we haven't already used - them then use that instead */ - if (has_clip_planes) - { - COGL_NOTE (CLIPPING, - "Adding clip planes clip for rectangle"); - - set_clip_planes (framebuffer, - rect->matrix_entry, - rect->x0, - rect->y0, - rect->x1, - rect->y1); - using_clip_planes = TRUE; - /* We can't use clip planes a second time */ - has_clip_planes = FALSE; - } - else - { - COGL_NOTE (CLIPPING, "Adding stencil clip for rectangle"); - - add_stencil_clip_rectangle (framebuffer, - rect->matrix_entry, - rect->x0, - rect->y0, - rect->x1, - rect->y1, - !using_stencil_buffer); - using_stencil_buffer = TRUE; - } - } - break; - } - case COGL_CLIP_STACK_WINDOW_RECT: - break; - /* We don't need to do anything for window space rectangles because - * their functionality is entirely implemented by the entry bounding - * box */ - } - } - - /* Enabling clip planes is delayed to now so that they won't affect - setting up the stencil buffer */ - if (using_clip_planes) - enable_clip_planes (); - ctx->current_clip_stack_uses_stencil = using_stencil_buffer; + ctx->driver_vtable->clip_stack_flush (stack, framebuffer); } diff --git a/cogl/cogl-driver.h b/cogl/cogl-driver.h index bea53cd6..527e5ffa 100644 --- a/cogl/cogl-driver.h +++ b/cogl/cogl-driver.h @@ -222,6 +222,13 @@ struct _CoglDriverVtable CoglDrawFlags flags, CoglAttribute **attributes, int n_attributes); + + /* Flushes the clip stack to the GPU using a combination of the + * stencil buffer, scissor and clip plane state. + */ + void + (* clip_stack_flush) (CoglClipStack *stack, CoglFramebuffer *framebuffer); + }; #endif /* __COGL_DRIVER_H */ diff --git a/cogl/driver/gl/cogl-clip-stack-gl-private.h b/cogl/driver/gl/cogl-clip-stack-gl-private.h new file mode 100644 index 00000000..2946b3ba --- /dev/null +++ b/cogl/driver/gl/cogl-clip-stack-gl-private.h @@ -0,0 +1,39 @@ +/* + * Cogl + * + * An object oriented GL/GLES Abstraction/Utility Layer + * + * Copyright (C) 2012 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/>. + * + * + * + * Authors: + * Robert Bragg <robert@linux.intel.com> + */ + +#ifndef _COGL_CLIP_STACK_GL_PRIVATE_H_ +#define _COGL_CLIP_STACK_GL_PRIVATE_H_ + +#include "cogl-types.h" +#include "cogl-framebuffer.h" +#include "cogl-clip-stack.h" + +void +_cogl_clip_stack_gl_flush (CoglClipStack *stack, + CoglFramebuffer *framebuffer); + +#endif /* _COGL_CLIP_STACK_GL_PRIVATE_H_ */ diff --git a/cogl/driver/gl/cogl-clip-stack-gl.c b/cogl/driver/gl/cogl-clip-stack-gl.c new file mode 100644 index 00000000..2547fc9e --- /dev/null +++ b/cogl/driver/gl/cogl-clip-stack-gl.c @@ -0,0 +1,633 @@ +/* + * Cogl + * + * An object oriented GL/GLES Abstraction/Utility Layer + * + * Copyright (C) 2007,2008,2009,2010,2011,2012 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/>. + * + * + * + * Authors: + * Neil Roberts <neil@linux.intel.com> + * Robert Bragg <robert@linux.intel.com> + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "cogl-context-private.h" +#include "cogl-primitives-private.h" +#include "cogl-pipeline-opengl-private.h" +#include "cogl-path-private.h" +#include "cogl-clip-stack-gl-private.h" + +#ifndef GL_CLIP_PLANE0 +#define GL_CLIP_PLANE0 0x3000 +#define GL_CLIP_PLANE1 0x3001 +#define GL_CLIP_PLANE2 0x3002 +#define GL_CLIP_PLANE3 0x3003 +#define GL_CLIP_PLANE4 0x3004 +#define GL_CLIP_PLANE5 0x3005 +#endif + +static void +project_vertex (const CoglMatrix *modelview_projection, + float *vertex) +{ + int i; + + cogl_matrix_transform_point (modelview_projection, + &vertex[0], &vertex[1], + &vertex[2], &vertex[3]); + + /* Convert from homogenized coordinates */ + for (i = 0; i < 4; i++) + vertex[i] /= vertex[3]; +} + +static void +set_clip_plane (CoglFramebuffer *framebuffer, + int plane_num, + const float *vertex_a, + const float *vertex_b) +{ + CoglContext *ctx = framebuffer->context; + float planef[4]; + double planed[4]; + float angle; + CoglMatrixStack *modelview_stack = + _cogl_framebuffer_get_modelview_stack (framebuffer); + CoglMatrixStack *projection_stack = + _cogl_framebuffer_get_projection_stack (framebuffer); + CoglMatrix inverse_projection; + + _cogl_matrix_stack_get_inverse (projection_stack, &inverse_projection); + + /* Calculate the angle between the axes and the line crossing the + two points */ + angle = atan2f (vertex_b[1] - vertex_a[1], + vertex_b[0] - vertex_a[0]) * (180.0/G_PI); + + _cogl_matrix_stack_push (modelview_stack); + + /* Load the inverse of the projection matrix so we can specify the plane + * in screen coordinates */ + _cogl_matrix_stack_set (modelview_stack, &inverse_projection); + + /* Rotate about point a */ + _cogl_matrix_stack_translate (modelview_stack, + vertex_a[0], vertex_a[1], vertex_a[2]); + /* Rotate the plane by the calculated angle so that it will connect + the two points */ + _cogl_matrix_stack_rotate (modelview_stack, angle, 0.0f, 0.0f, 1.0f); + _cogl_matrix_stack_translate (modelview_stack, + -vertex_a[0], -vertex_a[1], -vertex_a[2]); + + /* Clip planes can only be used when a fixed function backend is in + use so we know we can directly push this matrix to the builtin + state */ + _cogl_matrix_entry_flush_to_gl_builtins (ctx, + modelview_stack->last_entry, + COGL_MATRIX_MODELVIEW, + framebuffer, + FALSE /* don't disable flip */); + + planef[0] = 0; + planef[1] = -1.0; + planef[2] = 0; + planef[3] = vertex_a[1]; + + switch (ctx->driver) + { + default: + g_assert_not_reached (); + break; + + case COGL_DRIVER_GLES1: + GE( ctx, glClipPlanef (plane_num, planef) ); + break; + + case COGL_DRIVER_GL: + planed[0] = planef[0]; + planed[1] = planef[1]; + planed[2] = planef[2]; + planed[3] = planef[3]; + GE( ctx, glClipPlane (plane_num, planed) ); + break; + } + + _cogl_matrix_stack_pop (modelview_stack); +} + +static void +set_clip_planes (CoglFramebuffer *framebuffer, + CoglMatrixEntry *modelview_entry, + float x_1, + float y_1, + float x_2, + float y_2) +{ + CoglMatrix modelview_matrix; + CoglMatrixStack *projection_stack = + _cogl_framebuffer_get_projection_stack (framebuffer); + CoglMatrix projection_matrix; + CoglMatrix modelview_projection; + float signed_area; + + float vertex_tl[4] = { x_1, y_1, 0, 1.0 }; + float vertex_tr[4] = { x_2, y_1, 0, 1.0 }; + float vertex_bl[4] = { x_1, y_2, 0, 1.0 }; + float vertex_br[4] = { x_2, y_2, 0, 1.0 }; + + _cogl_matrix_stack_get (projection_stack, &projection_matrix); + _cogl_matrix_entry_get (modelview_entry, &modelview_matrix); + + cogl_matrix_multiply (&modelview_projection, + &projection_matrix, + &modelview_matrix); + + project_vertex (&modelview_projection, vertex_tl); + project_vertex (&modelview_projection, vertex_tr); + project_vertex (&modelview_projection, vertex_bl); + project_vertex (&modelview_projection, vertex_br); + + /* Calculate the signed area of the polygon formed by the four + vertices so that we can know its orientation */ + signed_area = (vertex_tl[0] * (vertex_tr[1] - vertex_bl[1]) + + vertex_tr[0] * (vertex_br[1] - vertex_tl[1]) + + vertex_br[0] * (vertex_bl[1] - vertex_tr[1]) + + vertex_bl[0] * (vertex_tl[1] - vertex_br[1])); + + /* Set the clip planes to form lines between all of the vertices + using the same orientation as we calculated */ + if (signed_area > 0.0f) + { + /* counter-clockwise */ + set_clip_plane (framebuffer, GL_CLIP_PLANE0, vertex_tl, vertex_bl); + set_clip_plane (framebuffer, GL_CLIP_PLANE1, vertex_bl, vertex_br); + set_clip_plane (framebuffer, GL_CLIP_PLANE2, vertex_br, vertex_tr); + set_clip_plane (framebuffer, GL_CLIP_PLANE3, vertex_tr, vertex_tl); + } + else + { + /* clockwise */ + set_clip_plane (framebuffer, GL_CLIP_PLANE0, vertex_tl, vertex_tr); + set_clip_plane (framebuffer, GL_CLIP_PLANE1, vertex_tr, vertex_br); + set_clip_plane (framebuffer, GL_CLIP_PLANE2, vertex_br, vertex_bl); + set_clip_plane (framebuffer, GL_CLIP_PLANE3, vertex_bl, vertex_tl); + } +} + +static void +add_stencil_clip_rectangle (CoglFramebuffer *framebuffer, + CoglMatrixEntry *modelview_entry, + float x_1, + float y_1, + float x_2, + float y_2, + CoglBool first) +{ + CoglMatrixStack *projection_stack = + _cogl_framebuffer_get_projection_stack (framebuffer); + CoglContext *ctx = cogl_framebuffer_get_context (framebuffer); + + /* NB: This can be called while flushing the journal so we need + * to be very conservative with what state we change. + */ + + _cogl_context_set_current_projection_entry (ctx, + projection_stack->last_entry); + _cogl_context_set_current_modelview_entry (ctx, modelview_entry); + + if (first) + { + GE( ctx, glEnable (GL_STENCIL_TEST) ); + + /* Initially disallow everything */ + GE( ctx, glClearStencil (0) ); + GE( ctx, glClear (GL_STENCIL_BUFFER_BIT) ); + + /* Punch out a hole to allow the rectangle */ + GE( ctx, glStencilFunc (GL_NEVER, 0x1, 0x1) ); + GE( ctx, glStencilOp (GL_REPLACE, GL_REPLACE, GL_REPLACE) ); + + _cogl_rectangle_immediate (framebuffer, + ctx->stencil_pipeline, + x_1, y_1, x_2, y_2); + } + else + { + /* Add one to every pixel of the stencil buffer in the + rectangle */ + GE( ctx, glStencilFunc (GL_NEVER, 0x1, 0x3) ); + GE( ctx, glStencilOp (GL_INCR, GL_INCR, GL_INCR) ); + _cogl_rectangle_immediate (framebuffer, + ctx->stencil_pipeline, + x_1, y_1, x_2, y_2); + + /* 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( ctx, glStencilOp (GL_DECR, GL_DECR, GL_DECR) ); + + _cogl_context_set_current_projection_entry (ctx, &ctx->identity_entry); + _cogl_context_set_current_modelview_entry (ctx, &ctx->identity_entry); + + _cogl_rectangle_immediate (framebuffer, + ctx->stencil_pipeline, + -1.0, -1.0, 1.0, 1.0); + } + + /* Restore the stencil mode */ + GE( ctx, glStencilFunc (GL_EQUAL, 0x1, 0x1) ); + GE( ctx, glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP) ); +} + +typedef void (*SilhouettePaintCallback) (CoglFramebuffer *framebuffer, + CoglPipeline *pipeline, + void *user_data); + +static void +add_stencil_clip_silhouette (CoglFramebuffer *framebuffer, + SilhouettePaintCallback silhouette_callback, + CoglMatrixEntry *modelview_entry, + float bounds_x1, + float bounds_y1, + float bounds_x2, + float bounds_y2, + CoglBool merge, + CoglBool need_clear, + void *user_data) +{ + CoglMatrixStack *projection_stack = + _cogl_framebuffer_get_projection_stack (framebuffer); + CoglContext *ctx = cogl_framebuffer_get_context (framebuffer); + + /* NB: This can be called while flushing the journal so we need + * to be very conservative with what state we change. + */ + + _cogl_context_set_current_projection_entry (ctx, + projection_stack->last_entry); + _cogl_context_set_current_modelview_entry (ctx, modelview_entry); + + _cogl_pipeline_flush_gl_state (ctx->stencil_pipeline, framebuffer, FALSE, 0); + + GE( ctx, glEnable (GL_STENCIL_TEST) ); + + GE( ctx, glColorMask (FALSE, FALSE, FALSE, FALSE) ); + GE( ctx, glDepthMask (FALSE) ); + + if (merge) + { + GE (ctx, glStencilMask (2)); + GE (ctx, glStencilFunc (GL_LEQUAL, 0x2, 0x6)); + } + else + { + /* If we're not using the stencil buffer for clipping then we + don't need to clear the whole stencil buffer, just the area + that will be drawn */ + if (need_clear) + /* If this is being called from the clip stack code then it + will have set up a scissor for the minimum bounding box of + all of the clips. That box will likely mean that this + _cogl_clear won't need to clear the entire + buffer. _cogl_framebuffer_clear_without_flush4f is used instead + of cogl_clear because it won't try to flush the journal */ + _cogl_framebuffer_clear_without_flush4f (framebuffer, + COGL_BUFFER_BIT_STENCIL, + 0, 0, 0, 0); + else + { + /* Just clear the bounding box */ + GE( ctx, glStencilMask (~(GLuint) 0) ); + GE( ctx, glStencilOp (GL_ZERO, GL_ZERO, GL_ZERO) ); + _cogl_rectangle_immediate (framebuffer, + ctx->stencil_pipeline, + bounds_x1, bounds_y1, + bounds_x2, bounds_y2); + } + GE (ctx, glStencilMask (1)); + GE (ctx, glStencilFunc (GL_LEQUAL, 0x1, 0x3)); + } + + GE (ctx, glStencilOp (GL_INVERT, GL_INVERT, GL_INVERT)); + + silhouette_callback (framebuffer, ctx->stencil_pipeline, user_data); + + if (merge) + { + /* Now we have the new stencil buffer in bit 1 and the old + stencil buffer in bit 0 so we need to intersect them */ + GE (ctx, glStencilMask (3)); + GE (ctx, glStencilFunc (GL_NEVER, 0x2, 0x3)); + GE (ctx, glStencilOp (GL_DECR, GL_DECR, GL_DECR)); + /* Decrement all of the bits twice so that only pixels where the + value is 3 will remain */ + + _cogl_context_set_current_projection_entry (ctx, &ctx->identity_entry); + _cogl_context_set_current_modelview_entry (ctx, &ctx->identity_entry); + + _cogl_rectangle_immediate (framebuffer, ctx->stencil_pipeline, + -1.0, -1.0, 1.0, 1.0); + _cogl_rectangle_immediate (framebuffer, ctx->stencil_pipeline, + -1.0, -1.0, 1.0, 1.0); + } + + GE (ctx, glStencilMask (~(GLuint) 0)); + GE (ctx, glDepthMask (TRUE)); + GE (ctx, glColorMask (TRUE, TRUE, TRUE, TRUE)); + + GE (ctx, glStencilFunc (GL_EQUAL, 0x1, 0x1)); + GE (ctx, glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP)); +} + +static void +paint_path_silhouette (CoglFramebuffer *framebuffer, + CoglPipeline *pipeline, + void *user_data) +{ + CoglPath *path = user_data; + if (path->data->path_nodes->len >= 3) + _cogl_path_fill_nodes (path, + framebuffer, + pipeline, + COGL_DRAW_SKIP_JOURNAL_FLUSH | + COGL_DRAW_SKIP_PIPELINE_VALIDATION | + COGL_DRAW_SKIP_FRAMEBUFFER_FLUSH); +} + +static void +add_stencil_clip_path (CoglFramebuffer *framebuffer, + CoglMatrixEntry *modelview_entry, + CoglPath *path, + CoglBool merge, + CoglBool need_clear) +{ + CoglPathData *data = path->data; + add_stencil_clip_silhouette (framebuffer, + paint_path_silhouette, + modelview_entry, + data->path_nodes_min.x, + data->path_nodes_min.y, + data->path_nodes_max.x, + data->path_nodes_max.y, + merge, + need_clear, + path); +} + +static void +paint_primitive_silhouette (CoglFramebuffer *framebuffer, + CoglPipeline *pipeline, + void *user_data) +{ + _cogl_framebuffer_draw_primitive (framebuffer, + pipeline, + user_data, + COGL_DRAW_SKIP_JOURNAL_FLUSH | + COGL_DRAW_SKIP_PIPELINE_VALIDATION | + COGL_DRAW_SKIP_FRAMEBUFFER_FLUSH); +} + +static void +add_stencil_clip_primitive (CoglFramebuffer *framebuffer, + CoglMatrixEntry *modelview_entry, + CoglPrimitive *primitive, + float bounds_x1, + float bounds_y1, + float bounds_x2, + float bounds_y2, + CoglBool merge, + CoglBool need_clear) +{ + add_stencil_clip_silhouette (framebuffer, + paint_primitive_silhouette, + modelview_entry, + bounds_x1, + bounds_y1, + bounds_x2, + bounds_y2, + merge, + need_clear, + primitive); +} + +static void +enable_clip_planes (CoglContext *ctx) +{ + GE( ctx, glEnable (GL_CLIP_PLANE0) ); + GE( ctx, glEnable (GL_CLIP_PLANE1) ); + GE( ctx, glEnable (GL_CLIP_PLANE2) ); + GE( ctx, glEnable (GL_CLIP_PLANE3) ); +} + +static void +disable_clip_planes (CoglContext *ctx) +{ + GE( ctx, glDisable (GL_CLIP_PLANE3) ); + GE( ctx, glDisable (GL_CLIP_PLANE2) ); + GE( ctx, glDisable (GL_CLIP_PLANE1) ); + GE( ctx, glDisable (GL_CLIP_PLANE0) ); +} + +void +_cogl_clip_stack_gl_flush (CoglClipStack *stack, + CoglFramebuffer *framebuffer) +{ + CoglContext *ctx = framebuffer->context; + int has_clip_planes; + CoglBool using_clip_planes = FALSE; + CoglBool using_stencil_buffer = FALSE; + int scissor_x0; + int scissor_y0; + int scissor_x1; + int scissor_y1; + CoglClipStack *entry; + int scissor_y_start; + + /* If we have already flushed this state then we don't need to do + anything */ + if (ctx->current_clip_stack_valid) + { + if (ctx->current_clip_stack == stack) + return; + + _cogl_clip_stack_unref (ctx->current_clip_stack); + } + + ctx->current_clip_stack_valid = TRUE; + ctx->current_clip_stack = _cogl_clip_stack_ref (stack); + + has_clip_planes = + ctx->private_feature_flags & COGL_PRIVATE_FEATURE_FOUR_CLIP_PLANES; + + if (has_clip_planes) + disable_clip_planes (ctx); + GE( ctx, glDisable (GL_STENCIL_TEST) ); + + /* If the stack is empty then there's nothing else to do */ + if (stack == NULL) + { + COGL_NOTE (CLIPPING, "Flushed empty clip stack"); + + ctx->current_clip_stack_uses_stencil = FALSE; + GE (ctx, glDisable (GL_SCISSOR_TEST)); + return; + } + + /* Calculate the scissor rect first so that if we eventually have to + clear the stencil buffer then the clear will be clipped to the + intersection of all of the bounding boxes. This saves having to + clear the whole stencil buffer */ + _cogl_clip_stack_get_bounds (stack, + &scissor_x0, &scissor_y0, + &scissor_x1, &scissor_y1); + + /* Enable scissoring as soon as possible */ + if (scissor_x0 >= scissor_x1 || scissor_y0 >= scissor_y1) + scissor_x0 = scissor_y0 = scissor_x1 = scissor_y1 = scissor_y_start = 0; + else + { + /* We store the entry coordinates in Cogl coordinate space + * but OpenGL requires the window origin to be the bottom + * left so we may need to convert the incoming coordinates. + * + * NB: Cogl forces all offscreen rendering to be done upside + * down so in this case no conversion is needed. + */ + + if (cogl_is_offscreen (framebuffer)) + scissor_y_start = scissor_y0; + else + { + int framebuffer_height = + cogl_framebuffer_get_height (framebuffer); + + scissor_y_start = framebuffer_height - scissor_y1; + } + } + + COGL_NOTE (CLIPPING, "Flushing scissor to (%i, %i, %i, %i)", + scissor_x0, scissor_y0, + scissor_x1, scissor_y1); + + GE (ctx, glEnable (GL_SCISSOR_TEST)); + GE (ctx, glScissor (scissor_x0, scissor_y_start, + scissor_x1 - scissor_x0, + scissor_y1 - scissor_y0)); + + /* Add all of the entries. This will end up adding them in the + reverse order that they were specified but as all of the clips + are intersecting it should work out the same regardless of the + order */ + for (entry = stack; entry; entry = entry->parent) + { + switch (entry->type) + { + case COGL_CLIP_STACK_PATH: + { + CoglClipStackPath *path_entry = (CoglClipStackPath *) entry; + + COGL_NOTE (CLIPPING, "Adding stencil clip for path"); + + add_stencil_clip_path (framebuffer, + path_entry->matrix_entry, + path_entry->path, + using_stencil_buffer, + TRUE); + + using_stencil_buffer = TRUE; + break; + } + case COGL_CLIP_STACK_PRIMITIVE: + { + CoglClipStackPrimitive *primitive_entry = + (CoglClipStackPrimitive *) entry; + + COGL_NOTE (CLIPPING, "Adding stencil clip for primitive"); + + add_stencil_clip_primitive (framebuffer, + primitive_entry->matrix_entry, + primitive_entry->primitive, + primitive_entry->bounds_x1, + primitive_entry->bounds_y1, + primitive_entry->bounds_x2, + primitive_entry->bounds_y2, + using_stencil_buffer, + TRUE); + + using_stencil_buffer = TRUE; + break; + } + case COGL_CLIP_STACK_RECT: + { + CoglClipStackRect *rect = (CoglClipStackRect *) entry; + + /* We don't need to do anything extra if the clip for this + rectangle was entirely described by its scissor bounds */ + if (!rect->can_be_scissor) + { + /* If we support clip planes and we haven't already used + them then use that instead */ + if (has_clip_planes) + { + COGL_NOTE (CLIPPING, + "Adding clip planes clip for rectangle"); + + set_clip_planes (framebuffer, + rect->matrix_entry, + rect->x0, + rect->y0, + rect->x1, + rect->y1); + using_clip_planes = TRUE; + /* We can't use clip planes a second time */ + has_clip_planes = FALSE; + } + else + { + COGL_NOTE (CLIPPING, "Adding stencil clip for rectangle"); + + add_stencil_clip_rectangle (framebuffer, + rect->matrix_entry, + rect->x0, + rect->y0, + rect->x1, + rect->y1, + !using_stencil_buffer); + using_stencil_buffer = TRUE; + } + } + break; + } + case COGL_CLIP_STACK_WINDOW_RECT: + break; + /* We don't need to do anything for window space rectangles because + * their functionality is entirely implemented by the entry bounding + * box */ + } + } + + /* Enabling clip planes is delayed to now so that they won't affect + setting up the stencil buffer */ + if (using_clip_planes) + enable_clip_planes (ctx); + + ctx->current_clip_stack_uses_stencil = using_stencil_buffer; +} diff --git a/cogl/driver/gl/gl/cogl-driver-gl.c b/cogl/driver/gl/gl/cogl-driver-gl.c index c4674e37..aadef36e 100644 --- a/cogl/driver/gl/gl/cogl-driver-gl.c +++ b/cogl/driver/gl/gl/cogl-driver-gl.c @@ -36,6 +36,7 @@ #include "cogl-framebuffer-gl-private.h" #include "cogl-texture-2d-gl-private.h" #include "cogl-attribute-gl-private.h" +#include "cogl-clip-stack-gl-private.h" static CoglBool _cogl_driver_pixel_format_from_gl_internal (CoglContext *context, @@ -549,4 +550,5 @@ _cogl_driver_gl = _cogl_texture_2d_gl_copy_from_bitmap, _cogl_texture_2d_gl_get_data, _cogl_gl_flush_attributes_state, + _cogl_clip_stack_gl_flush, }; diff --git a/cogl/driver/gl/gles/cogl-driver-gles.c b/cogl/driver/gl/gles/cogl-driver-gles.c index b5487fbf..e2dbee5a 100644 --- a/cogl/driver/gl/gles/cogl-driver-gles.c +++ b/cogl/driver/gl/gles/cogl-driver-gles.c @@ -35,6 +35,7 @@ #include "cogl-framebuffer-gl-private.h" #include "cogl-texture-2d-gl-private.h" #include "cogl-attribute-gl-private.h" +#include "cogl-clip-stack-gl-private.h" #ifndef GL_UNSIGNED_INT_24_8 #define GL_UNSIGNED_INT_24_8 0x84FA @@ -367,4 +368,5 @@ _cogl_driver_gles = _cogl_texture_2d_gl_copy_from_bitmap, NULL, /* texture_2d_get_data */ _cogl_gl_flush_attributes_state, + _cogl_clip_stack_gl_flush, }; |