1 /*
2  * Cogl
3  *
4  * A Low Level GPU Graphics and Utilities API
5  *
6  * Copyright (C) 2009 Intel Corporation.
7  *
8  * Permission is hereby granted, free of charge, to any person
9  * obtaining a copy of this software and associated documentation
10  * files (the "Software"), to deal in the Software without
11  * restriction, including without limitation the rights to use, copy,
12  * modify, merge, publish, distribute, sublicense, and/or sell copies
13  * of the Software, and to permit persons to whom the Software is
14  * furnished to do so, subject to the following conditions:
15  *
16  * The above copyright notice and this permission notice shall be
17  * included in all copies or substantial portions of the Software.
18  *
19  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
23  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
24  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
25  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26  * SOFTWARE.
27  */
28 
29 #ifdef HAVE_CONFIG_H
30 #include "cogl-config.h"
31 #endif
32 
33 #include <glib.h>
34 #include <string.h>
35 
36 #include "cogl-pango-display-list.h"
37 #include "cogl-pango-pipeline-cache.h"
38 #include "cogl/cogl-context-private.h"
39 
40 typedef enum
41 {
42   COGL_PANGO_DISPLAY_LIST_TEXTURE,
43   COGL_PANGO_DISPLAY_LIST_RECTANGLE,
44   COGL_PANGO_DISPLAY_LIST_TRAPEZOID
45 } CoglPangoDisplayListNodeType;
46 
47 typedef struct _CoglPangoDisplayListNode CoglPangoDisplayListNode;
48 typedef struct _CoglPangoDisplayListRectangle CoglPangoDisplayListRectangle;
49 
50 struct _CoglPangoDisplayList
51 {
52   CoglBool                color_override;
53   CoglColor               color;
54   GSList                 *nodes;
55   GSList                 *last_node;
56   CoglPangoPipelineCache *pipeline_cache;
57 };
58 
59 /* This matches the format expected by cogl_rectangles_with_texture_coords */
60 struct _CoglPangoDisplayListRectangle
61 {
62   float x_1, y_1, x_2, y_2;
63   float s_1, t_1, s_2, t_2;
64 };
65 
66 struct _CoglPangoDisplayListNode
67 {
68   CoglPangoDisplayListNodeType type;
69 
70   CoglBool color_override;
71   CoglColor color;
72 
73   CoglPipeline *pipeline;
74 
75   union
76   {
77     struct
78     {
79       /* The texture to render these coords from */
80       CoglTexture *texture;
81       /* Array of rectangles in the format expected by
82          cogl_rectangles_with_texture_coords */
83       GArray *rectangles;
84       /* A primitive representing those vertices */
85       CoglPrimitive *primitive;
86     } texture;
87 
88     struct
89     {
90       float x_1, y_1;
91       float x_2, y_2;
92     } rectangle;
93 
94     struct
95     {
96       CoglPrimitive *primitive;
97     } trapezoid;
98   } d;
99 };
100 
101 CoglPangoDisplayList *
_cogl_pango_display_list_new(CoglPangoPipelineCache * pipeline_cache)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
_cogl_pango_display_list_append_node(CoglPangoDisplayList * dl,CoglPangoDisplayListNode * node)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
_cogl_pango_display_list_set_color_override(CoglPangoDisplayList * dl,const CoglColor * color)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
_cogl_pango_display_list_remove_color_override(CoglPangoDisplayList * dl)130 _cogl_pango_display_list_remove_color_override (CoglPangoDisplayList *dl)
131 {
132   dl->color_override = FALSE;
133 }
134 
135 void
_cogl_pango_display_list_add_texture(CoglPangoDisplayList * dl,CoglTexture * texture,float x_1,float y_1,float x_2,float y_2,float tx_1,float ty_1,float tx_2,float ty_2)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
_cogl_pango_display_list_add_rectangle(CoglPangoDisplayList * dl,float x_1,float y_1,float x_2,float y_2)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
_cogl_pango_display_list_add_trapezoid(CoglPangoDisplayList * dl,float y_1,float x_11,float x_21,float y_2,float x_12,float x_22)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   CoglContext *ctx = dl->pipeline_cache->ctx;
223   CoglPangoDisplayListNode *node = g_slice_new (CoglPangoDisplayListNode);
224   CoglVertexP2 vertices[4] = {
225         { x_11, y_1 },
226         { x_12, y_2 },
227         { x_22, y_2 },
228         { x_21, y_1 }
229   };
230 
231   node->type = COGL_PANGO_DISPLAY_LIST_TRAPEZOID;
232   node->color_override = dl->color_override;
233   node->color = dl->color;
234   node->pipeline = NULL;
235 
236   node->d.trapezoid.primitive =
237     cogl_primitive_new_p2 (ctx,
238                            COGL_VERTICES_MODE_TRIANGLE_FAN,
239                            4,
240                            vertices);
241 
242   _cogl_pango_display_list_append_node (dl, node);
243 }
244 
245 static void
emit_rectangles_through_journal(CoglFramebuffer * fb,CoglPipeline * pipeline,CoglPangoDisplayListNode * node)246 emit_rectangles_through_journal (CoglFramebuffer *fb,
247                                  CoglPipeline *pipeline,
248                                  CoglPangoDisplayListNode *node)
249 {
250   const float *rectangles = (const float *)node->d.texture.rectangles->data;
251 
252   cogl_framebuffer_draw_textured_rectangles (fb,
253                                              pipeline,
254                                              rectangles,
255                                              node->d.texture.rectangles->len);
256 }
257 
258 static void
emit_vertex_buffer_geometry(CoglFramebuffer * fb,CoglPipeline * pipeline,CoglPangoDisplayListNode * node)259 emit_vertex_buffer_geometry (CoglFramebuffer *fb,
260                              CoglPipeline *pipeline,
261                              CoglPangoDisplayListNode *node)
262 {
263   CoglContext *ctx = fb->context;
264 
265   /* It's expensive to go through the Cogl journal for large runs
266    * of text in part because the journal transforms the quads in software
267    * to avoid changing the modelview matrix. So for larger runs of text
268    * we load the vertices into a VBO, and this has the added advantage
269    * that if the text doesn't change from frame to frame the VBO can
270    * be re-used avoiding the repeated cost of validating the data and
271    * mapping it into the GPU... */
272 
273   if (node->d.texture.primitive == NULL)
274     {
275       CoglAttributeBuffer *buffer;
276       CoglVertexP2T2 *verts, *v;
277       int n_verts;
278       CoglBool allocated = FALSE;
279       CoglAttribute *attributes[2];
280       CoglPrimitive *prim;
281       int i;
282 
283       n_verts = node->d.texture.rectangles->len * 4;
284 
285       buffer
286         = cogl_attribute_buffer_new_with_size (ctx,
287                                                n_verts *
288                                                sizeof (CoglVertexP2T2));
289 
290       if ((verts = cogl_buffer_map (COGL_BUFFER (buffer),
291                                     COGL_BUFFER_ACCESS_WRITE,
292                                     COGL_BUFFER_MAP_HINT_DISCARD)) == NULL)
293         {
294           verts = g_new (CoglVertexP2T2, n_verts);
295           allocated = TRUE;
296         }
297 
298       v = verts;
299 
300       /* Copy the rectangles into the buffer and expand into four
301          vertices instead of just two */
302       for (i = 0; i < node->d.texture.rectangles->len; i++)
303         {
304           const CoglPangoDisplayListRectangle *rectangle
305             = &g_array_index (node->d.texture.rectangles,
306                               CoglPangoDisplayListRectangle, i);
307 
308           v->x = rectangle->x_1;
309           v->y = rectangle->y_1;
310           v->s = rectangle->s_1;
311           v->t = rectangle->t_1;
312           v++;
313           v->x = rectangle->x_1;
314           v->y = rectangle->y_2;
315           v->s = rectangle->s_1;
316           v->t = rectangle->t_2;
317           v++;
318           v->x = rectangle->x_2;
319           v->y = rectangle->y_2;
320           v->s = rectangle->s_2;
321           v->t = rectangle->t_2;
322           v++;
323           v->x = rectangle->x_2;
324           v->y = rectangle->y_1;
325           v->s = rectangle->s_2;
326           v->t = rectangle->t_1;
327           v++;
328         }
329 
330       if (allocated)
331         {
332           cogl_buffer_set_data (COGL_BUFFER (buffer),
333                                 0, /* offset */
334                                 verts,
335                                 sizeof (CoglVertexP2T2) * n_verts);
336           free (verts);
337         }
338       else
339         cogl_buffer_unmap (COGL_BUFFER (buffer));
340 
341       attributes[0] = cogl_attribute_new (buffer,
342                                           "cogl_position_in",
343                                           sizeof (CoglVertexP2T2),
344                                           G_STRUCT_OFFSET (CoglVertexP2T2, x),
345                                           2, /* n_components */
346                                           COGL_ATTRIBUTE_TYPE_FLOAT);
347       attributes[1] = cogl_attribute_new (buffer,
348                                           "cogl_tex_coord0_in",
349                                           sizeof (CoglVertexP2T2),
350                                           G_STRUCT_OFFSET (CoglVertexP2T2, s),
351                                           2, /* n_components */
352                                           COGL_ATTRIBUTE_TYPE_FLOAT);
353 
354       prim = cogl_primitive_new_with_attributes (COGL_VERTICES_MODE_TRIANGLES,
355                                                  n_verts,
356                                                  attributes,
357                                                  2 /* n_attributes */);
358 
359 #ifdef CLUTTER_COGL_HAS_GL
360       if (_cogl_has_private_feature (ctx, COGL_PRIVATE_FEATURE_QUADS))
361         cogl_primitive_set_mode (prim, GL_QUADS);
362       else
363 #endif
364         {
365           /* GLES doesn't support GL_QUADS so instead we use a VBO
366              with indexed vertices to generate GL_TRIANGLES from the
367              quads */
368 
369           CoglIndices *indices =
370             cogl_get_rectangle_indices (ctx, node->d.texture.rectangles->len);
371 
372           cogl_primitive_set_indices (prim, indices,
373                                       node->d.texture.rectangles->len * 6);
374         }
375 
376       node->d.texture.primitive = prim;
377 
378       cogl_object_unref (buffer);
379       cogl_object_unref (attributes[0]);
380       cogl_object_unref (attributes[1]);
381     }
382 
383   cogl_primitive_draw (node->d.texture.primitive,
384                        fb,
385                        pipeline);
386 }
387 
388 static void
_cogl_framebuffer_draw_display_list_texture(CoglFramebuffer * fb,CoglPipeline * pipeline,CoglPangoDisplayListNode * node)389 _cogl_framebuffer_draw_display_list_texture (CoglFramebuffer *fb,
390                                              CoglPipeline *pipeline,
391                                              CoglPangoDisplayListNode *node)
392 {
393   /* For small runs of text like icon labels, we can get better performance
394    * going through the Cogl journal since text may then be batched together
395    * with other geometry. */
396   /* FIXME: 25 is a number I plucked out of thin air; it would be good
397    * to determine this empirically! */
398   if (node->d.texture.rectangles->len < 25)
399     emit_rectangles_through_journal (fb, pipeline, node);
400   else
401     emit_vertex_buffer_geometry (fb, pipeline, node);
402 }
403 
404 void
_cogl_pango_display_list_render(CoglFramebuffer * fb,CoglPangoDisplayList * dl,const CoglColor * color)405 _cogl_pango_display_list_render (CoglFramebuffer *fb,
406                                  CoglPangoDisplayList *dl,
407                                  const CoglColor *color)
408 {
409   GSList *l;
410 
411   for (l = dl->nodes; l; l = l->next)
412     {
413       CoglPangoDisplayListNode *node = l->data;
414       CoglColor draw_color;
415 
416       if (node->pipeline == NULL)
417         {
418           if (node->type == COGL_PANGO_DISPLAY_LIST_TEXTURE)
419             node->pipeline =
420               _cogl_pango_pipeline_cache_get (dl->pipeline_cache,
421                                               node->d.texture.texture);
422           else
423             node->pipeline =
424               _cogl_pango_pipeline_cache_get (dl->pipeline_cache,
425                                               NULL);
426         }
427 
428       if (node->color_override)
429         /* Use the override color but preserve the alpha from the
430            draw color */
431         cogl_color_init_from_4ub (&draw_color,
432                                   cogl_color_get_red_byte (&node->color),
433                                   cogl_color_get_green_byte (&node->color),
434                                   cogl_color_get_blue_byte (&node->color),
435                                   cogl_color_get_alpha_byte (color));
436       else
437         draw_color = *color;
438       cogl_color_premultiply (&draw_color);
439 
440       cogl_pipeline_set_color (node->pipeline, &draw_color);
441 
442       switch (node->type)
443         {
444         case COGL_PANGO_DISPLAY_LIST_TEXTURE:
445           _cogl_framebuffer_draw_display_list_texture (fb, node->pipeline, node);
446           break;
447 
448         case COGL_PANGO_DISPLAY_LIST_RECTANGLE:
449           cogl_framebuffer_draw_rectangle (fb,
450                                            node->pipeline,
451                                            node->d.rectangle.x_1,
452                                            node->d.rectangle.y_1,
453                                            node->d.rectangle.x_2,
454                                            node->d.rectangle.y_2);
455           break;
456 
457         case COGL_PANGO_DISPLAY_LIST_TRAPEZOID:
458           cogl_framebuffer_draw_primitive (fb, node->pipeline,
459                                            node->d.trapezoid.primitive);
460           break;
461         }
462     }
463 }
464 
465 static void
_cogl_pango_display_list_node_free(CoglPangoDisplayListNode * node)466 _cogl_pango_display_list_node_free (CoglPangoDisplayListNode *node)
467 {
468   if (node->type == COGL_PANGO_DISPLAY_LIST_TEXTURE)
469     {
470       g_array_free (node->d.texture.rectangles, TRUE);
471       if (node->d.texture.texture != NULL)
472         cogl_object_unref (node->d.texture.texture);
473       if (node->d.texture.primitive != NULL)
474         cogl_object_unref (node->d.texture.primitive);
475     }
476   else if (node->type == COGL_PANGO_DISPLAY_LIST_TRAPEZOID)
477     cogl_object_unref (node->d.trapezoid.primitive);
478 
479   if (node->pipeline)
480     cogl_object_unref (node->pipeline);
481 
482   g_slice_free (CoglPangoDisplayListNode, node);
483 }
484 
485 void
_cogl_pango_display_list_clear(CoglPangoDisplayList * dl)486 _cogl_pango_display_list_clear (CoglPangoDisplayList *dl)
487 {
488   g_slist_foreach (dl->nodes, (GFunc) _cogl_pango_display_list_node_free, NULL);
489   g_slist_free (dl->nodes);
490   dl->nodes = NULL;
491   dl->last_node = NULL;
492 }
493 
494 void
_cogl_pango_display_list_free(CoglPangoDisplayList * dl)495 _cogl_pango_display_list_free (CoglPangoDisplayList *dl)
496 {
497   _cogl_pango_display_list_clear (dl);
498   g_slice_free (CoglPangoDisplayList, dl);
499 }
500