From fecaaea132ca2cfe449bd765035a8217c061caa8 Mon Sep 17 00:00:00 2001 From: Neil Roberts Date: Thu, 15 Apr 2010 23:56:46 +0100 Subject: [PATCH] cogl-path: Use the GLU tesselator to draw paths Instead of drawing paths using the stencil buffer trick, it now tesselates the path into triangles using the GLU tesselator and renders them directly. A vbo is created with one vertex for each node on the path. The tesselator is used to generate a series of indices into the vbo as triangles. The tesselator's output of strips and fans is converted into GL_TRIANGLES so that it can be rendered with a single draw call (but the vertices are still shared via the indices). The vbo is stored with the path so that if the application uses retained paths then Cogl won't have to tessellate again. The vertices also have texture coordinates associated with them so that it can replicate the old behaviour of drawing a material with a texture by fitting the texture to the bounding box of the path and then clipping it. However if the texture contains waste or is sliced then the vertex buffer code will refuse to draw it. In this case it will revert back to drawing the path into the stencil buffer and then drawing the material as a clipped quad. The VBO is used even when setting up the stencil buffer for clipping to a path because the tessellated geometry may cover less area. The old scanline rasterizer has been removed because the tesselator should work equally well on drivers with no stencil buffer. --- cogl/cogl-path-private.h | 5 + cogl/cogl-path.c | 737 ++++++++++++++++++++++++--------------- 2 files changed, 463 insertions(+), 279 deletions(-) diff --git a/cogl/cogl-path-private.h b/cogl/cogl-path-private.h index c5bc30531..ecfb52024 100644 --- a/cogl/cogl-path-private.h +++ b/cogl/cogl-path-private.h @@ -76,6 +76,11 @@ struct _CoglPathData unsigned int last_path; floatVec2 path_nodes_min; floatVec2 path_nodes_max; + + CoglHandle vbo; + unsigned int vbo_n_vertices; + CoglHandle vbo_indices; + unsigned int vbo_n_indices; }; /* This is an internal version of cogl_path_new that doesn't affect diff --git a/cogl/cogl-path.c b/cogl/cogl-path.c index 426e5ba12..f27218f0b 100644 --- a/cogl/cogl-path.c +++ b/cogl/cogl-path.c @@ -33,6 +33,8 @@ #include "cogl-material-private.h" #include "cogl-framebuffer-private.h" #include "cogl-path-private.h" +#include "cogl-texture-private.h" +#include "tesselator/tesselator.h" #include #include @@ -45,6 +47,8 @@ static void _cogl_path_free (CoglPath *path); +static void _cogl_path_build_vbo (CoglPath *path); + COGL_OBJECT_DEFINE (Path, path); static void @@ -54,6 +58,12 @@ _cogl_path_data_unref (CoglPathData *data) { g_array_free (data->path_nodes, TRUE); + if (data->vbo) + { + cogl_handle_unref (data->vbo); + cogl_handle_unref (data->vbo_indices); + } + g_slice_free (CoglPathData, data); } } @@ -77,10 +87,20 @@ _cogl_path_modify (CoglPath *path) old_data->path_nodes->data, old_data->path_nodes->len); + path->data->vbo = COGL_INVALID_HANDLE; + path->data->vbo_indices = COGL_INVALID_HANDLE; path->data->ref_count = 1; _cogl_path_data_unref (old_data); } + /* The path is altered so the vbo will now be invalid */ + else if (path->data->vbo) + { + cogl_handle_unref (path->data->vbo); + cogl_handle_unref (path->data->vbo_indices); + path->data->vbo = COGL_INVALID_HANDLE; + path->data->vbo_indices = COGL_INVALID_HANDLE; + } } static void @@ -213,13 +233,94 @@ _cogl_path_get_bounds (CoglPath *path, } } +static void +_cogl_path_fill_nodes_with_stencil_buffer (CoglPath *path) +{ + CoglFramebuffer *framebuffer; + CoglClipState *clip_state; + + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + _cogl_journal_flush (); + + framebuffer = _cogl_get_framebuffer (); + clip_state = _cogl_framebuffer_get_clip_state (framebuffer); + + _cogl_add_path_to_stencil_buffer (path, + clip_state->stencil_used, + FALSE); + + 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); + + /* The stencil buffer now contains garbage so the clip area needs to + * be rebuilt. + * + * NB: We only ever try and update the clip state during + * _cogl_journal_init (when we flush the framebuffer state) which is + * only called when the journal first gets something logged in it; so + * we call cogl_flush() to emtpy the journal. + */ + cogl_flush (); + _cogl_clip_state_dirty (clip_state); +} + +static void +_cogl_path_fill_nodes (CoglPath *path) +{ + const GList *l; + + _COGL_GET_CONTEXT (ctx, NO_RETVAL); + + /* If any of the layers of the current material contain sliced + textures or textures with waste then it won't work to draw the + path directly. Instead we can use draw the texture as a quad + clipped to the stencil buffer. */ + for (l = cogl_material_get_layers (ctx->source_material); l; l = l->next) + { + CoglHandle layer = l->data; + CoglHandle texture = cogl_material_layer_get_texture (layer); + + if (texture != COGL_INVALID_HANDLE && + (cogl_texture_is_sliced (texture) || + !_cogl_texture_can_hardware_repeat (texture))) + { + if (cogl_features_available (COGL_FEATURE_STENCIL_BUFFER)) + _cogl_path_fill_nodes_with_stencil_buffer (path); + else + { + 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; + } + } + + return; + } + } + + _cogl_path_build_vbo (path); + + cogl_vertex_buffer_draw_elements (path->data->vbo, + COGL_VERTICES_MODE_TRIANGLES, + path->data->vbo_indices, + 0, path->data->vbo_n_vertices - 1, + 0, path->data->vbo_n_indices); +} + void _cogl_add_path_to_stencil_buffer (CoglPath *path, gboolean merge, gboolean need_clear) { CoglPathData *data = path->data; - unsigned int path_start = 0; unsigned long enable_flags = COGL_ENABLE_VERTEX_ARRAY; CoglHandle prev_source; CoglFramebuffer *framebuffer = _cogl_get_framebuffer (); @@ -292,20 +393,8 @@ _cogl_add_path_to_stencil_buffer (CoglPath *path, GE (glStencilOp (GL_INVERT, GL_INVERT, GL_INVERT)); - /* Disable all client texture coordinate arrays */ - _cogl_bitmask_clear_all (&ctx->temp_bitmask); - _cogl_disable_other_texcoord_arrays (&ctx->temp_bitmask); - - while (path_start < data->path_nodes->len) - { - CoglPathNode *node = - &g_array_index (data->path_nodes, CoglPathNode, path_start); - - GE (glVertexPointer (2, GL_FLOAT, sizeof (CoglPathNode), &node->x)); - GE (glDrawArrays (GL_TRIANGLE_FAN, 0, node->path_size)); - - path_start += node->path_size; - } + if (path->data->path_nodes->len > 0) + _cogl_path_fill_nodes (path); if (merge) { @@ -349,268 +438,6 @@ _cogl_add_path_to_stencil_buffer (CoglPath *path, cogl_object_unref (prev_source); } -static int -compare_ints (gconstpointer a, - gconstpointer b) -{ - return GPOINTER_TO_INT(a)-GPOINTER_TO_INT(b); -} - -static void -_cogl_path_fill_nodes_scanlines (CoglPathNode *path, - unsigned int path_size, - int bounds_x, - int bounds_y, - unsigned int bounds_w, - unsigned int bounds_h) -{ - CoglHandle source; - - /* This is our edge list it stores intersections between our - * curve and scanlines, it should probably be implemented with a - * data structure that has smaller overhead for inserting the - * curve/scanline intersections. - */ - GSList **scanlines = g_alloca (bounds_h * sizeof (GSList *)); - - int i; - int prev_x; - int prev_y; - int first_x; - int first_y; - int lastdir = -2; /* last direction we vere moving */ - int lastline = -1; /* the previous scanline we added to */ - - _COGL_GET_CONTEXT (ctx, NO_RETVAL); - - /* We are going to use GL to draw directly so make sure any - * previously batched geometry gets to GL before we start... - */ - _cogl_journal_flush (); - - /* NB: _cogl_framebuffer_flush_state may disrupt various state (such - * as the material state) when flushing the clip stack, so should - * always be done first when preparing to draw. */ - _cogl_framebuffer_flush_state (_cogl_get_framebuffer (), 0); - - if (G_UNLIKELY (ctx->legacy_state_set)) - { - source = cogl_material_copy (ctx->source_material); - _cogl_material_apply_legacy_state (source); - } - else - source = ctx->source_material; - - _cogl_material_flush_gl_state (source, FALSE); - - _cogl_enable (COGL_ENABLE_VERTEX_ARRAY); - - - /* clear scanline intersection lists */ - for (i = 0; i < bounds_h; i++) - scanlines[i]=NULL; - - first_x = prev_x = path->x; - first_y = prev_y = path->y; - - /* create scanline intersection list */ - for (i=1; i < path_size; i++) - { - int dest_x = path[i].x; - int dest_y = path[i].y; - int ydir; - int dx; - int dy; - int y; - - fill_close: - dx = dest_x - prev_x; - dy = dest_y - prev_y; - - if (dy < 0) - ydir = -1; - else if (dy > 0) - ydir = 1; - else - ydir = 0; - - /* do linear interpolation between vertices */ - for (y = prev_y; y != dest_y; y += ydir) - { - - /* only add a point if the scanline has changed and we're - * within bounds. - */ - if (y - bounds_y >= 0 && - y - bounds_y < bounds_h && - lastline != y) - { - int x = prev_x + (dx * (y-prev_y)) / dy; - - scanlines[ y - bounds_y ]= - g_slist_insert_sorted (scanlines[ y - bounds_y], - GINT_TO_POINTER(x), - compare_ints); - - if (ydir != lastdir && /* add a double entry when changing */ - lastdir != -2) /* vertical direction */ - scanlines[ y - bounds_y ]= - g_slist_insert_sorted (scanlines[ y - bounds_y], - GINT_TO_POINTER(x), - compare_ints); - lastdir = ydir; - lastline = y; - } - } - - prev_x = dest_x; - prev_y = dest_y; - - /* if we're on the last knot, fake the first vertex being a - next one */ - if (path_size == i+1) - { - dest_x = first_x; - dest_y = first_y; - i++; /* to make the loop finally end */ - goto fill_close; - } - } - - { - int spans = 0; - int span_no; - GLfloat *coords; - - /* count number of spans */ - for (i = 0; i < bounds_h; i++) - { - GSList *iter = scanlines[i]; - while (iter) - { - GSList *next = iter->next; - if (!next) - { - break; - } - /* draw the segments that should be visible */ - spans ++; - iter = next->next; - } - } - coords = g_malloc0 (spans * sizeof (GLfloat) * 3 * 2 * 2); - - span_no = 0; - /* build list of triangles */ - for (i = 0; i < bounds_h; i++) - { - GSList *iter = scanlines[i]; - while (iter) - { - GSList *next = iter->next; - GLfloat x_0, x_1; - GLfloat y_0, y_1; - if (!next) - break; - - x_0 = GPOINTER_TO_INT (iter->data); - x_1 = GPOINTER_TO_INT (next->data); - y_0 = bounds_y + i; - y_1 = bounds_y + i + 1.0625f; - /* render scanlines 1.0625 high to avoid gaps when - transformed */ - - coords[span_no * 12 + 0] = x_0; - coords[span_no * 12 + 1] = y_0; - coords[span_no * 12 + 2] = x_1; - coords[span_no * 12 + 3] = y_0; - coords[span_no * 12 + 4] = x_1; - coords[span_no * 12 + 5] = y_1; - coords[span_no * 12 + 6] = x_0; - coords[span_no * 12 + 7] = y_0; - coords[span_no * 12 + 8] = x_0; - coords[span_no * 12 + 9] = y_1; - coords[span_no * 12 + 10] = x_1; - coords[span_no * 12 + 11] = y_1; - span_no ++; - iter = next->next; - } - } - for (i = 0; i < bounds_h; i++) - g_slist_free (scanlines[i]); - - /* render triangles */ - GE (glVertexPointer (2, GL_FLOAT, 0, coords )); - GE (glDrawArrays (GL_TRIANGLES, 0, spans * 2 * 3)); - g_free (coords); - } - - if (G_UNLIKELY (source != ctx->source_material)) - cogl_handle_unref (source); -} - -static void -_cogl_path_fill_nodes (void) -{ - CoglPathData *data; - - _COGL_GET_CONTEXT (ctx, NO_RETVAL); - - data = COGL_PATH (ctx->current_path)->data; - - if (G_LIKELY (!(cogl_debug_flags & COGL_DEBUG_FORCE_SCANLINE_PATHS)) && - cogl_features_available (COGL_FEATURE_STENCIL_BUFFER)) - { - CoglFramebuffer *framebuffer; - CoglClipState *clip_state; - - _cogl_journal_flush (); - - framebuffer = _cogl_get_framebuffer (); - clip_state = _cogl_framebuffer_get_clip_state (framebuffer); - - _cogl_add_path_to_stencil_buffer (ctx->current_path, - clip_state->stencil_used, - FALSE); - - cogl_rectangle (data->path_nodes_min.x, - data->path_nodes_min.y, - data->path_nodes_max.x, - data->path_nodes_max.y); - - /* The stencil buffer now contains garbage so the clip area needs to - * be rebuilt. - * - * NB: We only ever try and update the clip state during - * _cogl_journal_init (when we flush the framebuffer state) which is - * only called when the journal first gets something logged in it; so - * we call cogl_flush() to emtpy the journal. - */ - cogl_flush (); - _cogl_clip_state_dirty (clip_state); - } - else - { - unsigned int path_start = 0; - - while (path_start < data->path_nodes->len) - { - CoglPathNode *node = &g_array_index (data->path_nodes, CoglPathNode, - path_start); - - _cogl_path_fill_nodes_scanlines (node, node->path_size, - data->path_nodes_min.x, - data->path_nodes_min.y, - data->path_nodes_max.x - - data->path_nodes_min.x, - data->path_nodes_max.y - - data->path_nodes_min.y); - - path_start += node->path_size; - } - } -} - void cogl_path_fill (void) { @@ -622,12 +449,16 @@ cogl_path_fill (void) void cogl_path_fill_preserve (void) { + CoglPath *path; + _COGL_GET_CONTEXT (ctx, NO_RETVAL); - if (COGL_PATH (ctx->current_path)->data->path_nodes->len == 0) + path = COGL_PATH (ctx->current_path); + + if (path->data->path_nodes->len == 0) return; - _cogl_path_fill_nodes (); + _cogl_path_fill_nodes (path); } void @@ -1116,6 +947,8 @@ _cogl_path_new (void) data->ref_count = 1; data->path_nodes = g_array_new (FALSE, FALSE, sizeof (CoglPathNode)); data->last_path = 0; + data->vbo = COGL_INVALID_HANDLE; + data->vbo_indices = COGL_INVALID_HANDLE; return _cogl_path_object_new (path); } @@ -1286,3 +1119,349 @@ cogl_set_path (CoglPath *path) ctx->current_path = path; } +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 (gpointer 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 (GLdouble coords[3], + void *vertex_data[4], + GLfloat 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_vbo (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->vbo) + return; + + tess.primitive_type = GL_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 (); + /* 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++) + { + GLdouble 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->vbo = cogl_vertex_buffer_new (tess.vertices->len); + cogl_vertex_buffer_add (data->vbo, + "gl_Vertex", + 2, COGL_ATTRIBUTE_TYPE_FLOAT, + FALSE, + sizeof (CoglPathTesselatorVertex), + &g_array_index (tess.vertices, + CoglPathTesselatorVertex, + 0).x); + cogl_vertex_buffer_add (data->vbo, + "gl_MultiTexCoord0", + 2, COGL_ATTRIBUTE_TYPE_FLOAT, + FALSE, + sizeof (CoglPathTesselatorVertex), + &g_array_index (tess.vertices, + CoglPathTesselatorVertex, + 0).s); + cogl_vertex_buffer_submit (data->vbo); + data->vbo_n_vertices = tess.vertices->len; + data->vbo_indices = + cogl_vertex_buffer_indices_new (tess.indices_type, + tess.indices->data, + tess.indices->len); + data->vbo_n_indices = tess.indices->len; + + g_array_free (tess.vertices, TRUE); + g_array_free (tess.indices, TRUE); +}