2 * kmscon - OpenGL Textures Text Renderer Backend
4 * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@googlemail.com>
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:
14 * The above copyright notice and this permission notice shall be included
15 * in all copies or substantial portions of the Software.
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.
27 * SECTION:text_gltex.c
28 * @short_description: OpenGL Textures Text Renderer Backend
31 * Uses OpenGL textures to store glyph information and draws these textures with
32 * a custom fragment shader.
33 * Glyphs are stored in texture-atlases. OpenGL has heavy restrictions on
34 * texture sizes so we need to use multiple atlases. As there is no way to pass
35 * a varying amount of textures to a shader, we need to render the screen for
39 #define GL_GLEXT_PROTOTYPES
42 #include <GLES2/gl2.h>
43 #include <GLES2/gl2ext.h>
48 #include "shl_dlist.h"
49 #include "shl_hashtable.h"
52 #include "static_gl.h"
54 #include "uterm_video.h"
56 #define LOG_SUBSYSTEM "text_gltex"
59 struct shl_dlist list;
67 unsigned int cache_size;
68 unsigned int cache_num;
70 GLfloat *cache_texpos;
79 const struct kmscon_glyph *glyph;
84 #define GLYPH_WIDTH(gly) ((gly)->glyph->buf.width)
85 #define GLYPH_HEIGHT(gly) ((gly)->glyph->buf.height)
86 #define GLYPH_STRIDE(gly) ((gly)->glyph->buf.stride)
87 #define GLYPH_DATA(gly) ((gly)->glyph->buf.data)
90 struct shl_hashtable *glyphs;
91 struct shl_hashtable *bold_glyphs;
92 unsigned int max_tex_size;
95 struct shl_dlist atlases;
100 struct gl_shader *shader;
103 GLuint uni_advance_htex;
104 GLuint uni_advance_vtex;
110 #define FONT_WIDTH(txt) ((txt)->font->attr.width)
111 #define FONT_HEIGHT(txt) ((txt)->font->attr.height)
113 static int gltex_init(struct kmscon_text *txt)
117 gt = malloc(sizeof(*gt));
125 static void gltex_destroy(struct kmscon_text *txt)
127 struct gltex *gt = txt->data;
132 static void free_glyph(void *data)
134 struct glyph *glyph = data;
139 extern const char *gl_static_gltex_vert;
140 extern const char *gl_static_gltex_frag;
142 static int gltex_set(struct kmscon_text *txt)
144 struct gltex *gt = txt->data;
146 static char *attr[] = { "position", "texture_position",
147 "fgcolor", "bgcolor" };
150 struct uterm_mode *mode;
153 memset(gt, 0, sizeof(*gt));
154 shl_dlist_init(>->atlases);
156 ret = shl_hashtable_new(>->glyphs, shl_direct_hash,
157 shl_direct_equal, NULL,
162 ret = shl_hashtable_new(>->bold_glyphs, shl_direct_hash,
163 shl_direct_equal, NULL,
168 ret = uterm_display_use(txt->disp, &opengl);
169 if (ret < 0 || !opengl) {
170 if (ret == -EOPNOTSUPP)
171 log_error("display doesn't support hardware-acceleration");
172 goto err_bold_htable;
177 ret = gl_shader_new(>->shader, gl_static_gltex_vert,
178 gl_static_gltex_frag, attr, 4, log_llog, NULL);
180 goto err_bold_htable;
182 gt->uni_proj = gl_shader_get_uniform(gt->shader, "projection");
183 gt->uni_atlas = gl_shader_get_uniform(gt->shader, "atlas");
184 gt->uni_advance_htex = gl_shader_get_uniform(gt->shader,
186 gt->uni_advance_vtex = gl_shader_get_uniform(gt->shader,
189 if (gl_has_error(gt->shader)) {
190 log_warning("cannot create shader");
194 mode = uterm_display_get_current(txt->disp);
195 gt->sw = uterm_mode_get_width(mode);
196 gt->sh = uterm_mode_get_height(mode);
198 txt->cols = gt->sw / FONT_WIDTH(txt);
199 txt->rows = gt->sh / FONT_HEIGHT(txt);
201 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &s);
206 gt->max_tex_size = s;
210 ext = (const char*)glGetString(GL_EXTENSIONS);
211 if (ext && strstr((const char*)ext, "GL_EXT_unpack_subimage")) {
212 gt->supports_rowlen = true;
214 log_warning("your GL implementation does not support GL_EXT_unpack_subimage, glyph-rendering may be slower than usual");
220 gl_shader_unref(gt->shader);
222 shl_hashtable_free(gt->bold_glyphs);
224 shl_hashtable_free(gt->glyphs);
228 static void gltex_unset(struct kmscon_text *txt)
230 struct gltex *gt = txt->data;
232 struct shl_dlist *iter;
236 ret = uterm_display_use(txt->disp, NULL);
239 log_warning("cannot activate OpenGL-CTX during destruction");
242 shl_hashtable_free(gt->bold_glyphs);
243 shl_hashtable_free(gt->glyphs);
245 while (!shl_dlist_empty(>->atlases)) {
246 iter = gt->atlases.next;
247 shl_dlist_unlink(iter);
248 atlas = shl_dlist_entry(iter, struct atlas, list);
250 free(atlas->cache_pos);
251 free(atlas->cache_texpos);
252 free(atlas->cache_fgcol);
253 free(atlas->cache_bgcol);
256 gl_tex_free(&atlas->tex, 1);
261 gl_shader_unref(gt->shader);
267 /* returns an atlas with at least 1 free glyph position; NULL on error */
268 static struct atlas *get_atlas(struct kmscon_text *txt, unsigned int num)
270 struct gltex *gt = txt->data;
273 unsigned int width, height, nsize;
276 /* check whether the last added atlas has still room for one glyph */
277 if (!shl_dlist_empty(>->atlases)) {
278 atlas = shl_dlist_entry(gt->atlases.next, struct atlas,
280 if (atlas->fill + num <= atlas->count)
284 /* all atlases are full so we have to create a new atlas */
285 atlas = malloc(sizeof(*atlas));
288 memset(atlas, 0, sizeof(*atlas));
292 gl_tex_new(&atlas->tex, 1);
294 if (err != GL_NO_ERROR || !atlas->tex) {
296 log_warning("cannot create new OpenGL texture: %d", err);
300 newsize = gt->max_tex_size / FONT_WIDTH(txt);
304 /* OpenGL texture sizes are heavily restricted so we need to find a
305 * valid texture size that is big enough to hold as many glyphs as
306 * possible but at least 1 */
308 width = shl_next_pow2(FONT_WIDTH(txt) * newsize);
309 height = shl_next_pow2(FONT_HEIGHT(txt));
313 glBindTexture(GL_TEXTURE_2D, atlas->tex);
314 glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, width, height,
315 0, GL_ALPHA, GL_UNSIGNED_BYTE, NULL);
318 if (err != GL_NO_ERROR) {
324 log_warning("OpenGL textures too small for a single glyph (%d)",
329 log_debug("new atlas of size %ux%u for %zu", width, height, newsize);
331 nsize = txt->cols * txt->rows;
333 atlas->cache_pos = malloc(sizeof(GLfloat) * nsize * 2 * 6);
334 if (!atlas->cache_pos)
337 atlas->cache_texpos = malloc(sizeof(GLfloat) * nsize * 2 * 6);
338 if (!atlas->cache_texpos)
341 atlas->cache_fgcol = malloc(sizeof(GLfloat) * nsize * 3 * 6);
342 if (!atlas->cache_fgcol)
345 atlas->cache_bgcol = malloc(sizeof(GLfloat) * nsize * 3 * 6);
346 if (!atlas->cache_bgcol)
349 atlas->cache_size = nsize;
350 atlas->count = newsize;
351 atlas->width = width;
352 atlas->height = height;
353 atlas->advance_htex = 1.0 / atlas->width * FONT_WIDTH(txt);
354 atlas->advance_vtex = 1.0 / atlas->height * FONT_HEIGHT(txt);
356 shl_dlist_link(>->atlases, &atlas->list);
360 free(atlas->cache_pos);
361 free(atlas->cache_texpos);
362 free(atlas->cache_fgcol);
363 free(atlas->cache_bgcol);
365 gl_tex_free(&atlas->tex, 1);
371 static int find_glyph(struct kmscon_text *txt, struct glyph **out,
372 uint32_t id, const uint32_t *ch, size_t len, bool bold)
374 struct gltex *gt = txt->data;
380 uint8_t *packed_data, *dst, *src;
381 struct shl_hashtable *gtable;
382 struct kmscon_font *font;
385 gtable = gt->bold_glyphs;
386 font = txt->bold_font;
392 res = shl_hashtable_find(gtable, (void**)&glyph,
393 (void*)(unsigned long)id);
399 glyph = malloc(sizeof(*glyph));
402 memset(glyph, 0, sizeof(*glyph));
405 ret = kmscon_font_render_empty(font, &glyph->glyph);
407 ret = kmscon_font_render(font, id, ch, len, &glyph->glyph);
410 ret = kmscon_font_render_inval(font, &glyph->glyph);
415 atlas = get_atlas(txt, glyph->glyph->width);
421 /* Funnily, not all OpenGLESv2 implementations support specifying the
422 * stride of a texture. Therefore, we then need to create a
423 * temporary image with a stride equal to the image width for loading
424 * the texture. This may slow down loading new glyphs but doesn't affect
425 * overall rendering performance. But driver developers should really
430 glBindTexture(GL_TEXTURE_2D, atlas->tex);
431 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
432 if (!gt->supports_rowlen) {
433 if (GLYPH_STRIDE(glyph) == GLYPH_WIDTH(glyph)) {
434 glTexSubImage2D(GL_TEXTURE_2D, 0,
435 FONT_WIDTH(txt) * atlas->fill, 0,
438 GL_ALPHA, GL_UNSIGNED_BYTE,
441 packed_data = malloc(GLYPH_WIDTH(glyph) * GLYPH_HEIGHT(glyph));
443 log_error("cannot allocate memory for glyph storage");
448 src = GLYPH_DATA(glyph);
450 for (i = 0; i < GLYPH_HEIGHT(glyph); ++i) {
451 memcpy(dst, src, GLYPH_WIDTH(glyph));
452 dst += GLYPH_WIDTH(glyph);
453 src += GLYPH_STRIDE(glyph);
456 glTexSubImage2D(GL_TEXTURE_2D, 0,
457 FONT_WIDTH(txt) * atlas->fill, 0,
460 GL_ALPHA, GL_UNSIGNED_BYTE,
465 glPixelStorei(GL_UNPACK_ROW_LENGTH, GLYPH_STRIDE(glyph));
466 glTexSubImage2D(GL_TEXTURE_2D, 0,
467 FONT_WIDTH(txt) * atlas->fill, 0,
470 GL_ALPHA, GL_UNSIGNED_BYTE,
472 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
474 glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
476 /* Check for GL-errors
477 * As OpenGL is a state-machine, we cannot really tell which call failed
478 * without adding a glGetError() after each call. This is totally
479 * overkill so let us at least catch the error afterwards.
480 * We also add a hint to disable OpenGL if this does not work. This
481 * should _always_ work but OpenGL is kind of a black-box that isn't
482 * verbose at all and many things can go wrong. */
485 if (err != GL_NO_ERROR) {
487 log_warning("cannot load glyph data into OpenGL texture (%d: %s); disable the GL-renderer if this does not work reliably",
488 err, gl_err_to_str(err));
493 glyph->atlas = atlas;
494 glyph->texoff = atlas->fill;
496 ret = shl_hashtable_insert(gtable, (void*)(long)id, glyph);
500 atlas->fill += glyph->glyph->width;
510 static int gltex_prepare(struct kmscon_text *txt)
512 struct gltex *gt = txt->data;
514 struct shl_dlist *iter;
517 ret = uterm_display_use(txt->disp, NULL);
521 shl_dlist_for_each(iter, >->atlases) {
522 atlas = shl_dlist_entry(iter, struct atlas, list);
524 atlas->cache_num = 0;
527 gt->advance_x = 2.0 / gt->sw * FONT_WIDTH(txt);
528 gt->advance_y = 2.0 / gt->sh * FONT_HEIGHT(txt);
533 static int gltex_draw(struct kmscon_text *txt,
534 uint32_t id, const uint32_t *ch, size_t len,
536 unsigned int posx, unsigned int posy,
537 const struct tsm_screen_attr *attr)
539 struct gltex *gt = txt->data;
547 ret = find_glyph(txt, &glyph, id, ch, len, attr->bold);
550 atlas = glyph->atlas;
552 if (atlas->cache_num >= atlas->cache_size)
555 atlas->cache_pos[atlas->cache_num * 2 * 6 + 0] =
556 gt->advance_x * posx - 1;
557 atlas->cache_pos[atlas->cache_num * 2 * 6 + 1] =
558 1 - gt->advance_y * posy;
559 atlas->cache_pos[atlas->cache_num * 2 * 6 + 2] =
560 gt->advance_x * posx - 1;
561 atlas->cache_pos[atlas->cache_num * 2 * 6 + 3] =
562 1 - (gt->advance_y * posy + gt->advance_y);
563 atlas->cache_pos[atlas->cache_num * 2 * 6 + 4] =
564 gt->advance_x * posx + width * gt->advance_x - 1;
565 atlas->cache_pos[atlas->cache_num * 2 * 6 + 5] =
566 1 - (gt->advance_y * posy + gt->advance_y);
568 atlas->cache_pos[atlas->cache_num * 2 * 6 + 6] =
569 gt->advance_x * posx - 1;
570 atlas->cache_pos[atlas->cache_num * 2 * 6 + 7] =
571 1 - gt->advance_y * posy;
572 atlas->cache_pos[atlas->cache_num * 2 * 6 + 8] =
573 gt->advance_x * posx + width * gt->advance_x - 1;
574 atlas->cache_pos[atlas->cache_num * 2 * 6 + 9] =
575 1 - (gt->advance_y * posy + gt->advance_y);
576 atlas->cache_pos[atlas->cache_num * 2 * 6 + 10] =
577 gt->advance_x * posx + width * gt->advance_x - 1;
578 atlas->cache_pos[atlas->cache_num * 2 * 6 + 11] =
579 1 - gt->advance_y * posy;
581 atlas->cache_texpos[atlas->cache_num * 2 * 6 + 0] = glyph->texoff;
582 atlas->cache_texpos[atlas->cache_num * 2 * 6 + 1] = 0.0;
583 atlas->cache_texpos[atlas->cache_num * 2 * 6 + 2] = glyph->texoff;
584 atlas->cache_texpos[atlas->cache_num * 2 * 6 + 3] = 1.0;
585 atlas->cache_texpos[atlas->cache_num * 2 * 6 + 4] = glyph->texoff + width;
586 atlas->cache_texpos[atlas->cache_num * 2 * 6 + 5] = 1.0;
588 atlas->cache_texpos[atlas->cache_num * 2 * 6 + 6] = glyph->texoff;
589 atlas->cache_texpos[atlas->cache_num * 2 * 6 + 7] = 0.0;
590 atlas->cache_texpos[atlas->cache_num * 2 * 6 + 8] = glyph->texoff + width;
591 atlas->cache_texpos[atlas->cache_num * 2 * 6 + 9] = 1.0;
592 atlas->cache_texpos[atlas->cache_num * 2 * 6 + 10] = glyph->texoff + width;
593 atlas->cache_texpos[atlas->cache_num * 2 * 6 + 11] = 0.0;
595 for (i = 0; i < 6; ++i) {
596 idx = atlas->cache_num * 3 * 6 + i * 3;
598 atlas->cache_fgcol[idx + 0] = attr->br / 255.0;
599 atlas->cache_fgcol[idx + 1] = attr->bg / 255.0;
600 atlas->cache_fgcol[idx + 2] = attr->bb / 255.0;
601 atlas->cache_bgcol[idx + 0] = attr->fr / 255.0;
602 atlas->cache_bgcol[idx + 1] = attr->fg / 255.0;
603 atlas->cache_bgcol[idx + 2] = attr->fb / 255.0;
605 atlas->cache_fgcol[idx + 0] = attr->fr / 255.0;
606 atlas->cache_fgcol[idx + 1] = attr->fg / 255.0;
607 atlas->cache_fgcol[idx + 2] = attr->fb / 255.0;
608 atlas->cache_bgcol[idx + 0] = attr->br / 255.0;
609 atlas->cache_bgcol[idx + 1] = attr->bg / 255.0;
610 atlas->cache_bgcol[idx + 2] = attr->bb / 255.0;
619 static int gltex_render(struct kmscon_text *txt)
621 struct gltex *gt = txt->data;
623 struct shl_dlist *iter;
628 gl_shader_use(gt->shader);
630 glViewport(0, 0, gt->sw, gt->sh);
634 glUniformMatrix4fv(gt->uni_proj, 1, GL_FALSE, mat);
636 glEnableVertexAttribArray(0);
637 glEnableVertexAttribArray(1);
638 glEnableVertexAttribArray(2);
639 glEnableVertexAttribArray(3);
641 glActiveTexture(GL_TEXTURE0);
642 glUniform1i(gt->uni_atlas, 0);
644 shl_dlist_for_each(iter, >->atlases) {
645 atlas = shl_dlist_entry(iter, struct atlas, list);
646 if (!atlas->cache_num)
649 glBindTexture(GL_TEXTURE_2D, atlas->tex);
650 glUniform1f(gt->uni_advance_htex, atlas->advance_htex);
651 glUniform1f(gt->uni_advance_vtex, atlas->advance_vtex);
653 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, atlas->cache_pos);
654 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, atlas->cache_texpos);
655 glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, atlas->cache_fgcol);
656 glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 0, atlas->cache_bgcol);
657 glDrawArrays(GL_TRIANGLES, 0, 6 * atlas->cache_num);
660 glDisableVertexAttribArray(0);
661 glDisableVertexAttribArray(1);
662 glDisableVertexAttribArray(2);
663 glDisableVertexAttribArray(3);
665 if (gl_has_error(gt->shader)) {
666 log_warning("rendering console caused OpenGL errors");
673 struct kmscon_text_ops kmscon_text_gltex_ops = {
677 .destroy = gltex_destroy,
679 .unset = gltex_unset,
680 .prepare = gltex_prepare,
682 .render = gltex_render,