uvtd: vt: implement VT_GETMODE/SETMODE ioctl state-tracking
[platform/upstream/kmscon.git] / src / text_cairo.c
1 /*
2  * kmscon - Cairo Text Renderer Backend
3  *
4  * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@googlemail.com>
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining
7  * a copy of this software and associated documentation files
8  * (the "Software"), to deal in the Software without restriction, including
9  * without limitation the rights to use, copy, modify, merge, publish,
10  * distribute, sublicense, and/or sell copies of the Software, and to
11  * permit persons to whom the Software is furnished to do so, subject to
12  * the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be included
15  * in all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
21  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
22  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24  */
25
26 /*
27  * Cairo based text renderer
28  */
29
30 #include <cairo.h>
31 #include <errno.h>
32 #include <stdbool.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include "shl_hashtable.h"
36 #include "shl_log.h"
37 #include "text.h"
38 #include "uterm_video.h"
39
40 #define LOG_SUBSYSTEM "text_cairo"
41
42 struct tc_glyph {
43         const struct kmscon_glyph *glyph;
44         cairo_surface_t *surf;
45         uint8_t *data;
46 };
47
48 struct tc_cairo {
49         struct shl_hashtable *glyphs;
50         struct shl_hashtable *bold_glyphs;
51
52         bool new_stride;
53         unsigned int cur;
54         struct uterm_video_buffer buf[2];
55         cairo_surface_t *surf[2];
56         cairo_t *ctx[2];
57
58         bool use_indirect;
59         uint8_t *data[2];
60         struct uterm_video_buffer vbuf;
61 };
62
63 static int tc_init(struct kmscon_text *txt)
64 {
65         struct tc_cairo *tc;
66
67         tc = malloc(sizeof(*tc));
68         if (!tc)
69                 return -ENOMEM;
70
71         txt->data = tc;
72         return 0;
73 }
74
75 static void tc_destroy(struct kmscon_text *txt)
76 {
77         struct tc_cairo *tc = txt->data;
78
79         free(tc);
80 }
81
82 static void free_glyph(void *data)
83 {
84         struct tc_glyph *glyph = data;
85
86         cairo_surface_destroy(glyph->surf);
87         free(glyph->data);
88         free(glyph);
89 }
90
91 static unsigned int format_u2c(unsigned int f)
92 {
93         switch (f) {
94         case UTERM_FORMAT_XRGB32:
95                 return CAIRO_FORMAT_ARGB32;
96         case UTERM_FORMAT_RGB16:
97                 return CAIRO_FORMAT_RGB16_565;
98         case UTERM_FORMAT_GREY:
99                 return CAIRO_FORMAT_A8;
100         default:
101                 return CAIRO_FORMAT_INVALID;
102         }
103 }
104
105 static int alloc_indirect(struct kmscon_text *txt,
106                           unsigned int w, unsigned int h)
107 {
108         struct tc_cairo *tc = txt->data;
109         unsigned int s, i, format;
110         int ret;
111
112         log_info("using blitting engine");
113
114         format = format_u2c(UTERM_FORMAT_XRGB32);
115         s = cairo_format_stride_for_width(format, w);
116
117         tc->data[0] = malloc(s * h);
118         tc->data[1] = malloc(s * h);
119         if (!tc->data[0] || !tc->data[1]) {
120                 log_error("cannot allocate memory for render-buffer");
121                 ret = -ENOMEM;
122                 goto err_free;
123         }
124
125         for (i = 0; i < 2; ++i) {
126                 tc->surf[i] = cairo_image_surface_create_for_data(tc->data[i],
127                                                                   format,
128                                                                   w, h, s);
129
130                 ret = cairo_surface_status(tc->surf[i]);
131                 if (ret != CAIRO_STATUS_SUCCESS) {
132                         log_error("cannot create cairo surface: %d", ret);
133                         goto err_cairo;
134                 }
135         }
136
137         tc->vbuf.width = w;
138         tc->vbuf.height = h;
139         tc->vbuf.stride = s;
140         tc->vbuf.format = UTERM_FORMAT_XRGB32;
141         tc->use_indirect = true;
142         return 0;
143
144 err_cairo:
145         cairo_surface_destroy(tc->surf[1]);
146         cairo_surface_destroy(tc->surf[0]);
147 err_free:
148         free(tc->data[1]);
149         free(tc->data[0]);
150         tc->data[1] = NULL;
151         tc->data[0] = NULL;
152         return ret;
153 }
154
155 static int tc_set(struct kmscon_text *txt)
156 {
157         struct tc_cairo *tc = txt->data;
158         int ret, ret2;
159         unsigned int format, w, h;
160         struct uterm_mode *m;
161
162         memset(tc, 0, sizeof(*tc));
163         m = uterm_display_get_current(txt->disp);
164         w = uterm_mode_get_width(m);
165         h = uterm_mode_get_height(m);
166
167         ret = shl_hashtable_new(&tc->glyphs, shl_direct_hash,
168                                 shl_direct_equal, NULL,
169                                 free_glyph);
170         if (ret)
171                 return ret;
172
173         ret = shl_hashtable_new(&tc->bold_glyphs, shl_direct_hash,
174                                 shl_direct_equal, NULL,
175                                 free_glyph);
176         if (ret)
177                 goto err_htable;
178
179         /*
180          * TODO: It is actually faster to use a local shadow buffer and then
181          * blit all data to the framebuffer afterwards. Reads seem to be
182          * horribly slow on some mmap'ed framebuffers. However, that's not true
183          * for all so we actually don't know which to use here.
184          */
185         ret = uterm_display_get_buffers(txt->disp, tc->buf,
186                                         UTERM_FORMAT_XRGB32 |
187                                         UTERM_FORMAT_RGB16);
188         if (ret) {
189                 log_warning("cannot get buffers for display %p",
190                             txt->disp);
191                 ret = alloc_indirect(txt, w, h);
192                 if (ret)
193                         goto err_htable_bold;
194         } else {
195                 format = format_u2c(tc->buf[0].format);
196                 tc->surf[0] = cairo_image_surface_create_for_data(
197                                                         tc->buf[0].data,
198                                                         format,
199                                                         tc->buf[0].width,
200                                                         tc->buf[0].height,
201                                                         tc->buf[0].stride);
202                 format = format_u2c(tc->buf[1].format);
203                 tc->surf[1] = cairo_image_surface_create_for_data(
204                                                         tc->buf[1].data,
205                                                         format,
206                                                         tc->buf[1].width,
207                                                         tc->buf[1].height,
208                                                         tc->buf[1].stride);
209
210                 ret = cairo_surface_status(tc->surf[0]);
211                 ret2 = cairo_surface_status(tc->surf[1]);
212                 if (ret != CAIRO_STATUS_SUCCESS ||
213                     ret2 != CAIRO_STATUS_SUCCESS) {
214                         log_error("cannot create cairo surface: %d %d",
215                                   ret, ret2);
216                         cairo_surface_destroy(tc->surf[1]);
217                         cairo_surface_destroy(tc->surf[0]);
218                         ret = alloc_indirect(txt, w, h);
219                         if (ret)
220                                 goto err_htable_bold;
221                 }
222         }
223
224         tc->ctx[0] = cairo_create(tc->surf[0]);
225         tc->ctx[1] = cairo_create(tc->surf[1]);
226         ret = cairo_status(tc->ctx[0]);
227         ret2 = cairo_status(tc->ctx[1]);
228         if (ret != CAIRO_STATUS_SUCCESS || ret2 != CAIRO_STATUS_SUCCESS) {
229                 log_error("cannot create cairo contexts: %d %d", ret, ret2);
230                 goto err_ctx;
231         }
232
233         txt->cols = w / txt->font->attr.width;
234         txt->rows = h / txt->font->attr.height;
235
236         return 0;
237
238 err_ctx:
239         cairo_destroy(tc->ctx[1]);
240         cairo_destroy(tc->ctx[0]);
241         cairo_surface_destroy(tc->surf[1]);
242         cairo_surface_destroy(tc->surf[0]);
243         free(tc->data[1]);
244         free(tc->data[0]);
245 err_htable_bold:
246         shl_hashtable_free(tc->bold_glyphs);
247 err_htable:
248         shl_hashtable_free(tc->glyphs);
249         return ret;
250 }
251
252 static void tc_unset(struct kmscon_text *txt)
253 {
254         struct tc_cairo *tc = txt->data;
255
256         cairo_destroy(tc->ctx[1]);
257         cairo_destroy(tc->ctx[0]);
258         cairo_surface_destroy(tc->surf[1]);
259         cairo_surface_destroy(tc->surf[0]);
260         free(tc->data[1]);
261         free(tc->data[0]);
262         shl_hashtable_free(tc->bold_glyphs);
263         shl_hashtable_free(tc->glyphs);
264 }
265
266 static int find_glyph(struct kmscon_text *txt, struct tc_glyph **out,
267                       uint32_t id, const uint32_t *ch, size_t len, bool bold)
268 {
269         struct tc_cairo *tc = txt->data;
270         struct tc_glyph *glyph;
271         struct shl_hashtable *gtable;
272         struct kmscon_font *font;
273         const struct uterm_video_buffer *buf;
274         uint8_t *dst, *src;
275         unsigned int format, i;
276         int ret, stride;
277         bool res;
278
279         if (bold) {
280                 gtable = tc->bold_glyphs;
281                 font = txt->bold_font;
282         } else {
283                 gtable = tc->glyphs;
284                 font = txt->font;
285         }
286
287         res = shl_hashtable_find(gtable, (void**)&glyph,
288                                  (void*)(unsigned long)id);
289         if (res) {
290                 *out = glyph;
291                 return 0;
292         }
293
294         glyph = malloc(sizeof(*glyph));
295         if (!glyph)
296                 return -ENOMEM;
297         memset(glyph, 0, sizeof(*glyph));
298
299         if (!len)
300                 ret = kmscon_font_render_empty(font, &glyph->glyph);
301         else
302                 ret = kmscon_font_render(font, id, ch, len, &glyph->glyph);
303
304         if (ret) {
305                 ret = kmscon_font_render_inval(font, &glyph->glyph);
306                 if (ret)
307                         goto err_free;
308         }
309
310         buf = &glyph->glyph->buf;
311         stride = buf->stride;
312         format = format_u2c(buf->format);
313         glyph->surf = cairo_image_surface_create_for_data(buf->data,
314                                                           format,
315                                                           buf->width,
316                                                           buf->height,
317                                                           buf->stride);
318         ret = cairo_surface_status(glyph->surf);
319         if (ret == CAIRO_STATUS_INVALID_STRIDE) {
320                 stride = cairo_format_stride_for_width(format, buf->width);
321                 if (!tc->new_stride) {
322                         tc->new_stride = true;
323                         log_debug("wrong stride, copy buffer (%d => %d)",
324                           buf->stride, stride);
325                 }
326
327                 glyph->data = malloc(stride * buf->height);
328                 if (!glyph->data) {
329                         log_error("cannot allocate memory for glyph storage");
330                         ret = -ENOMEM;
331                         goto err_cairo;
332                 }
333
334                 src = buf->data;
335                 dst = glyph->data;
336                 for (i = 0; i < buf->height; ++i) {
337                         memcpy(dst, src, buf->width);
338                         dst += stride;
339                         src += buf->stride;
340                 }
341
342                 cairo_surface_destroy(glyph->surf);
343                 glyph->surf = cairo_image_surface_create_for_data(glyph->data,
344                                                                   format,
345                                                                   buf->width,
346                                                                   buf->height,
347                                                                   stride);
348                 ret = cairo_surface_status(glyph->surf);
349         }
350         if (ret != CAIRO_STATUS_SUCCESS) {
351                 log_error("cannot create cairo-glyph: %d %p %d %d %d %d",
352                           ret, glyph->data ? glyph->data : buf->data, format,
353                           buf->width, buf->height, stride);
354                 ret = -EFAULT;
355                 goto err_cairo;
356         }
357
358         ret = shl_hashtable_insert(gtable, (void*)(long)id, glyph);
359         if (ret)
360                 goto err_cairo;
361
362         *out = glyph;
363         return 0;
364
365 err_cairo:
366         cairo_surface_destroy(glyph->surf);
367         free(glyph->data);
368 err_free:
369         free(glyph);
370         return ret;
371 }
372
373 static int tc_prepare(struct kmscon_text *txt)
374 {
375         struct tc_cairo *tc = txt->data;
376         int ret;
377
378         ret = uterm_display_use(txt->disp, NULL);
379         if (ret < 0) {
380                 log_error("cannot use display %p", txt->disp);
381                 return ret;
382         }
383
384         tc->cur = ret;
385
386         return 0;
387 }
388
389 static int tc_draw(struct kmscon_text *txt,
390                    uint32_t id, const uint32_t *ch, size_t len,
391                    unsigned int width,
392                    unsigned int posx, unsigned int posy,
393                    const struct tsm_screen_attr *attr)
394 {
395         struct tc_cairo *tc = txt->data;
396         cairo_t *cr = tc->ctx[tc->cur];
397         struct tc_glyph *glyph;
398         int ret;
399
400         if (!width)
401                 return 0;
402
403         ret = find_glyph(txt, &glyph, id, ch, len, attr->bold);
404         if (ret)
405                 return ret;
406
407         cairo_rectangle(cr,
408                         posx * txt->font->attr.width,
409                         posy * txt->font->attr.height,
410                         txt->font->attr.width,
411                         txt->font->attr.height);
412
413         if (attr->inverse)
414                 cairo_set_source_rgb(cr, attr->fr / 255.0, attr->fg / 255.0,
415                                      attr->fb / 255.0);
416         else
417                 cairo_set_source_rgb(cr, attr->br / 255.0, attr->bg / 255.0,
418                                      attr->bb / 255.0);
419
420         cairo_fill(cr);
421
422         if (attr->inverse)
423                 cairo_set_source_rgb(cr, attr->br / 255.0, attr->bg / 255.0,
424                                      attr->bb / 255.0);
425         else
426                 cairo_set_source_rgb(cr, attr->fr / 255.0, attr->fg / 255.0,
427                                      attr->fb / 255.0);
428
429         cairo_mask_surface(cr, glyph->surf,
430                            posx * txt->font->attr.width,
431                            posy * txt->font->attr.height);
432
433         return 0;
434 }
435
436 static int tc_render(struct kmscon_text *txt)
437 {
438         struct tc_cairo *tc = txt->data;
439         int ret;
440
441         cairo_surface_flush(tc->surf[tc->cur]);
442
443         if (!tc->use_indirect)
444                 return 0;
445
446         tc->vbuf.data = tc->data[tc->cur];
447         ret = uterm_display_blit(txt->disp, &tc->vbuf, 0, 0);
448         if (ret) {
449                 log_error("cannot blit back-buffer to display: %d", ret);
450                 return ret;
451         }
452
453         return 0;
454 }
455
456 struct kmscon_text_ops kmscon_text_cairo_ops = {
457         .name = "cairo",
458         .owner = NULL,
459         .init = tc_init,
460         .destroy = tc_destroy,
461         .set = tc_set,
462         .unset = tc_unset,
463         .prepare = tc_prepare,
464         .draw = tc_draw,
465         .render = tc_render,
466         .abort = NULL,
467 };