cogl-path: Use true copy-on-write semantics

Previously a path copy was implemented such that only the array of
path nodes was shared with the source and the rest of the data is
copied. This was so that the copy could avoid a deep copy if the
source path is appended to because the copy keeps track of its own
length. This optimisation is probably not worthwhile because it makes
the copies less cheap. Instead the CoglPath struct now just contains a
single pointer to a new CoglPathData struct which is separately
ref-counted. When the path is modified it will be copied if the ref
count on the data is not 1.
This commit is contained in:
Neil Roberts 2010-04-22 11:58:52 +01:00
parent 582bd409dd
commit 8193e12546
2 changed files with 122 additions and 141 deletions

View File

@ -57,24 +57,20 @@ typedef struct _CoglBezCubic
} CoglBezCubic; } CoglBezCubic;
typedef struct _CoglPath CoglPath; typedef struct _CoglPath CoglPath;
typedef struct _CoglPathData CoglPathData;
struct _CoglPath struct _CoglPath
{ {
CoglHandleObject _parent; CoglHandleObject _parent;
/* If this path was created with cogl_path_copy then parent_path CoglPathData *data;
will point to the copied path. Otherwise it will be };
COGL_INVALID_HANDLE to indicate that we own path_nodes. */
CoglHandle parent_path; struct _CoglPathData
/* Pointer to the path nodes array. This will point directly into {
the parent path if this path is a copy */ unsigned int ref_count;
GArray *path_nodes; GArray *path_nodes;
/* Number of nodes to render from the data. This may be different
from path_nodes->len if this is a copied path and the parent path
was appended to. If that is the case then we need to be careful
to check that the size of a sub path doesn't extend past
path_size */
unsigned int path_size;
floatVec2 path_start; floatVec2 path_start;
floatVec2 path_pen; floatVec2 path_pen;

View File

@ -46,42 +46,39 @@ static void _cogl_path_free (CoglPath *path);
COGL_HANDLE_DEFINE (Path, path); COGL_HANDLE_DEFINE (Path, path);
static void
_cogl_path_data_unref (CoglPathData *data)
{
if (--data->ref_count <= 0)
{
g_array_free (data->path_nodes, TRUE);
g_slice_free (CoglPathData, data);
}
}
static void static void
_cogl_path_modify (CoglPath *path) _cogl_path_modify (CoglPath *path)
{ {
/* This needs to be called whenever the path is about to be modified /* This needs to be called whenever the path is about to be modified
to implement copy-on-write semantics. Note that the current to implement copy-on-write semantics */
mechanism assumes that a path will only ever be appended to (ie,
the path won't be cleared or have nodes in the middle
changed). This means that we don't need to keep track of how many
copies a node has because the copies can just keep track of the
number of nodes they should draw */
/* If this path is a copy then we need to actually copy the data so /* If there is more than one path using the data then we need to
we can modify it */ copy the data instead */
if (path->parent_path) if (path->data->ref_count != 1)
{ {
CoglPath *old_path = COGL_PATH (path->parent_path); CoglPathData *old_data = path->data;
CoglPathNode *old_nodes = &g_array_index (old_path->path_nodes,
CoglPathNode, 0);
CoglPathNode *new_nodes;
int i;
path->path_nodes = g_array_new (FALSE, FALSE, sizeof (CoglPathNode)); path->data = g_slice_dup (CoglPathData, old_data);
/* The parent path may have extra nodes added after the copy was path->data->path_nodes = g_array_new (FALSE, FALSE,
made so we need to truncate it */ sizeof (CoglPathNode));
g_array_set_size (path->path_nodes, path->path_size); g_array_append_vals (path->data->path_nodes,
memcpy (path->path_nodes->data, old_nodes, old_data->path_nodes->data,
sizeof (CoglPathNode) * path->path_size); old_data->path_nodes->len);
/* We need to make sure the last path size doesn't extend past
the total path size */
new_nodes = &g_array_index (path->path_nodes, CoglPathNode, 0);
for (i = 0; i < path->path_size; i += new_nodes[i].path_size)
if (i + new_nodes[i].path_size >= path->path_size)
new_nodes[i].path_size = path->path_size - i;
cogl_handle_unref (path->parent_path); path->data->ref_count = 1;
path->parent_path = COGL_INVALID_HANDLE;
_cogl_path_data_unref (old_data);
} }
} }
@ -92,6 +89,7 @@ _cogl_path_add_node (gboolean new_sub_path,
{ {
CoglPathNode new_node; CoglPathNode new_node;
CoglPath *path; CoglPath *path;
CoglPathData *data;
_COGL_GET_CONTEXT (ctx, NO_RETVAL); _COGL_GET_CONTEXT (ctx, NO_RETVAL);
@ -99,29 +97,30 @@ _cogl_path_add_node (gboolean new_sub_path,
_cogl_path_modify (path); _cogl_path_modify (path);
data = path->data;
new_node.x = x; new_node.x = x;
new_node.y = y; new_node.y = y;
new_node.path_size = 0; new_node.path_size = 0;
if (new_sub_path || path->path_size == 0) if (new_sub_path || data->path_nodes->len == 0)
path->last_path = path->path_size; data->last_path = data->path_nodes->len;
g_array_append_val (path->path_nodes, new_node); g_array_append_val (data->path_nodes, new_node);
path->path_size++;
g_array_index (path->path_nodes, CoglPathNode, path->last_path).path_size++; g_array_index (data->path_nodes, CoglPathNode, data->last_path).path_size++;
if (path->path_size == 1) if (data->path_nodes->len == 1)
{ {
path->path_nodes_min.x = path->path_nodes_max.x = x; data->path_nodes_min.x = data->path_nodes_max.x = x;
path->path_nodes_min.y = path->path_nodes_max.y = y; data->path_nodes_min.y = data->path_nodes_max.y = y;
} }
else else
{ {
if (x < path->path_nodes_min.x) path->path_nodes_min.x = x; if (x < data->path_nodes_min.x) data->path_nodes_min.x = x;
if (x > path->path_nodes_max.x) path->path_nodes_max.x = x; if (x > data->path_nodes_max.x) data->path_nodes_max.x = x;
if (y < path->path_nodes_min.y) path->path_nodes_min.y = y; if (y < data->path_nodes_min.y) data->path_nodes_min.y = y;
if (y > path->path_nodes_max.y) path->path_nodes_max.y = y; if (y > data->path_nodes_max.y) data->path_nodes_max.y = y;
} }
} }
@ -130,12 +129,12 @@ _cogl_path_stroke_nodes (void)
{ {
unsigned int path_start = 0; unsigned int path_start = 0;
unsigned long enable_flags = COGL_ENABLE_VERTEX_ARRAY; unsigned long enable_flags = COGL_ENABLE_VERTEX_ARRAY;
CoglPath *path; CoglPathData *data;
CoglMaterialFlushOptions options; CoglMaterialFlushOptions options;
_COGL_GET_CONTEXT (ctx, NO_RETVAL); _COGL_GET_CONTEXT (ctx, NO_RETVAL);
path = COGL_PATH (ctx->current_path); data = COGL_PATH (ctx->current_path)->data;
_cogl_journal_flush (); _cogl_journal_flush ();
@ -153,17 +152,13 @@ _cogl_path_stroke_nodes (void)
_cogl_material_flush_gl_state (ctx->source_material, &options); _cogl_material_flush_gl_state (ctx->source_material, &options);
while (path_start < path->path_size) while (path_start < data->path_nodes->len)
{ {
CoglPathNode *node = &g_array_index (path->path_nodes, CoglPathNode, CoglPathNode *node = &g_array_index (data->path_nodes, CoglPathNode,
path_start); path_start);
GE( glVertexPointer (2, GL_FLOAT, sizeof (CoglPathNode), &node->x) ); GE( glVertexPointer (2, GL_FLOAT, sizeof (CoglPathNode), &node->x) );
/* We need to limit the size of the sub path to the size of our GE( glDrawArrays (GL_LINE_STRIP, 0, node->path_size) );
path in case this path is a copy and the parent path has
grown */
GE( glDrawArrays (GL_LINE_STRIP, 0,
MIN (node->path_size, path->path_size - path_start)) );
path_start += node->path_size; path_start += node->path_size;
} }
@ -226,7 +221,7 @@ _cogl_add_path_to_stencil_buffer (CoglHandle path_handle,
_cogl_material_get_cogl_enable_flags (ctx->source_material); _cogl_material_get_cogl_enable_flags (ctx->source_material);
_cogl_enable (enable_flags); _cogl_enable (enable_flags);
_cogl_path_get_bounds (path->path_nodes_min, path->path_nodes_max, _cogl_path_get_bounds (path->data->path_nodes_min, path->data->path_nodes_max,
&bounds_x, &bounds_y, &bounds_w, &bounds_h); &bounds_x, &bounds_y, &bounds_w, &bounds_h);
GE( glEnable (GL_STENCIL_TEST) ); GE( glEnable (GL_STENCIL_TEST) );
@ -275,17 +270,13 @@ _cogl_add_path_to_stencil_buffer (CoglHandle path_handle,
} }
ctx->n_texcoord_arrays_enabled = 0; ctx->n_texcoord_arrays_enabled = 0;
while (path_start < path->path_size) while (path_start < path->data->path_nodes->len)
{ {
CoglPathNode *node = CoglPathNode *node =
&g_array_index (path->path_nodes, CoglPathNode, path_start); &g_array_index (path->data->path_nodes, CoglPathNode, path_start);
GE (glVertexPointer (2, GL_FLOAT, sizeof (CoglPathNode), &node->x)); GE (glVertexPointer (2, GL_FLOAT, sizeof (CoglPathNode), &node->x));
/* We need to limit the size of the sub path to the size of our GE (glDrawArrays (GL_TRIANGLE_FAN, 0, node->path_size));
path in case this path is a copy and the parent path has
grown */
GE (glDrawArrays (GL_TRIANGLE_FAN, 0,
MIN (node->path_size, path->path_size - path_start)));
path_start += node->path_size; path_start += node->path_size;
} }
@ -522,7 +513,7 @@ _cogl_path_fill_nodes_scanlines (CoglPathNode *path,
static void static void
_cogl_path_fill_nodes (void) _cogl_path_fill_nodes (void)
{ {
CoglPath *path; CoglPathData *data;
float bounds_x; float bounds_x;
float bounds_y; float bounds_y;
float bounds_w; float bounds_w;
@ -530,9 +521,9 @@ _cogl_path_fill_nodes (void)
_COGL_GET_CONTEXT (ctx, NO_RETVAL); _COGL_GET_CONTEXT (ctx, NO_RETVAL);
path = COGL_PATH (ctx->current_path); data = COGL_PATH (ctx->current_path)->data;
_cogl_path_get_bounds (path->path_nodes_min, path->path_nodes_max, _cogl_path_get_bounds (data->path_nodes_min, data->path_nodes_max,
&bounds_x, &bounds_y, &bounds_w, &bounds_h); &bounds_x, &bounds_y, &bounds_w, &bounds_h);
if (G_LIKELY (!(cogl_debug_flags & COGL_DEBUG_FORCE_SCANLINE_PATHS)) && if (G_LIKELY (!(cogl_debug_flags & COGL_DEBUG_FORCE_SCANLINE_PATHS)) &&
@ -568,17 +559,12 @@ _cogl_path_fill_nodes (void)
{ {
unsigned int path_start = 0; unsigned int path_start = 0;
while (path_start < path->path_size) while (path_start < data->path_nodes->len)
{ {
CoglPathNode *node = &g_array_index (path->path_nodes, CoglPathNode, CoglPathNode *node = &g_array_index (data->path_nodes, CoglPathNode,
path_start); path_start);
/* We need to limit the size of the sub path to the size of _cogl_path_fill_nodes_scanlines (node, node->path_size,
our path in case this path is a copy and the parent path
has grown */
_cogl_path_fill_nodes_scanlines (node,
MIN (node->path_size,
path->path_size - path_start),
bounds_x, bounds_y, bounds_x, bounds_y,
bounds_w, bounds_h); bounds_w, bounds_h);
@ -600,7 +586,7 @@ cogl_path_fill_preserve (void)
{ {
_COGL_GET_CONTEXT (ctx, NO_RETVAL); _COGL_GET_CONTEXT (ctx, NO_RETVAL);
if (COGL_PATH (ctx->current_path)->path_size == 0) if (COGL_PATH (ctx->current_path)->data->path_nodes->len == 0)
return; return;
_cogl_path_fill_nodes (); _cogl_path_fill_nodes ();
@ -619,7 +605,7 @@ cogl_path_stroke_preserve (void)
{ {
_COGL_GET_CONTEXT (ctx, NO_RETVAL); _COGL_GET_CONTEXT (ctx, NO_RETVAL);
if (COGL_PATH (ctx->current_path)->path_size == 0) if (COGL_PATH (ctx->current_path)->data->path_nodes->len == 0)
return; return;
_cogl_path_stroke_nodes (); _cogl_path_stroke_nodes ();
@ -629,62 +615,62 @@ void
cogl_path_move_to (float x, cogl_path_move_to (float x,
float y) float y)
{ {
CoglPath *path; CoglPathData *data;
_COGL_GET_CONTEXT (ctx, NO_RETVAL); _COGL_GET_CONTEXT (ctx, NO_RETVAL);
_cogl_path_add_node (TRUE, x, y); _cogl_path_add_node (TRUE, x, y);
path = COGL_PATH (ctx->current_path); data = COGL_PATH (ctx->current_path)->data;
path->path_start.x = x; data->path_start.x = x;
path->path_start.y = y; data->path_start.y = y;
path->path_pen = path->path_start; data->path_pen = data->path_start;
} }
void void
cogl_path_rel_move_to (float x, cogl_path_rel_move_to (float x,
float y) float y)
{ {
CoglPath *path; CoglPathData *data;
_COGL_GET_CONTEXT (ctx, NO_RETVAL); _COGL_GET_CONTEXT (ctx, NO_RETVAL);
path = COGL_PATH (ctx->current_path); data = COGL_PATH (ctx->current_path)->data;
cogl_path_move_to (path->path_pen.x + x, cogl_path_move_to (data->path_pen.x + x,
path->path_pen.y + y); data->path_pen.y + y);
} }
void void
cogl_path_line_to (float x, cogl_path_line_to (float x,
float y) float y)
{ {
CoglPath *path; CoglPathData *data;
_COGL_GET_CONTEXT (ctx, NO_RETVAL); _COGL_GET_CONTEXT (ctx, NO_RETVAL);
_cogl_path_add_node (FALSE, x, y); _cogl_path_add_node (FALSE, x, y);
path = COGL_PATH (ctx->current_path); data = COGL_PATH (ctx->current_path)->data;
path->path_pen.x = x; data->path_pen.x = x;
path->path_pen.y = y; data->path_pen.y = y;
} }
void void
cogl_path_rel_line_to (float x, cogl_path_rel_line_to (float x,
float y) float y)
{ {
CoglPath *path; CoglPathData *data;
_COGL_GET_CONTEXT (ctx, NO_RETVAL); _COGL_GET_CONTEXT (ctx, NO_RETVAL);
path = COGL_PATH (ctx->current_path); data = COGL_PATH (ctx->current_path)->data;
cogl_path_line_to (path->path_pen.x + x, cogl_path_line_to (data->path_pen.x + x,
path->path_pen.y + y); data->path_pen.y + y);
} }
void void
@ -696,10 +682,10 @@ cogl_path_close (void)
path = COGL_PATH (ctx->current_path); path = COGL_PATH (ctx->current_path);
_cogl_path_add_node (FALSE, path->path_start.x, _cogl_path_add_node (FALSE, path->data->path_start.x,
path->path_start.y); path->data->path_start.y);
path->path_pen = path->path_start; path->data->path_pen = path->data->path_start;
} }
void void
@ -847,14 +833,14 @@ _cogl_path_rel_arc (float center_x,
float angle_2, float angle_2,
float angle_step) float angle_step)
{ {
CoglPath *path; CoglPathData *data;
_COGL_GET_CONTEXT (ctx, NO_RETVAL); _COGL_GET_CONTEXT (ctx, NO_RETVAL);
path = COGL_PATH (ctx->current_path); data = COGL_PATH (ctx->current_path)->data;
_cogl_path_arc (path->path_pen.x + center_x, _cogl_path_arc (data->path_pen.x + center_x,
path->path_pen.y + center_y, data->path_pen.y + center_y,
radius_x, radius_y, radius_x, radius_y,
angle_1, angle_2, angle_1, angle_2,
angle_step, 0 /* no move */); angle_step, 0 /* no move */);
@ -902,16 +888,16 @@ cogl_path_round_rectangle (float x_1,
270, 270,
arc_step); arc_step);
cogl_path_line_to (path->path_pen.x + inner_width, cogl_path_line_to (path->data->path_pen.x + inner_width,
path->path_pen.y); path->data->path_pen.y);
_cogl_path_rel_arc (0, radius, _cogl_path_rel_arc (0, radius,
radius, radius, radius, radius,
-90, -90,
0, 0,
arc_step); arc_step);
cogl_path_line_to (path->path_pen.x, cogl_path_line_to (path->data->path_pen.x,
path->path_pen.y + inner_height); path->data->path_pen.y + inner_height);
_cogl_path_rel_arc (-radius, 0, _cogl_path_rel_arc (-radius, 0,
radius, radius, radius, radius,
@ -919,8 +905,8 @@ cogl_path_round_rectangle (float x_1,
90, 90,
arc_step); arc_step);
cogl_path_line_to (path->path_pen.x - inner_width, cogl_path_line_to (path->data->path_pen.x - inner_width,
path->path_pen.y); path->data->path_pen.y);
_cogl_path_rel_arc (0, -radius, _cogl_path_rel_arc (0, -radius,
radius, radius, radius, radius,
90, 90,
@ -1042,7 +1028,7 @@ cogl_path_curve_to (float x_1,
path = COGL_PATH (ctx->current_path); path = COGL_PATH (ctx->current_path);
/* Prepare cubic curve */ /* Prepare cubic curve */
cubic.p1 = path->path_pen; cubic.p1 = path->data->path_pen;
cubic.p2.x = x_1; cubic.p2.x = x_1;
cubic.p2.y = y_1; cubic.p2.y = y_1;
cubic.p3.x = x_2; cubic.p3.x = x_2;
@ -1055,7 +1041,7 @@ cogl_path_curve_to (float x_1,
/* Add last point */ /* Add last point */
_cogl_path_add_node (FALSE, cubic.p4.x, cubic.p4.y); _cogl_path_add_node (FALSE, cubic.p4.x, cubic.p4.y);
path->path_pen = cubic.p4; path->data->path_pen = cubic.p4;
} }
void void
@ -1066,18 +1052,18 @@ cogl_path_rel_curve_to (float x_1,
float x_3, float x_3,
float y_3) float y_3)
{ {
CoglPath *path; CoglPathData *data;
_COGL_GET_CONTEXT (ctx, NO_RETVAL); _COGL_GET_CONTEXT (ctx, NO_RETVAL);
path = COGL_PATH (ctx->current_path); data = COGL_PATH (ctx->current_path)->data;
cogl_path_curve_to (path->path_pen.x + x_1, cogl_path_curve_to (data->path_pen.x + x_1,
path->path_pen.y + y_1, data->path_pen.y + y_1,
path->path_pen.x + x_2, data->path_pen.x + x_2,
path->path_pen.y + y_2, data->path_pen.y + y_2,
path->path_pen.x + x_3, data->path_pen.x + x_3,
path->path_pen.y + y_3); data->path_pen.y + y_3);
} }
CoglHandle CoglHandle
@ -1107,12 +1093,14 @@ CoglHandle
_cogl_path_new (void) _cogl_path_new (void)
{ {
CoglPath *path; CoglPath *path;
CoglPathData *data;
path = g_slice_new (CoglPath); path = g_slice_new (CoglPath);
path->path_nodes = g_array_new (FALSE, FALSE, sizeof (CoglPathNode)); data = path->data = g_slice_new (CoglPathData);
path->last_path = 0;
path->parent_path = COGL_INVALID_HANDLE; data->ref_count = 1;
path->path_size = 0; data->path_nodes = g_array_new (FALSE, FALSE, sizeof (CoglPathNode));
data->last_path = 0;
return _cogl_path_handle_new (path); return _cogl_path_handle_new (path);
} }
@ -1129,8 +1117,9 @@ cogl_path_copy (CoglHandle handle)
old_path = COGL_PATH (handle); old_path = COGL_PATH (handle);
new_path = g_slice_dup (CoglPath, old_path); new_path = g_slice_new (CoglPath);
new_path->parent_path = cogl_handle_ref (handle); new_path->data = old_path->data;
new_path->data->ref_count++;
return _cogl_path_handle_new (new_path); return _cogl_path_handle_new (new_path);
} }
@ -1138,11 +1127,7 @@ cogl_path_copy (CoglHandle handle)
static void static void
_cogl_path_free (CoglPath *path) _cogl_path_free (CoglPath *path)
{ {
if (path->parent_path) _cogl_path_data_unref (path->data);
cogl_handle_unref (path->parent_path);
else
g_array_free (path->path_nodes, TRUE);
g_slice_free (CoglPath, path); g_slice_free (CoglPath, path);
} }
@ -1231,7 +1216,7 @@ cogl_path_curve2_to (float x_1,
path = COGL_PATH (ctx->current_path); path = COGL_PATH (ctx->current_path);
/* Prepare quadratic curve */ /* Prepare quadratic curve */
quad.p1 = ctx->path_pen; quad.p1 = path->data->path_pen;
quad.p2.x = x_1; quad.p2.x = x_1;
quad.p2.y = y_1; quad.p2.y = y_1;
quad.p3.x = x_2; quad.p3.x = x_2;
@ -1242,7 +1227,7 @@ cogl_path_curve2_to (float x_1,
/* Add last point */ /* Add last point */
_cogl_path_add_node (FALSE, quad.p3.x, quad.p3.y); _cogl_path_add_node (FALSE, quad.p3.x, quad.p3.y);
path->path_pen = quad.p3; path->data->path_pen = quad.p3;
} }
void void
@ -1251,16 +1236,16 @@ cogl_rel_curve2_to (float x_1,
float x_2, float x_2,
float y_2) float y_2)
{ {
CoglPath *path; CoglPathData *data;
_COGL_GET_CONTEXT (ctx, NO_RETVAL); _COGL_GET_CONTEXT (ctx, NO_RETVAL);
path = COGL_PATH (ctx->current_path); data = COGL_PATH (ctx->current_path)->data;
cogl_path_curve2_to (path->path_pen.x + x_1, cogl_path_curve2_to (data->path_pen.x + x_1,
path->path_pen.y + y_1, data->path_pen.y + y_1,
path->path_pen.x + x_2, data->path_pen.x + x_2,
path->path_pen.y + y_2); data->path_pen.y + y_2);
} }
#endif #endif