/* * Cogl * * An object oriented GL/GLES Abstraction/Utility Layer * * Copyright (C) 2007,2008,2009,2010 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 * . * * Authors: * Ivan Leben * Øyvind Kolås * Neil Roberts * Robert Bragg */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "cogl.h" #include "cogl-object.h" #include "cogl-internal.h" #include "cogl-context-private.h" #include "cogl-journal-private.h" #include "cogl-pipeline-private.h" #include "cogl-pipeline-opengl-private.h" #include "cogl-framebuffer-private.h" #include "cogl-path-private.h" #include "cogl-texture-private.h" #include "cogl-primitives-private.h" #include "cogl-private.h" #include "cogl-attribute-private.h" #include "tesselator/tesselator.h" #include #include #define _COGL_MAX_BEZ_RECURSE_DEPTH 16 static void _cogl_path_free (CoglPath *path); static void _cogl_path_build_fill_attribute_buffer (CoglPath *path); static void _cogl_path_build_stroke_attribute_buffer (CoglPath *path); COGL_OBJECT_DEFINE (Path, path); static void _cogl_path_data_clear_vbos (CoglPathData *data) { int i; if (data->fill_attribute_buffer) { cogl_object_unref (data->fill_attribute_buffer); cogl_object_unref (data->fill_vbo_indices); for (i = 0; i < COGL_PATH_N_ATTRIBUTES; i++) cogl_object_unref (data->fill_attributes[i]); data->fill_attribute_buffer = NULL; } if (data->stroke_attribute_buffer) { cogl_object_unref (data->stroke_attribute_buffer); for (i = 0; i < data->stroke_n_attributes; i++) cogl_object_unref (data->stroke_attributes[i]); g_free (data->stroke_attributes); data->stroke_attribute_buffer = NULL; } } static void _cogl_path_data_unref (CoglPathData *data) { if (--data->ref_count <= 0) { _cogl_path_data_clear_vbos (data); g_array_free (data->path_nodes, TRUE); g_slice_free (CoglPathData, data); } } static void _cogl_path_modify (CoglPath *path) { /* This needs to be called whenever the path is about to be modified to implement copy-on-write semantics */ /* If there is more than one path using the data then we need to copy the data instead */ if (path->data->ref_count != 1) { CoglPathData *old_data = path->data; path->data = g_slice_dup (CoglPathData, old_data); path->data->path_nodes = g_array_new (FALSE, FALSE, sizeof (CoglPathNode)); g_array_append_vals (path->data->path_nodes, old_data->path_nodes->data, old_data->path_nodes->len); path->data->fill_attribute_buffer = NULL; path->data->stroke_attribute_buffer = NULL; path->data->ref_count = 1; _cogl_path_data_unref (old_data); } else /* The path is altered so the vbos will now be invalid */ _cogl_path_data_clear_vbos (path->data); } void cogl2_path_set_fill_rule (CoglPath *path, CoglPathFillRule fill_rule) { g_return_if_fail (cogl_is_path (path)); if (path->data->fill_rule != fill_rule) { _cogl_path_modify (path); path->data->fill_rule = fill_rule; } } CoglPathFillRule cogl2_path_get_fill_rule (CoglPath *path) { g_return_val_if_fail (cogl_is_path (path), COGL_PATH_FILL_RULE_NON_ZERO); return path->data->fill_rule; } static void _cogl_path_add_node (CoglPath *path, gboolean new_sub_path, float x, float y) { CoglPathNode new_node; CoglPathData *data; _cogl_path_modify (path); data = path->data; new_node.x = x; new_node.y = y; new_node.path_size = 0; if (new_sub_path || data->path_nodes->len == 0) data->last_path = data->path_nodes->len; g_array_append_val (data->path_nodes, new_node); g_array_index (data->path_nodes, CoglPathNode, data->last_path).path_size++; if (data->path_nodes->len == 1) { data->path_nodes_min.x = data->path_nodes_max.x = x; data->path_nodes_min.y = data->path_nodes_max.y = y; } else { if (x < data->path_nodes_min.x) data->path_nodes_min.x = x; if (x > data->path_nodes_max.x) data->path_nodes_max.x = x; if (y < data->path_nodes_min.y) data->path_nodes_min.y = y; if (y > data->path_nodes_max.y) data->path_nodes_max.y = y; } /* Once the path nodes have been modified then we'll assume it's no longer a rectangle. cogl2_path_rectangle will set this back to TRUE if this has been called from there */ data->is_rectangle = FALSE; } static void _cogl_path_stroke_nodes (CoglPath *path) { CoglPathData *data = path->data; CoglPipeline *copy = NULL; CoglPipeline *source; unsigned int path_start; int path_num = 0; CoglPathNode *node; _COGL_GET_CONTEXT (ctx, NO_RETVAL); source = cogl_get_source (); if (cogl_pipeline_get_n_layers (source) != 0) { copy = cogl_pipeline_copy (source); _cogl_pipeline_prune_to_n_layers (copy, 0); source = copy; } _cogl_path_build_stroke_attribute_buffer (path); cogl_push_source (source); for (path_start = 0; path_start < data->path_nodes->len; path_start += node->path_size) { node = &g_array_index (data->path_nodes, CoglPathNode, path_start); cogl_vdraw_attributes (COGL_VERTICES_MODE_LINE_STRIP, 0, node->path_size, data->stroke_attributes[path_num], NULL); path_num++; } cogl_pop_source (); if (copy) cogl_object_unref (copy); } void _cogl_path_get_bounds (CoglPath *path, float *min_x, float *min_y, float *max_x, float *max_y) { CoglPathData *data = path->data; if (data->path_nodes->len == 0) { *min_x = 0.0f; *min_y = 0.0f; *max_x = 0.0f; *max_y = 0.0f; } else { *min_x = data->path_nodes_min.x; *min_y = data->path_nodes_min.y; *max_x = data->path_nodes_max.x; *max_y = data->path_nodes_max.y; } } static void _cogl_path_fill_nodes_with_clipped_rectangle (CoglPath *path) { _COGL_GET_CONTEXT (ctx, NO_RETVAL); if (!cogl_features_available (COGL_FEATURE_STENCIL_BUFFER)) { static gboolean seen_warning = FALSE; if (!seen_warning) { g_warning ("Paths can not be filled using materials with " "sliced textures unless there is a stencil " "buffer"); seen_warning = TRUE; } } cogl_clip_push_from_path (path); cogl_rectangle (path->data->path_nodes_min.x, path->data->path_nodes_min.y, path->data->path_nodes_max.x, path->data->path_nodes_max.y); cogl_clip_pop (); } static gboolean validate_layer_cb (CoglPipelineLayer *layer, void *user_data) { gboolean *needs_fallback = user_data; CoglTexture *texture = _cogl_pipeline_layer_get_texture (layer); /* If any of the layers of the current pipeline contain sliced * textures or textures with waste then it won't work to draw the * path directly. Instead we fallback to pushing the path as a clip * on the clip-stack and drawing the path's bounding rectangle * instead. */ if (texture != NULL && (cogl_texture_is_sliced (texture) || !_cogl_texture_can_hardware_repeat (texture))) *needs_fallback = TRUE; return !*needs_fallback; } static void _cogl_path_fill_nodes (CoglPath *path, CoglDrawFlags flags) { gboolean needs_fallback = FALSE; _cogl_pipeline_foreach_layer_internal (cogl_get_source (), validate_layer_cb, &needs_fallback); if (needs_fallback) { _cogl_path_fill_nodes_with_clipped_rectangle (path); return; } _cogl_path_build_fill_attribute_buffer (path); _cogl_draw_indexed_attributes (COGL_VERTICES_MODE_TRIANGLES, 0, /* first_vertex */ path->data->fill_vbo_n_indices, path->data->fill_vbo_indices, path->data->fill_attributes, COGL_PATH_N_ATTRIBUTES, flags); } void _cogl_add_path_to_stencil_buffer (CoglPath *path, gboolean merge, gboolean need_clear) { CoglPathData *data = path->data; CoglFramebuffer *framebuffer = cogl_get_draw_framebuffer (); CoglMatrixStack *modelview_stack = _cogl_framebuffer_get_modelview_stack (framebuffer); CoglMatrixStack *projection_stack = _cogl_framebuffer_get_projection_stack (framebuffer); _COGL_GET_CONTEXT (ctx, NO_RETVAL); /* This can be called from the clip stack code which doesn't flush the matrix stacks between calls so we need to ensure they're flushed now */ _cogl_matrix_stack_flush_to_gl (modelview_stack, COGL_MATRIX_MODELVIEW); _cogl_matrix_stack_flush_to_gl (projection_stack, COGL_MATRIX_PROJECTION); /* Just setup a simple pipeline that doesn't use texturing... */ _cogl_push_source (ctx->stencil_pipeline, FALSE); _cogl_pipeline_flush_gl_state (ctx->stencil_pipeline, 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 (data->path_nodes_min.x, data->path_nodes_min.y, data->path_nodes_max.x, data->path_nodes_max.y); } GE (ctx, glStencilMask (1)); GE (ctx, glStencilFunc (GL_LEQUAL, 0x1, 0x3)); } GE (ctx, glStencilOp (GL_INVERT, GL_INVERT, GL_INVERT)); if (path->data->path_nodes->len >= 3) _cogl_path_fill_nodes (path, COGL_DRAW_SKIP_JOURNAL_FLUSH | COGL_DRAW_SKIP_PIPELINE_VALIDATION | COGL_DRAW_SKIP_FRAMEBUFFER_FLUSH); 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_matrix_stack_push (projection_stack); _cogl_matrix_stack_load_identity (projection_stack); _cogl_matrix_stack_flush_to_gl (projection_stack, COGL_MATRIX_PROJECTION); _cogl_matrix_stack_push (modelview_stack); _cogl_matrix_stack_load_identity (modelview_stack); _cogl_matrix_stack_flush_to_gl (modelview_stack, COGL_MATRIX_MODELVIEW); _cogl_rectangle_immediate (-1.0, -1.0, 1.0, 1.0); _cogl_rectangle_immediate (-1.0, -1.0, 1.0, 1.0); _cogl_matrix_stack_pop (modelview_stack); _cogl_matrix_stack_pop (projection_stack); } 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)); /* restore the original pipeline */ cogl_pop_source (); } void cogl2_path_fill (CoglPath *path) { g_return_if_fail (cogl_is_path (path)); if (path->data->path_nodes->len == 0) return; /* If the path is a simple rectangle then we can divert to using cogl_rectangle which should be faster because it can go through the journal instead of uploading the geometry just for two triangles */ if (path->data->is_rectangle) { float x_1, y_1, x_2, y_2; _cogl_path_get_bounds (path, &x_1, &y_1, &x_2, &y_2); cogl_rectangle (x_1, y_1, x_2, y_2); } else _cogl_path_fill_nodes (path, 0); } void cogl2_path_stroke (CoglPath *path) { g_return_if_fail (cogl_is_path (path)); if (path->data->path_nodes->len == 0) return; _cogl_path_stroke_nodes (path); } void cogl2_path_move_to (CoglPath *path, float x, float y) { CoglPathData *data; g_return_if_fail (cogl_is_path (path)); _cogl_path_add_node (path, TRUE, x, y); data = path->data; data->path_start.x = x; data->path_start.y = y; data->path_pen = data->path_start; } void cogl2_path_rel_move_to (CoglPath *path, float x, float y) { CoglPathData *data; g_return_if_fail (cogl_is_path (path)); data = path->data; cogl2_path_move_to (path, data->path_pen.x + x, data->path_pen.y + y); } void cogl2_path_line_to (CoglPath *path, float x, float y) { CoglPathData *data; g_return_if_fail (cogl_is_path (path)); _cogl_path_add_node (path, FALSE, x, y); data = path->data; data->path_pen.x = x; data->path_pen.y = y; } void cogl2_path_rel_line_to (CoglPath *path, float x, float y) { CoglPathData *data; g_return_if_fail (cogl_is_path (path)); data = path->data; cogl2_path_line_to (path, data->path_pen.x + x, data->path_pen.y + y); } void cogl2_path_close (CoglPath *path) { g_return_if_fail (cogl_is_path (path)); _cogl_path_add_node (path, FALSE, path->data->path_start.x, path->data->path_start.y); path->data->path_pen = path->data->path_start; } void cogl2_path_line (CoglPath *path, float x_1, float y_1, float x_2, float y_2) { cogl2_path_move_to (path, x_1, y_1); cogl2_path_line_to (path, x_2, y_2); } void cogl2_path_polyline (CoglPath *path, const float *coords, int num_points) { int c = 0; g_return_if_fail (cogl_is_path (path)); cogl2_path_move_to (path, coords[0], coords[1]); for (c = 1; c < num_points; ++c) cogl2_path_line_to (path, coords[2*c], coords[2*c+1]); } void cogl2_path_polygon (CoglPath *path, const float *coords, int num_points) { cogl2_path_polyline (path, coords, num_points); cogl2_path_close (path); } void cogl2_path_rectangle (CoglPath *path, float x_1, float y_1, float x_2, float y_2) { gboolean is_rectangle; /* If the path was previously empty and the rectangle isn't mirrored then we'll record that this is a simple rectangle path so that we can optimise it */ is_rectangle = (path->data->path_nodes->len == 0 && x_2 >= x_1 && y_2 >= y_1); cogl2_path_move_to (path, x_1, y_1); cogl2_path_line_to (path, x_2, y_1); cogl2_path_line_to (path, x_2, y_2); cogl2_path_line_to (path, x_1, y_2); cogl2_path_close (path); path->data->is_rectangle = is_rectangle; } gboolean _cogl_path_is_rectangle (CoglPath *path) { return path->data->is_rectangle; } static void _cogl_path_arc (CoglPath *path, float center_x, float center_y, float radius_x, float radius_y, float angle_1, float angle_2, float angle_step, unsigned int move_first) { float a = 0x0; float cosa = 0x0; float sina = 0x0; float px = 0x0; float py = 0x0; /* Fix invalid angles */ if (angle_1 == angle_2 || angle_step == 0x0) return; if (angle_step < 0x0) angle_step = -angle_step; /* Walk the arc by given step */ a = angle_1; while (a != angle_2) { cosa = cosf (a * (G_PI/180.0)); sina = sinf (a * (G_PI/180.0)); px = center_x + (cosa * radius_x); py = center_y + (sina * radius_y); if (a == angle_1 && move_first) cogl2_path_move_to (path, px, py); else cogl2_path_line_to (path, px, py); if (G_LIKELY (angle_2 > angle_1)) { a += angle_step; if (a > angle_2) a = angle_2; } else { a -= angle_step; if (a < angle_2) a = angle_2; } } /* Make sure the final point is drawn */ cosa = cosf (angle_2 * (G_PI/180.0)); sina = sinf (angle_2 * (G_PI/180.0)); px = center_x + (cosa * radius_x); py = center_y + (sina * radius_y); cogl2_path_line_to (path, px, py); } void cogl2_path_arc (CoglPath *path, float center_x, float center_y, float radius_x, float radius_y, float angle_1, float angle_2) { float angle_step = 10; g_return_if_fail (cogl_is_path (path)); /* it is documented that a move to is needed to create a freestanding * arc */ _cogl_path_arc (path, center_x, center_y, radius_x, radius_y, angle_1, angle_2, angle_step, 0 /* no move */); } static void _cogl_path_rel_arc (CoglPath *path, float center_x, float center_y, float radius_x, float radius_y, float angle_1, float angle_2, float angle_step) { CoglPathData *data; data = path->data; _cogl_path_arc (path, data->path_pen.x + center_x, data->path_pen.y + center_y, radius_x, radius_y, angle_1, angle_2, angle_step, 0 /* no move */); } void cogl2_path_ellipse (CoglPath *path, float center_x, float center_y, float radius_x, float radius_y) { float angle_step = 10; g_return_if_fail (cogl_is_path (path)); /* FIXME: if shows to be slow might be optimized * by mirroring just a quarter of it */ _cogl_path_arc (path, center_x, center_y, radius_x, radius_y, 0, 360, angle_step, 1 /* move first */); cogl2_path_close (path); } void cogl2_path_round_rectangle (CoglPath *path, float x_1, float y_1, float x_2, float y_2, float radius, float arc_step) { float inner_width = x_2 - x_1 - radius * 2; float inner_height = y_2 - y_1 - radius * 2; g_return_if_fail (cogl_is_path (path)); cogl2_path_move_to (path, x_1, y_1 + radius); _cogl_path_rel_arc (path, radius, 0, radius, radius, 180, 270, arc_step); cogl2_path_line_to (path, path->data->path_pen.x + inner_width, path->data->path_pen.y); _cogl_path_rel_arc (path, 0, radius, radius, radius, -90, 0, arc_step); cogl2_path_line_to (path, path->data->path_pen.x, path->data->path_pen.y + inner_height); _cogl_path_rel_arc (path, -radius, 0, radius, radius, 0, 90, arc_step); cogl2_path_line_to (path, path->data->path_pen.x - inner_width, path->data->path_pen.y); _cogl_path_rel_arc (path, 0, -radius, radius, radius, 90, 180, arc_step); cogl2_path_close (path); } static void _cogl_path_bezier3_sub (CoglPath *path, CoglBezCubic *cubic) { CoglBezCubic cubics[_COGL_MAX_BEZ_RECURSE_DEPTH]; CoglBezCubic *cleft; CoglBezCubic *cright; CoglBezCubic *c; floatVec2 dif1; floatVec2 dif2; floatVec2 mm; floatVec2 c1; floatVec2 c2; floatVec2 c3; floatVec2 c4; floatVec2 c5; int cindex; /* Put first curve on stack */ cubics[0] = *cubic; cindex = 0; while (cindex >= 0) { c = &cubics[cindex]; /* Calculate distance of control points from their * counterparts on the line between end points */ dif1.x = (c->p2.x * 3) - (c->p1.x * 2) - c->p4.x; dif1.y = (c->p2.y * 3) - (c->p1.y * 2) - c->p4.y; dif2.x = (c->p3.x * 3) - (c->p4.x * 2) - c->p1.x; dif2.y = (c->p3.y * 3) - (c->p4.y * 2) - c->p1.y; if (dif1.x < 0) dif1.x = -dif1.x; if (dif1.y < 0) dif1.y = -dif1.y; if (dif2.x < 0) dif2.x = -dif2.x; if (dif2.y < 0) dif2.y = -dif2.y; /* Pick the greatest of two distances */ if (dif1.x < dif2.x) dif1.x = dif2.x; if (dif1.y < dif2.y) dif1.y = dif2.y; /* Cancel if the curve is flat enough */ if (dif1.x + dif1.y <= 1.0 || cindex == _COGL_MAX_BEZ_RECURSE_DEPTH-1) { /* Add subdivision point (skip last) */ if (cindex == 0) return; _cogl_path_add_node (path, FALSE, c->p4.x, c->p4.y); --cindex; continue; } /* Left recursion goes on top of stack! */ cright = c; cleft = &cubics[++cindex]; /* Subdivide into 2 sub-curves */ c1.x = ((c->p1.x + c->p2.x) / 2); c1.y = ((c->p1.y + c->p2.y) / 2); mm.x = ((c->p2.x + c->p3.x) / 2); mm.y = ((c->p2.y + c->p3.y) / 2); c5.x = ((c->p3.x + c->p4.x) / 2); c5.y = ((c->p3.y + c->p4.y) / 2); c2.x = ((c1.x + mm.x) / 2); c2.y = ((c1.y + mm.y) / 2); c4.x = ((mm.x + c5.x) / 2); c4.y = ((mm.y + c5.y) / 2); c3.x = ((c2.x + c4.x) / 2); c3.y = ((c2.y + c4.y) / 2); /* Add left recursion to stack */ cleft->p1 = c->p1; cleft->p2 = c1; cleft->p3 = c2; cleft->p4 = c3; /* Add right recursion to stack */ cright->p1 = c3; cright->p2 = c4; cright->p3 = c5; cright->p4 = c->p4; } } void cogl2_path_curve_to (CoglPath *path, float x_1, float y_1, float x_2, float y_2, float x_3, float y_3) { CoglBezCubic cubic; g_return_if_fail (cogl_is_path (path)); /* Prepare cubic curve */ cubic.p1 = path->data->path_pen; cubic.p2.x = x_1; cubic.p2.y = y_1; cubic.p3.x = x_2; cubic.p3.y = y_2; cubic.p4.x = x_3; cubic.p4.y = y_3; /* Run subdivision */ _cogl_path_bezier3_sub (path, &cubic); /* Add last point */ _cogl_path_add_node (path, FALSE, cubic.p4.x, cubic.p4.y); path->data->path_pen = cubic.p4; } void cogl2_path_rel_curve_to (CoglPath *path, float x_1, float y_1, float x_2, float y_2, float x_3, float y_3) { CoglPathData *data; g_return_if_fail (cogl_is_path (path)); data = path->data; cogl2_path_curve_to (path, data->path_pen.x + x_1, data->path_pen.y + y_1, data->path_pen.x + x_2, data->path_pen.y + y_2, data->path_pen.x + x_3, data->path_pen.y + y_3); } CoglPath * cogl2_path_new (void) { CoglPath *path; CoglPathData *data; path = g_slice_new (CoglPath); data = path->data = g_slice_new (CoglPathData); data->ref_count = 1; data->fill_rule = COGL_PATH_FILL_RULE_EVEN_ODD; data->path_nodes = g_array_new (FALSE, FALSE, sizeof (CoglPathNode)); data->last_path = 0; data->fill_attribute_buffer = NULL; data->stroke_attribute_buffer = NULL; data->is_rectangle = FALSE; return _cogl_path_object_new (path); } CoglPath * cogl_path_copy (CoglPath *old_path) { CoglPath *new_path; g_return_val_if_fail (cogl_is_path (old_path), NULL); new_path = g_slice_new (CoglPath); new_path->data = old_path->data; new_path->data->ref_count++; return _cogl_path_object_new (new_path); } static void _cogl_path_free (CoglPath *path) { _cogl_path_data_unref (path->data); g_slice_free (CoglPath, path); } /* If second order beziers were needed the following code could * be re-enabled: */ #if 0 static void _cogl_path_bezier2_sub (CoglPath *path, CoglBezQuad *quad) { CoglBezQuad quads[_COGL_MAX_BEZ_RECURSE_DEPTH]; CoglBezQuad *qleft; CoglBezQuad *qright; CoglBezQuad *q; floatVec2 mid; floatVec2 dif; floatVec2 c1; floatVec2 c2; floatVec2 c3; int qindex; /* Put first curve on stack */ quads[0] = *quad; qindex = 0; /* While stack is not empty */ while (qindex >= 0) { q = &quads[qindex]; /* Calculate distance of control point from its * counterpart on the line between end points */ mid.x = ((q->p1.x + q->p3.x) / 2); mid.y = ((q->p1.y + q->p3.y) / 2); dif.x = (q->p2.x - mid.x); dif.y = (q->p2.y - mid.y); if (dif.x < 0) dif.x = -dif.x; if (dif.y < 0) dif.y = -dif.y; /* Cancel if the curve is flat enough */ if (dif.x + dif.y <= 1.0 || qindex == _COGL_MAX_BEZ_RECURSE_DEPTH - 1) { /* Add subdivision point (skip last) */ if (qindex == 0) return; _cogl_path_add_node (path, FALSE, q->p3.x, q->p3.y); --qindex; continue; } /* Left recursion goes on top of stack! */ qright = q; qleft = &quads[++qindex]; /* Subdivide into 2 sub-curves */ c1.x = ((q->p1.x + q->p2.x) / 2); c1.y = ((q->p1.y + q->p2.y) / 2); c3.x = ((q->p2.x + q->p3.x) / 2); c3.y = ((q->p2.y + q->p3.y) / 2); c2.x = ((c1.x + c3.x) / 2); c2.y = ((c1.y + c3.y) / 2); /* Add left recursion onto stack */ qleft->p1 = q->p1; qleft->p2 = c1; qleft->p3 = c2; /* Add right recursion onto stack */ qright->p1 = c2; qright->p2 = c3; qright->p3 = q->p3; } } void cogl_path_curve2_to (CoglPath *path, float x_1, float y_1, float x_2, float y_2) { CoglBezQuad quad; /* Prepare quadratic curve */ quad.p1 = path->data->path_pen; quad.p2.x = x_1; quad.p2.y = y_1; quad.p3.x = x_2; quad.p3.y = y_2; /* Run subdivision */ _cogl_path_bezier2_sub (&quad); /* Add last point */ _cogl_path_add_node (FALSE, quad.p3.x, quad.p3.y); path->data->path_pen = quad.p3; } void cogl_rel_curve2_to (CoglPath *path, float x_1, float y_1, float x_2, float y_2) { CoglPathData *data; g_return_if_fail (cogl_is_path (path)); data = path->data; cogl_path_curve2_to (data->path_pen.x + x_1, data->path_pen.y + y_1, data->path_pen.x + x_2, data->path_pen.y + y_2); } #endif typedef struct _CoglPathTesselator CoglPathTesselator; typedef struct _CoglPathTesselatorVertex CoglPathTesselatorVertex; struct _CoglPathTesselator { GLUtesselator *glu_tess; GLenum primitive_type; int vertex_number; /* Array of CoglPathTesselatorVertex. This needs to grow when the combine callback is called */ GArray *vertices; /* Array of integers for the indices into the vertices array. Each element will either be guint8, guint16 or guint32 depending on the number of vertices */ GArray *indices; CoglIndicesType indices_type; /* Indices used to split fans and strips */ int index_a, index_b; }; struct _CoglPathTesselatorVertex { float x, y, s, t; }; static void _cogl_path_tesselator_begin (GLenum type, CoglPathTesselator *tess) { g_assert (type == GL_TRIANGLES || type == GL_TRIANGLE_FAN || type == GL_TRIANGLE_STRIP); tess->primitive_type = type; tess->vertex_number = 0; } static CoglIndicesType _cogl_path_tesselator_get_indices_type_for_size (int n_vertices) { if (n_vertices <= 256) return COGL_INDICES_TYPE_UNSIGNED_BYTE; else if (n_vertices <= 65536) return COGL_INDICES_TYPE_UNSIGNED_SHORT; else return COGL_INDICES_TYPE_UNSIGNED_INT; } static void _cogl_path_tesselator_allocate_indices_array (CoglPathTesselator *tess) { switch (tess->indices_type) { case COGL_INDICES_TYPE_UNSIGNED_BYTE: tess->indices = g_array_new (FALSE, FALSE, sizeof (guint8)); break; case COGL_INDICES_TYPE_UNSIGNED_SHORT: tess->indices = g_array_new (FALSE, FALSE, sizeof (guint16)); break; case COGL_INDICES_TYPE_UNSIGNED_INT: tess->indices = g_array_new (FALSE, FALSE, sizeof (guint32)); break; } } static void _cogl_path_tesselator_add_index (CoglPathTesselator *tess, int vertex_index) { switch (tess->indices_type) { case COGL_INDICES_TYPE_UNSIGNED_BYTE: { guint8 val = vertex_index; g_array_append_val (tess->indices, val); } break; case COGL_INDICES_TYPE_UNSIGNED_SHORT: { guint16 val = vertex_index; g_array_append_val (tess->indices, val); } break; case COGL_INDICES_TYPE_UNSIGNED_INT: { guint32 val = vertex_index; g_array_append_val (tess->indices, val); } break; } } static void _cogl_path_tesselator_vertex (void *vertex_data, CoglPathTesselator *tess) { int vertex_index; vertex_index = GPOINTER_TO_INT (vertex_data); /* This tries to convert all of the primitives into GL_TRIANGLES with indices to share vertices */ switch (tess->primitive_type) { case GL_TRIANGLES: /* Directly use the vertex */ _cogl_path_tesselator_add_index (tess, vertex_index); break; case GL_TRIANGLE_FAN: if (tess->vertex_number == 0) tess->index_a = vertex_index; else if (tess->vertex_number == 1) tess->index_b = vertex_index; else { /* Create a triangle with the first vertex, the previous vertex and this vertex */ _cogl_path_tesselator_add_index (tess, tess->index_a); _cogl_path_tesselator_add_index (tess, tess->index_b); _cogl_path_tesselator_add_index (tess, vertex_index); /* Next time we will use this vertex as the previous vertex */ tess->index_b = vertex_index; } break; case GL_TRIANGLE_STRIP: if (tess->vertex_number == 0) tess->index_a = vertex_index; else if (tess->vertex_number == 1) tess->index_b = vertex_index; else { _cogl_path_tesselator_add_index (tess, tess->index_a); _cogl_path_tesselator_add_index (tess, tess->index_b); _cogl_path_tesselator_add_index (tess, vertex_index); if (tess->vertex_number & 1) tess->index_b = vertex_index; else tess->index_a = vertex_index; } break; default: g_assert_not_reached (); } tess->vertex_number++; } static void _cogl_path_tesselator_end (CoglPathTesselator *tess) { tess->primitive_type = GL_FALSE; } static void _cogl_path_tesselator_combine (double coords[3], void *vertex_data[4], float weight[4], void **out_data, CoglPathTesselator *tess) { CoglPathTesselatorVertex *vertex; CoglIndicesType new_indices_type; int i; /* Add a new vertex to the array */ g_array_set_size (tess->vertices, tess->vertices->len + 1); vertex = &g_array_index (tess->vertices, CoglPathTesselatorVertex, tess->vertices->len - 1); /* The data is just the index to the vertex */ *out_data = GINT_TO_POINTER (tess->vertices->len - 1); /* Set the coordinates of the new vertex */ vertex->x = coords[0]; vertex->y = coords[1]; /* Generate the texture coordinates as the weighted average of the four incoming coordinates */ vertex->s = 0.0f; vertex->t = 0.0f; for (i = 0; i < 4; i++) { CoglPathTesselatorVertex *old_vertex = &g_array_index (tess->vertices, CoglPathTesselatorVertex, GPOINTER_TO_INT (vertex_data[i])); vertex->s += old_vertex->s * weight[i]; vertex->t += old_vertex->t * weight[i]; } /* Check if we've reached the limit for the data type of our indices */ new_indices_type = _cogl_path_tesselator_get_indices_type_for_size (tess->vertices->len); if (new_indices_type != tess->indices_type) { CoglIndicesType old_indices_type = new_indices_type; GArray *old_vertices = tess->indices; /* Copy the indices to an array of the new type */ tess->indices_type = new_indices_type; _cogl_path_tesselator_allocate_indices_array (tess); switch (old_indices_type) { case COGL_INDICES_TYPE_UNSIGNED_BYTE: for (i = 0; i < old_vertices->len; i++) _cogl_path_tesselator_add_index (tess, g_array_index (old_vertices, guint8, i)); break; case COGL_INDICES_TYPE_UNSIGNED_SHORT: for (i = 0; i < old_vertices->len; i++) _cogl_path_tesselator_add_index (tess, g_array_index (old_vertices, guint16, i)); break; case COGL_INDICES_TYPE_UNSIGNED_INT: for (i = 0; i < old_vertices->len; i++) _cogl_path_tesselator_add_index (tess, g_array_index (old_vertices, guint32, i)); break; } g_array_free (old_vertices, TRUE); } } static void _cogl_path_build_fill_attribute_buffer (CoglPath *path) { CoglPathTesselator tess; unsigned int path_start = 0; CoglPathData *data = path->data; int i; /* If we've already got a vbo then we don't need to do anything */ if (data->fill_attribute_buffer) return; tess.primitive_type = FALSE; /* Generate a vertex for each point on the path */ tess.vertices = g_array_new (FALSE, FALSE, sizeof (CoglPathTesselatorVertex)); g_array_set_size (tess.vertices, data->path_nodes->len); for (i = 0; i < data->path_nodes->len; i++) { CoglPathNode *node = &g_array_index (data->path_nodes, CoglPathNode, i); CoglPathTesselatorVertex *vertex = &g_array_index (tess.vertices, CoglPathTesselatorVertex, i); vertex->x = node->x; vertex->y = node->y; /* Add texture coordinates so that a texture would be drawn to fit the bounding box of the path and then cropped by the path */ if (data->path_nodes_min.x == data->path_nodes_max.x) vertex->s = 0.0f; else vertex->s = ((node->x - data->path_nodes_min.x) / (data->path_nodes_max.x - data->path_nodes_min.x)); if (data->path_nodes_min.y == data->path_nodes_max.y) vertex->t = 0.0f; else vertex->t = ((node->y - data->path_nodes_min.y) / (data->path_nodes_max.y - data->path_nodes_min.y)); } tess.indices_type = _cogl_path_tesselator_get_indices_type_for_size (data->path_nodes->len); _cogl_path_tesselator_allocate_indices_array (&tess); tess.glu_tess = gluNewTess (); if (data->fill_rule == COGL_PATH_FILL_RULE_EVEN_ODD) gluTessProperty (tess.glu_tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD); else gluTessProperty (tess.glu_tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NONZERO); /* All vertices are on the xy-plane */ gluTessNormal (tess.glu_tess, 0.0, 0.0, 1.0); gluTessCallback (tess.glu_tess, GLU_TESS_BEGIN_DATA, _cogl_path_tesselator_begin); gluTessCallback (tess.glu_tess, GLU_TESS_VERTEX_DATA, _cogl_path_tesselator_vertex); gluTessCallback (tess.glu_tess, GLU_TESS_END_DATA, _cogl_path_tesselator_end); gluTessCallback (tess.glu_tess, GLU_TESS_COMBINE_DATA, _cogl_path_tesselator_combine); gluTessBeginPolygon (tess.glu_tess, &tess); while (path_start < data->path_nodes->len) { CoglPathNode *node = &g_array_index (data->path_nodes, CoglPathNode, path_start); gluTessBeginContour (tess.glu_tess); for (i = 0; i < node->path_size; i++) { double vertex[3] = { node[i].x, node[i].y, 0.0 }; gluTessVertex (tess.glu_tess, vertex, GINT_TO_POINTER (i + path_start)); } gluTessEndContour (tess.glu_tess); path_start += node->path_size; } gluTessEndPolygon (tess.glu_tess); gluDeleteTess (tess.glu_tess); data->fill_attribute_buffer = cogl_attribute_buffer_new (sizeof (CoglPathTesselatorVertex) * tess.vertices->len, tess.vertices->data); g_array_free (tess.vertices, TRUE); data->fill_attributes[0] = cogl_attribute_new (data->fill_attribute_buffer, "cogl_position_in", sizeof (CoglPathTesselatorVertex), G_STRUCT_OFFSET (CoglPathTesselatorVertex, x), 2, /* n_components */ COGL_ATTRIBUTE_TYPE_FLOAT); data->fill_attributes[1] = cogl_attribute_new (data->fill_attribute_buffer, "cogl_tex_coord0_in", sizeof (CoglPathTesselatorVertex), G_STRUCT_OFFSET (CoglPathTesselatorVertex, s), 2, /* n_components */ COGL_ATTRIBUTE_TYPE_FLOAT); data->fill_vbo_indices = cogl_indices_new (tess.indices_type, tess.indices->data, tess.indices->len); data->fill_vbo_n_indices = tess.indices->len; g_array_free (tess.indices, TRUE); } static void _cogl_path_build_stroke_attribute_buffer (CoglPath *path) { CoglPathData *data = path->data; CoglBuffer *buffer; unsigned int n_attributes = 0; unsigned int path_start; CoglPathNode *node; floatVec2 *buffer_p; unsigned int i; /* If we've already got a cached vbo then we don't need to do anything */ if (data->stroke_attribute_buffer) return; data->stroke_attribute_buffer = cogl_attribute_buffer_new (data->path_nodes->len * sizeof (floatVec2), NULL); buffer = COGL_BUFFER (data->stroke_attribute_buffer); buffer_p = _cogl_buffer_map_for_fill_or_fallback (buffer); /* Copy the vertices in and count the number of sub paths. Each sub path will form a separate attribute so we can paint the disjoint line strips */ for (path_start = 0; path_start < data->path_nodes->len; path_start += node->path_size) { node = &g_array_index (data->path_nodes, CoglPathNode, path_start); for (i = 0; i < node->path_size; i++) { buffer_p[path_start + i].x = node[i].x; buffer_p[path_start + i].y = node[i].y; } n_attributes++; } _cogl_buffer_unmap_for_fill_or_fallback (buffer); data->stroke_attributes = g_new (CoglAttribute *, n_attributes); /* Now we can loop the sub paths again to create the attributes */ for (i = 0, path_start = 0; path_start < data->path_nodes->len; i++, path_start += node->path_size) { node = &g_array_index (data->path_nodes, CoglPathNode, path_start); data->stroke_attributes[i] = cogl_attribute_new (data->stroke_attribute_buffer, "cogl_position_in", sizeof (floatVec2), path_start * sizeof (floatVec2), 2, /* n_components */ COGL_ATTRIBUTE_TYPE_FLOAT); } data->stroke_n_attributes = n_attributes; }