"Initial commit to Gerrit"
[profile/ivi/cogl.git] / cogl-pango / cogl-pango-display-list.c
1 /*
2  * Clutter.
3  *
4  * An OpenGL based 'interactive canvas' library.
5  *
6  * Authored By Neil Roberts  <neil@linux.intel.com>
7  *
8  * Copyright (C) 2009 Intel Corporation.
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public
12  * License as published by the Free Software Foundation; either
13  * version 2 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27
28 #include <glib.h>
29 #include <cogl/cogl.h>
30 #include <string.h>
31
32 #include "cogl-pango-display-list.h"
33 #include "cogl/cogl-context-private.h"
34
35 typedef enum
36 {
37   COGL_PANGO_DISPLAY_LIST_TEXTURE,
38   COGL_PANGO_DISPLAY_LIST_RECTANGLE,
39   COGL_PANGO_DISPLAY_LIST_TRAPEZOID
40 } CoglPangoDisplayListNodeType;
41
42 typedef struct _CoglPangoDisplayListNode CoglPangoDisplayListNode;
43 typedef struct _CoglPangoDisplayListRectangle CoglPangoDisplayListRectangle;
44
45 struct _CoglPangoDisplayList
46 {
47   gboolean                color_override;
48   CoglColor               color;
49   GSList                 *nodes;
50   GSList                 *last_node;
51   CoglPangoPipelineCache *pipeline_cache;
52 };
53
54 /* This matches the format expected by cogl_rectangles_with_texture_coords */
55 struct _CoglPangoDisplayListRectangle
56 {
57   float x_1, y_1, x_2, y_2;
58   float s_1, t_1, s_2, t_2;
59 };
60
61 struct _CoglPangoDisplayListNode
62 {
63   CoglPangoDisplayListNodeType type;
64
65   gboolean color_override;
66   CoglColor color;
67
68   CoglPipeline *pipeline;
69
70   union
71   {
72     struct
73     {
74       /* The texture to render these coords from */
75       CoglTexture *texture;
76       /* Array of rectangles in the format expected by
77          cogl_rectangles_with_texture_coords */
78       GArray *rectangles;
79       /* A primitive representing those vertices */
80       CoglPrimitive *primitive;
81     } texture;
82
83     struct
84     {
85       float x_1, y_1;
86       float x_2, y_2;
87     } rectangle;
88
89     struct
90     {
91       float y_1;
92       float x_11;
93       float x_21;
94       float y_2;
95       float x_12;
96       float x_22;
97     } trapezoid;
98   } d;
99 };
100
101 CoglPangoDisplayList *
102 _cogl_pango_display_list_new (CoglPangoPipelineCache *pipeline_cache)
103 {
104   CoglPangoDisplayList *dl = g_slice_new0 (CoglPangoDisplayList);
105
106   dl->pipeline_cache = pipeline_cache;
107
108   return dl;
109 }
110
111 static void
112 _cogl_pango_display_list_append_node (CoglPangoDisplayList *dl,
113                                       CoglPangoDisplayListNode *node)
114 {
115   if (dl->last_node)
116     dl->last_node = dl->last_node->next = g_slist_prepend (NULL, node);
117   else
118     dl->last_node = dl->nodes = g_slist_prepend (NULL, node);
119 }
120
121 void
122 _cogl_pango_display_list_set_color_override (CoglPangoDisplayList *dl,
123                                              const CoglColor *color)
124 {
125   dl->color_override = TRUE;
126   dl->color = *color;
127 }
128
129 void
130 _cogl_pango_display_list_remove_color_override (CoglPangoDisplayList *dl)
131 {
132   dl->color_override = FALSE;
133 }
134
135 void
136 _cogl_pango_display_list_add_texture (CoglPangoDisplayList *dl,
137                                       CoglTexture *texture,
138                                       float x_1, float y_1,
139                                       float x_2, float y_2,
140                                       float tx_1, float ty_1,
141                                       float tx_2, float ty_2)
142 {
143   CoglPangoDisplayListNode *node;
144   CoglPangoDisplayListRectangle *rectangle;
145
146   /* Add to the last node if it is a texture node with the same
147      target texture */
148   if (dl->last_node
149       && (node = dl->last_node->data)->type == COGL_PANGO_DISPLAY_LIST_TEXTURE
150       && node->d.texture.texture == texture
151       && (dl->color_override
152           ? (node->color_override && cogl_color_equal (&dl->color, &node->color))
153           : !node->color_override))
154     {
155       /* Get rid of the vertex buffer so that it will be recreated */
156       if (node->d.texture.primitive != NULL)
157         {
158           cogl_object_unref (node->d.texture.primitive);
159           node->d.texture.primitive = NULL;
160         }
161     }
162   else
163     {
164       /* Otherwise create a new node */
165       node = g_slice_new (CoglPangoDisplayListNode);
166
167       node->type = COGL_PANGO_DISPLAY_LIST_TEXTURE;
168       node->color_override = dl->color_override;
169       node->color = dl->color;
170       node->pipeline = NULL;
171       node->d.texture.texture = cogl_object_ref (texture);
172       node->d.texture.rectangles
173         = g_array_new (FALSE, FALSE, sizeof (CoglPangoDisplayListRectangle));
174       node->d.texture.primitive = NULL;
175
176       _cogl_pango_display_list_append_node (dl, node);
177     }
178
179   g_array_set_size (node->d.texture.rectangles,
180                     node->d.texture.rectangles->len + 1);
181   rectangle = &g_array_index (node->d.texture.rectangles,
182                               CoglPangoDisplayListRectangle,
183                               node->d.texture.rectangles->len - 1);
184   rectangle->x_1 = x_1;
185   rectangle->y_1 = y_1;
186   rectangle->x_2 = x_2;
187   rectangle->y_2 = y_2;
188   rectangle->s_1 = tx_1;
189   rectangle->t_1 = ty_1;
190   rectangle->s_2 = tx_2;
191   rectangle->t_2 = ty_2;
192 }
193
194 void
195 _cogl_pango_display_list_add_rectangle (CoglPangoDisplayList *dl,
196                                         float x_1, float y_1,
197                                         float x_2, float y_2)
198 {
199   CoglPangoDisplayListNode *node = g_slice_new (CoglPangoDisplayListNode);
200
201   node->type = COGL_PANGO_DISPLAY_LIST_RECTANGLE;
202   node->color_override = dl->color_override;
203   node->color = dl->color;
204   node->d.rectangle.x_1 = x_1;
205   node->d.rectangle.y_1 = y_1;
206   node->d.rectangle.x_2 = x_2;
207   node->d.rectangle.y_2 = y_2;
208   node->pipeline = NULL;
209
210   _cogl_pango_display_list_append_node (dl, node);
211 }
212
213 void
214 _cogl_pango_display_list_add_trapezoid (CoglPangoDisplayList *dl,
215                                         float y_1,
216                                         float x_11,
217                                         float x_21,
218                                         float y_2,
219                                         float x_12,
220                                         float x_22)
221 {
222   CoglPangoDisplayListNode *node = g_slice_new (CoglPangoDisplayListNode);
223
224   node->type = COGL_PANGO_DISPLAY_LIST_TRAPEZOID;
225   node->color_override = dl->color_override;
226   node->color = dl->color;
227   node->d.trapezoid.y_1 = y_1;
228   node->d.trapezoid.x_11 = x_11;
229   node->d.trapezoid.x_21 = x_21;
230   node->d.trapezoid.y_2 = y_2;
231   node->d.trapezoid.x_12 = x_12;
232   node->d.trapezoid.x_22 = x_22;
233   node->pipeline = NULL;
234
235   _cogl_pango_display_list_append_node (dl, node);
236 }
237
238 static void
239 emit_rectangles_through_journal (CoglPangoDisplayListNode *node)
240 {
241   cogl_rectangles_with_texture_coords ((float *)
242                                        node->d.texture.rectangles->data,
243                                        node->d.texture.rectangles->len);
244 }
245
246 static void
247 emit_vertex_buffer_geometry (CoglPangoDisplayListNode *node)
248 {
249   _COGL_GET_CONTEXT (ctx, NO_RETVAL);
250
251   /* It's expensive to go through the Cogl journal for large runs
252    * of text in part because the journal transforms the quads in software
253    * to avoid changing the modelview matrix. So for larger runs of text
254    * we load the vertices into a VBO, and this has the added advantage
255    * that if the text doesn't change from frame to frame the VBO can
256    * be re-used avoiding the repeated cost of validating the data and
257    * mapping it into the GPU... */
258
259   if (node->d.texture.primitive == NULL)
260     {
261       CoglAttributeBuffer *buffer;
262       CoglVertexP2T2 *verts, *v;
263       int n_verts;
264       gboolean allocated = FALSE;
265       CoglAttribute *attributes[2];
266       CoglPrimitive *prim;
267       int i;
268
269       n_verts = node->d.texture.rectangles->len * 4;
270
271       buffer
272         = cogl_attribute_buffer_new (ctx,
273                                      n_verts * sizeof (CoglVertexP2T2), NULL);
274
275       if ((verts = cogl_buffer_map (COGL_BUFFER (buffer),
276                                     COGL_BUFFER_ACCESS_WRITE,
277                                     COGL_BUFFER_MAP_HINT_DISCARD)) == NULL)
278         {
279           verts = g_new (CoglVertexP2T2, n_verts);
280           allocated = TRUE;
281         }
282
283       v = verts;
284
285       /* Copy the rectangles into the buffer and expand into four
286          vertices instead of just two */
287       for (i = 0; i < node->d.texture.rectangles->len; i++)
288         {
289           const CoglPangoDisplayListRectangle *rectangle
290             = &g_array_index (node->d.texture.rectangles,
291                               CoglPangoDisplayListRectangle, i);
292
293           v->x = rectangle->x_1;
294           v->y = rectangle->y_1;
295           v->s = rectangle->s_1;
296           v->t = rectangle->t_1;
297           v++;
298           v->x = rectangle->x_1;
299           v->y = rectangle->y_2;
300           v->s = rectangle->s_1;
301           v->t = rectangle->t_2;
302           v++;
303           v->x = rectangle->x_2;
304           v->y = rectangle->y_2;
305           v->s = rectangle->s_2;
306           v->t = rectangle->t_2;
307           v++;
308           v->x = rectangle->x_2;
309           v->y = rectangle->y_1;
310           v->s = rectangle->s_2;
311           v->t = rectangle->t_1;
312           v++;
313         }
314
315       if (allocated)
316         {
317           cogl_buffer_set_data (COGL_BUFFER (buffer),
318                                 0, /* offset */
319                                 verts,
320                                 sizeof (CoglVertexP2T2) * n_verts);
321           g_free (verts);
322         }
323       else
324         cogl_buffer_unmap (COGL_BUFFER (buffer));
325
326       attributes[0] = cogl_attribute_new (buffer,
327                                           "cogl_position_in",
328                                           sizeof (CoglVertexP2T2),
329                                           G_STRUCT_OFFSET (CoglVertexP2T2, x),
330                                           2, /* n_components */
331                                           COGL_ATTRIBUTE_TYPE_FLOAT);
332       attributes[1] = cogl_attribute_new (buffer,
333                                           "cogl_tex_coord0_in",
334                                           sizeof (CoglVertexP2T2),
335                                           G_STRUCT_OFFSET (CoglVertexP2T2, s),
336                                           2, /* n_components */
337                                           COGL_ATTRIBUTE_TYPE_FLOAT);
338
339       prim = cogl_primitive_new_with_attributes (COGL_VERTICES_MODE_TRIANGLES,
340                                                  n_verts,
341                                                  attributes,
342                                                  2 /* n_attributes */);
343
344 #ifdef CLUTTER_COGL_HAS_GL
345       if (ctx->driver == COGL_DRIVER_GL)
346         cogl_primitive_set_mode (prim, GL_QUADS);
347       else
348 #endif
349         {
350           /* GLES doesn't support GL_QUADS so instead we use a VBO
351              with indexed vertices to generate GL_TRIANGLES from the
352              quads */
353
354           CoglIndices *indices =
355             cogl_get_rectangle_indices (ctx, node->d.texture.rectangles->len);
356
357           cogl_primitive_set_indices (prim, indices,
358                                       node->d.texture.rectangles->len * 6);
359         }
360
361       node->d.texture.primitive = prim;
362
363       cogl_object_unref (buffer);
364       cogl_object_unref (attributes[0]);
365       cogl_object_unref (attributes[1]);
366     }
367
368   cogl_framebuffer_draw_primitive (cogl_get_draw_framebuffer (),
369                                    cogl_get_source (),
370                                    node->d.texture.primitive);
371 }
372
373 static void
374 _cogl_pango_display_list_render_texture (CoglPangoDisplayListNode *node)
375 {
376   /* For small runs of text like icon labels, we can get better performance
377    * going through the Cogl journal since text may then be batched together
378    * with other geometry. */
379   /* FIXME: 25 is a number I plucked out of thin air; it would be good
380    * to determine this empirically! */
381   if (node->d.texture.rectangles->len < 25)
382     emit_rectangles_through_journal (node);
383   else
384     emit_vertex_buffer_geometry (node);
385 }
386
387 void
388 _cogl_pango_display_list_render (CoglPangoDisplayList *dl,
389                                  const CoglColor *color)
390 {
391   GSList *l;
392
393   for (l = dl->nodes; l; l = l->next)
394     {
395       CoglPangoDisplayListNode *node = l->data;
396       CoglColor draw_color;
397
398       if (node->pipeline == NULL)
399         {
400           if (node->type == COGL_PANGO_DISPLAY_LIST_TEXTURE)
401             node->pipeline =
402               _cogl_pango_pipeline_cache_get (dl->pipeline_cache,
403                                               node->d.texture.texture);
404           else
405             node->pipeline =
406               _cogl_pango_pipeline_cache_get (dl->pipeline_cache,
407                                               NULL);
408         }
409
410       if (node->color_override)
411         /* Use the override color but preserve the alpha from the
412            draw color */
413         cogl_color_init_from_4ub (&draw_color,
414                                   cogl_color_get_red_byte (&node->color),
415                                   cogl_color_get_green_byte (&node->color),
416                                   cogl_color_get_blue_byte (&node->color),
417                                   cogl_color_get_alpha_byte (color));
418       else
419         draw_color = *color;
420       cogl_color_premultiply (&draw_color);
421
422       cogl_pipeline_set_color (node->pipeline, &draw_color);
423       cogl_push_source (node->pipeline);
424
425       switch (node->type)
426         {
427         case COGL_PANGO_DISPLAY_LIST_TEXTURE:
428           _cogl_pango_display_list_render_texture (node);
429           break;
430
431         case COGL_PANGO_DISPLAY_LIST_RECTANGLE:
432           cogl_rectangle (node->d.rectangle.x_1,
433                           node->d.rectangle.y_1,
434                           node->d.rectangle.x_2,
435                           node->d.rectangle.y_2);
436           break;
437
438         case COGL_PANGO_DISPLAY_LIST_TRAPEZOID:
439           {
440             float points[8];
441             CoglPath *path;
442
443             points[0] =  node->d.trapezoid.x_11;
444             points[1] =  node->d.trapezoid.y_1;
445             points[2] =  node->d.trapezoid.x_12;
446             points[3] =  node->d.trapezoid.y_2;
447             points[4] =  node->d.trapezoid.x_22;
448             points[5] =  node->d.trapezoid.y_2;
449             points[6] =  node->d.trapezoid.x_21;
450             points[7] =  node->d.trapezoid.y_1;
451
452             path = cogl_path_new ();
453             cogl_path_polygon (path, points, 4);
454             cogl_path_fill (path);
455             cogl_object_unref (path);
456           }
457           break;
458         }
459
460       cogl_pop_source ();
461     }
462 }
463
464 static void
465 _cogl_pango_display_list_node_free (CoglPangoDisplayListNode *node)
466 {
467   if (node->type == COGL_PANGO_DISPLAY_LIST_TEXTURE)
468     {
469       g_array_free (node->d.texture.rectangles, TRUE);
470       if (node->d.texture.texture != NULL)
471         cogl_object_unref (node->d.texture.texture);
472       if (node->d.texture.primitive != NULL)
473         cogl_object_unref (node->d.texture.primitive);
474     }
475
476   if (node->pipeline)
477     cogl_object_unref (node->pipeline);
478
479   g_slice_free (CoglPangoDisplayListNode, node);
480 }
481
482 void
483 _cogl_pango_display_list_clear (CoglPangoDisplayList *dl)
484 {
485   g_slist_foreach (dl->nodes, (GFunc) _cogl_pango_display_list_node_free, NULL);
486   g_slist_free (dl->nodes);
487   dl->nodes = NULL;
488   dl->last_node = NULL;
489 }
490
491 void
492 _cogl_pango_display_list_free (CoglPangoDisplayList *dl)
493 {
494   _cogl_pango_display_list_clear (dl);
495   g_slice_free (CoglPangoDisplayList, dl);
496 }