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>
49 #include "shl_dlist.h"
50 #include "shl_hashtable.h"
51 #include "static_gl.h"
53 #include "uterm_video.h"
55 #define LOG_SUBSYSTEM "text_gltex"
58 struct shl_dlist list;
66 unsigned int cache_size;
67 unsigned int cache_num;
69 GLfloat *cache_texpos;
78 const struct kmscon_glyph *glyph;
83 #define GLYPH_WIDTH(gly) ((gly)->glyph->buf.width)
84 #define GLYPH_HEIGHT(gly) ((gly)->glyph->buf.height)
85 #define GLYPH_STRIDE(gly) ((gly)->glyph->buf.stride)
86 #define GLYPH_DATA(gly) ((gly)->glyph->buf.data)
89 struct shl_hashtable *glyphs;
90 struct shl_hashtable *bold_glyphs;
91 unsigned int max_tex_size;
94 struct shl_dlist atlases;
99 struct gl_shader *shader;
102 GLuint uni_advance_htex;
103 GLuint uni_advance_vtex;
109 #define FONT_WIDTH(txt) ((txt)->font->attr.width)
110 #define FONT_HEIGHT(txt) ((txt)->font->attr.height)
112 static int gltex_init(struct kmscon_text *txt)
116 gt = malloc(sizeof(*gt));
124 static void gltex_destroy(struct kmscon_text *txt)
126 struct gltex *gt = txt->data;
131 static void free_glyph(void *data)
133 struct glyph *glyph = data;
138 extern const char *gl_static_gltex_vert;
139 extern const char *gl_static_gltex_frag;
141 static int gltex_set(struct kmscon_text *txt)
143 struct gltex *gt = txt->data;
145 static char *attr[] = { "position", "texture_position",
146 "fgcolor", "bgcolor" };
149 struct uterm_mode *mode;
152 memset(gt, 0, sizeof(*gt));
153 shl_dlist_init(>->atlases);
155 ret = shl_hashtable_new(>->glyphs, shl_direct_hash,
156 shl_direct_equal, NULL,
161 ret = shl_hashtable_new(>->bold_glyphs, shl_direct_hash,
162 shl_direct_equal, NULL,
167 ret = uterm_display_use(txt->disp, &opengl);
168 if (ret < 0 || !opengl) {
169 if (ret == -EOPNOTSUPP)
170 log_error("display doesn't support hardware-acceleration");
171 goto err_bold_htable;
176 ret = gl_shader_new(>->shader, gl_static_gltex_vert,
177 gl_static_gltex_frag, attr, 4, log_llog);
179 goto err_bold_htable;
181 gt->uni_proj = gl_shader_get_uniform(gt->shader, "projection");
182 gt->uni_atlas = gl_shader_get_uniform(gt->shader, "atlas");
183 gt->uni_advance_htex = gl_shader_get_uniform(gt->shader,
185 gt->uni_advance_vtex = gl_shader_get_uniform(gt->shader,
188 if (gl_has_error(gt->shader)) {
189 log_warning("cannot create shader");
193 mode = uterm_display_get_current(txt->disp);
194 gt->sw = uterm_mode_get_width(mode);
195 gt->sh = uterm_mode_get_height(mode);
197 txt->cols = gt->sw / FONT_WIDTH(txt);
198 txt->rows = gt->sh / FONT_HEIGHT(txt);
200 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &s);
205 gt->max_tex_size = s;
209 ext = (const char*)glGetString(GL_EXTENSIONS);
210 if (ext && strstr((const char*)ext, "GL_EXT_unpack_subimage")) {
211 gt->supports_rowlen = true;
213 log_warning("your GL implementation does not support GL_EXT_unpack_subimage, glyph-rendering may be slower than usual");
219 gl_shader_unref(gt->shader);
221 shl_hashtable_free(gt->bold_glyphs);
223 shl_hashtable_free(gt->glyphs);
227 static void gltex_unset(struct kmscon_text *txt)
229 struct gltex *gt = txt->data;
231 struct shl_dlist *iter;
235 ret = uterm_display_use(txt->disp, NULL);
238 log_warning("cannot activate OpenGL-CTX during destruction");
241 shl_hashtable_free(gt->bold_glyphs);
242 shl_hashtable_free(gt->glyphs);
244 while (!shl_dlist_empty(>->atlases)) {
245 iter = gt->atlases.next;
246 shl_dlist_unlink(iter);
247 atlas = shl_dlist_entry(iter, struct atlas, list);
249 free(atlas->cache_pos);
250 free(atlas->cache_texpos);
251 free(atlas->cache_fgcol);
252 free(atlas->cache_bgcol);
255 gl_tex_free(&atlas->tex, 1);
260 gl_shader_unref(gt->shader);
266 static unsigned int next_pow2(unsigned int num)
274 for (i = 1; i < sizeof(unsigned int) * CHAR_BIT; i <<= 1)
275 num = num | num >> i;
280 /* returns an atlas with at least 1 free glyph position; NULL on error */
281 static struct atlas *get_atlas(struct kmscon_text *txt, unsigned int num)
283 struct gltex *gt = txt->data;
286 unsigned int width, height, nsize;
289 /* check whether the last added atlas has still room for one glyph */
290 if (!shl_dlist_empty(>->atlases)) {
291 atlas = shl_dlist_entry(gt->atlases.next, struct atlas,
293 if (atlas->fill + num <= atlas->count)
297 /* all atlases are full so we have to create a new atlas */
298 atlas = malloc(sizeof(*atlas));
301 memset(atlas, 0, sizeof(*atlas));
305 gl_tex_new(&atlas->tex, 1);
307 if (err != GL_NO_ERROR || !atlas->tex) {
309 log_warning("cannot create new OpenGL texture: %d", err);
313 newsize = gt->max_tex_size / FONT_WIDTH(txt);
317 /* OpenGL texture sizes are heavily restricted so we need to find a
318 * valid texture size that is big enough to hold as many glyphs as
319 * possible but at least 1 */
321 width = next_pow2(FONT_WIDTH(txt) * newsize);
322 height = next_pow2(FONT_HEIGHT(txt));
326 glBindTexture(GL_TEXTURE_2D, atlas->tex);
327 glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, width, height,
328 0, GL_ALPHA, GL_UNSIGNED_BYTE, NULL);
331 if (err != GL_NO_ERROR) {
337 log_warning("OpenGL textures too small for a single glyph (%d)",
342 log_debug("new atlas of size %ux%u for %zu", width, height, newsize);
344 nsize = txt->cols * txt->rows;
346 atlas->cache_pos = malloc(sizeof(GLfloat) * nsize * 2 * 6);
347 if (!atlas->cache_pos)
350 atlas->cache_texpos = malloc(sizeof(GLfloat) * nsize * 2 * 6);
351 if (!atlas->cache_texpos)
354 atlas->cache_fgcol = malloc(sizeof(GLfloat) * nsize * 3 * 6);
355 if (!atlas->cache_fgcol)
358 atlas->cache_bgcol = malloc(sizeof(GLfloat) * nsize * 3 * 6);
359 if (!atlas->cache_bgcol)
362 atlas->cache_size = nsize;
363 atlas->count = newsize;
364 atlas->width = width;
365 atlas->height = height;
366 atlas->advance_htex = 1.0 / atlas->width * FONT_WIDTH(txt);
367 atlas->advance_vtex = 1.0 / atlas->height * FONT_HEIGHT(txt);
369 shl_dlist_link(>->atlases, &atlas->list);
373 free(atlas->cache_pos);
374 free(atlas->cache_texpos);
375 free(atlas->cache_fgcol);
376 free(atlas->cache_bgcol);
378 gl_tex_free(&atlas->tex, 1);
384 static int find_glyph(struct kmscon_text *txt, struct glyph **out,
385 uint32_t id, const uint32_t *ch, size_t len, bool bold)
387 struct gltex *gt = txt->data;
393 uint8_t *packed_data, *dst, *src;
394 struct shl_hashtable *gtable;
395 struct kmscon_font *font;
398 gtable = gt->bold_glyphs;
399 font = txt->bold_font;
405 res = shl_hashtable_find(gtable, (void**)&glyph,
406 (void*)(unsigned long)id);
412 glyph = malloc(sizeof(*glyph));
415 memset(glyph, 0, sizeof(*glyph));
418 ret = kmscon_font_render_empty(font, &glyph->glyph);
420 ret = kmscon_font_render(font, id, ch, len, &glyph->glyph);
423 ret = kmscon_font_render_inval(font, &glyph->glyph);
428 atlas = get_atlas(txt, glyph->glyph->width);
434 /* Funnily, not all OpenGLESv2 implementations support specifying the
435 * stride of a texture. Therefore, we then need to create a
436 * temporary image with a stride equal to the image width for loading
437 * the texture. This may slow down loading new glyphs but doesn't affect
438 * overall rendering performance. But driver developers should really
443 glBindTexture(GL_TEXTURE_2D, atlas->tex);
444 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
445 if (!gt->supports_rowlen) {
446 if (GLYPH_STRIDE(glyph) == GLYPH_WIDTH(glyph)) {
447 glTexSubImage2D(GL_TEXTURE_2D, 0,
448 FONT_WIDTH(txt) * atlas->fill, 0,
451 GL_ALPHA, GL_UNSIGNED_BYTE,
454 packed_data = malloc(GLYPH_WIDTH(glyph) * GLYPH_HEIGHT(glyph));
456 log_error("cannot allocate memory for glyph storage");
461 src = GLYPH_DATA(glyph);
463 for (i = 0; i < GLYPH_HEIGHT(glyph); ++i) {
464 memcpy(dst, src, GLYPH_WIDTH(glyph));
465 dst += GLYPH_WIDTH(glyph);
466 src += GLYPH_STRIDE(glyph);
469 glTexSubImage2D(GL_TEXTURE_2D, 0,
470 FONT_WIDTH(txt) * atlas->fill, 0,
473 GL_ALPHA, GL_UNSIGNED_BYTE,
478 glPixelStorei(GL_UNPACK_ROW_LENGTH, GLYPH_STRIDE(glyph));
479 glTexSubImage2D(GL_TEXTURE_2D, 0,
480 FONT_WIDTH(txt) * atlas->fill, 0,
483 GL_ALPHA, GL_UNSIGNED_BYTE,
485 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
487 glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
489 /* Check for GL-errors
490 * As OpenGL is a state-machine, we cannot really tell which call failed
491 * without adding a glGetError() after each call. This is totally
492 * overkill so let us at least catch the error afterwards.
493 * We also add a hint to disable OpenGL if this does not work. This
494 * should _always_ work but OpenGL is kind of a black-box that isn't
495 * verbose at all and many things can go wrong. */
498 if (err != GL_NO_ERROR) {
500 log_warning("cannot load glyph data into OpenGL texture (%d: %s); disable the GL-renderer if this does not work reliably",
501 err, gl_err_to_str(err));
506 glyph->atlas = atlas;
507 glyph->texoff = atlas->fill;
509 ret = shl_hashtable_insert(gtable, (void*)(long)id, glyph);
513 atlas->fill += glyph->glyph->width;
523 static int gltex_prepare(struct kmscon_text *txt)
525 struct gltex *gt = txt->data;
527 struct shl_dlist *iter;
530 ret = uterm_display_use(txt->disp, NULL);
534 shl_dlist_for_each(iter, >->atlases) {
535 atlas = shl_dlist_entry(iter, struct atlas, list);
537 atlas->cache_num = 0;
540 gt->advance_x = 2.0 / gt->sw * FONT_WIDTH(txt);
541 gt->advance_y = 2.0 / gt->sh * FONT_HEIGHT(txt);
546 static int gltex_draw(struct kmscon_text *txt,
547 uint32_t id, const uint32_t *ch, size_t len,
549 unsigned int posx, unsigned int posy,
550 const struct tsm_screen_attr *attr)
552 struct gltex *gt = txt->data;
560 ret = find_glyph(txt, &glyph, id, ch, len, attr->bold);
563 atlas = glyph->atlas;
565 if (atlas->cache_num >= atlas->cache_size)
568 atlas->cache_pos[atlas->cache_num * 2 * 6 + 0] =
569 gt->advance_x * posx - 1;
570 atlas->cache_pos[atlas->cache_num * 2 * 6 + 1] =
571 1 - gt->advance_y * posy;
572 atlas->cache_pos[atlas->cache_num * 2 * 6 + 2] =
573 gt->advance_x * posx - 1;
574 atlas->cache_pos[atlas->cache_num * 2 * 6 + 3] =
575 1 - (gt->advance_y * posy + gt->advance_y);
576 atlas->cache_pos[atlas->cache_num * 2 * 6 + 4] =
577 gt->advance_x * posx + width * gt->advance_x - 1;
578 atlas->cache_pos[atlas->cache_num * 2 * 6 + 5] =
579 1 - (gt->advance_y * posy + gt->advance_y);
581 atlas->cache_pos[atlas->cache_num * 2 * 6 + 6] =
582 gt->advance_x * posx - 1;
583 atlas->cache_pos[atlas->cache_num * 2 * 6 + 7] =
584 1 - gt->advance_y * posy;
585 atlas->cache_pos[atlas->cache_num * 2 * 6 + 8] =
586 gt->advance_x * posx + width * gt->advance_x - 1;
587 atlas->cache_pos[atlas->cache_num * 2 * 6 + 9] =
588 1 - (gt->advance_y * posy + gt->advance_y);
589 atlas->cache_pos[atlas->cache_num * 2 * 6 + 10] =
590 gt->advance_x * posx + width * gt->advance_x - 1;
591 atlas->cache_pos[atlas->cache_num * 2 * 6 + 11] =
592 1 - gt->advance_y * posy;
594 atlas->cache_texpos[atlas->cache_num * 2 * 6 + 0] = glyph->texoff;
595 atlas->cache_texpos[atlas->cache_num * 2 * 6 + 1] = 0.0;
596 atlas->cache_texpos[atlas->cache_num * 2 * 6 + 2] = glyph->texoff;
597 atlas->cache_texpos[atlas->cache_num * 2 * 6 + 3] = 1.0;
598 atlas->cache_texpos[atlas->cache_num * 2 * 6 + 4] = glyph->texoff + width;
599 atlas->cache_texpos[atlas->cache_num * 2 * 6 + 5] = 1.0;
601 atlas->cache_texpos[atlas->cache_num * 2 * 6 + 6] = glyph->texoff;
602 atlas->cache_texpos[atlas->cache_num * 2 * 6 + 7] = 0.0;
603 atlas->cache_texpos[atlas->cache_num * 2 * 6 + 8] = glyph->texoff + width;
604 atlas->cache_texpos[atlas->cache_num * 2 * 6 + 9] = 1.0;
605 atlas->cache_texpos[atlas->cache_num * 2 * 6 + 10] = glyph->texoff + width;
606 atlas->cache_texpos[atlas->cache_num * 2 * 6 + 11] = 0.0;
608 for (i = 0; i < 6; ++i) {
609 idx = atlas->cache_num * 3 * 6 + i * 3;
611 atlas->cache_fgcol[idx + 0] = attr->br / 255.0;
612 atlas->cache_fgcol[idx + 1] = attr->bg / 255.0;
613 atlas->cache_fgcol[idx + 2] = attr->bb / 255.0;
614 atlas->cache_bgcol[idx + 0] = attr->fr / 255.0;
615 atlas->cache_bgcol[idx + 1] = attr->fg / 255.0;
616 atlas->cache_bgcol[idx + 2] = attr->fb / 255.0;
618 atlas->cache_fgcol[idx + 0] = attr->fr / 255.0;
619 atlas->cache_fgcol[idx + 1] = attr->fg / 255.0;
620 atlas->cache_fgcol[idx + 2] = attr->fb / 255.0;
621 atlas->cache_bgcol[idx + 0] = attr->br / 255.0;
622 atlas->cache_bgcol[idx + 1] = attr->bg / 255.0;
623 atlas->cache_bgcol[idx + 2] = attr->bb / 255.0;
632 static int gltex_render(struct kmscon_text *txt)
634 struct gltex *gt = txt->data;
636 struct shl_dlist *iter;
641 gl_shader_use(gt->shader);
643 glViewport(0, 0, gt->sw, gt->sh);
647 glUniformMatrix4fv(gt->uni_proj, 1, GL_FALSE, mat);
649 glEnableVertexAttribArray(0);
650 glEnableVertexAttribArray(1);
651 glEnableVertexAttribArray(2);
652 glEnableVertexAttribArray(3);
654 glActiveTexture(GL_TEXTURE0);
655 glUniform1i(gt->uni_atlas, 0);
657 shl_dlist_for_each(iter, >->atlases) {
658 atlas = shl_dlist_entry(iter, struct atlas, list);
659 if (!atlas->cache_num)
662 glBindTexture(GL_TEXTURE_2D, atlas->tex);
663 glUniform1f(gt->uni_advance_htex, atlas->advance_htex);
664 glUniform1f(gt->uni_advance_vtex, atlas->advance_vtex);
666 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, atlas->cache_pos);
667 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, atlas->cache_texpos);
668 glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, atlas->cache_fgcol);
669 glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 0, atlas->cache_bgcol);
670 glDrawArrays(GL_TRIANGLES, 0, 6 * atlas->cache_num);
673 glDisableVertexAttribArray(0);
674 glDisableVertexAttribArray(1);
675 glDisableVertexAttribArray(2);
676 glDisableVertexAttribArray(3);
678 if (gl_has_error(gt->shader)) {
679 log_warning("rendering console caused OpenGL errors");
686 struct kmscon_text_ops kmscon_text_gltex_ops = {
690 .destroy = gltex_destroy,
692 .unset = gltex_unset,
693 .prepare = gltex_prepare,
695 .render = gltex_render,