source: libcaca/trunk/caca/driver_gl.c @ 2141

Last change on this file since 2141 was 2141, checked in by Sam Hocevar, 12 years ago
  • Only call glutInit() once through the program's lifetime.
  • Free the internal font when closing the GL driver.
  • Hide the window when closing the GL driver, because freeglut's glutDestroyWindow() does not really closes the window.
  • Property svn:keywords set to Id
File size: 17.5 KB
Line 
1/*
2 *  libcaca       Colour ASCII-Art library
3 *  Copyright (c) 2002-2006 Sam Hocevar <sam@zoy.org>
4 *                2006 Jean-Yves Lamoureux <jylam@lnxscene.org>
5 *                2007 Ben Wiley Sittler <bsittler@gmail.com>
6 *                All Rights Reserved
7 *
8 *  $Id: driver_gl.c 2141 2007-12-16 11:54:43Z sam $
9 *
10 *  This library is free software. It comes without any warranty, to
11 *  the extent permitted by applicable law. You can redistribute it
12 *  and/or modify it under the terms of the Do What The Fuck You Want
13 *  To Public License, Version 2, as published by Sam Hocevar. See
14 *  http://sam.zoy.org/wtfpl/COPYING for more details.
15 */
16
17/*
18 *  This file contains the libcaca OpenGL input and output driver
19 */
20
21#include "config.h"
22#include "common.h"
23
24#if defined(USE_GL)
25
26#ifdef HAVE_OPENGL_GL_H
27#   include <OpenGL/gl.h>
28#   include <GLUT/glut.h>
29#else
30#   include <GL/gl.h>
31#   include <GL/glut.h>
32#   include <GL/freeglut_ext.h>
33#endif
34
35#include <string.h>
36#include <stdlib.h>
37#include <stdio.h>
38
39#include "cucul.h"
40#include "caca.h"
41#include "caca_internals.h"
42
43/*
44 * Global variables
45 */
46
47static int glut_init;
48static caca_display_t *gl_d; /* FIXME: we ought to get rid of this */
49
50/*
51 * Local functions
52 */
53static void gl_handle_keyboard(unsigned char, int, int);
54static void gl_handle_special_key(int, int, int);
55static void gl_handle_reshape(int, int);
56static void gl_handle_mouse(int, int, int, int);
57static void gl_handle_mouse_motion(int, int);
58#ifdef HAVE_GLUTCLOSEFUNC
59static void gl_handle_close(void);
60#endif
61static void _display(void);
62static void gl_compute_font(caca_display_t *);
63
64struct driver_private
65{
66    int window;
67    unsigned int width, height;
68    unsigned int new_width, new_height;
69    cucul_font_t *f;
70    float font_width, font_height;
71    float incx, incy;
72    unsigned long int const *blocks;
73    int *txid;
74    unsigned char close;
75    unsigned char bit;
76    unsigned char mouse_changed, mouse_clicked;
77    unsigned int mouse_x, mouse_y;
78    unsigned int mouse_button, mouse_state;
79
80    unsigned char key;
81    int special_key;
82
83    float sw, sh;
84};
85
86static int gl_init_graphics(caca_display_t *dp)
87{
88    char const *geometry;
89    char *argv[2] = { "", NULL };
90    char const * const * fonts;
91    unsigned int width = cucul_get_canvas_width(dp->cv);
92    unsigned int height = cucul_get_canvas_height(dp->cv);
93    int argc = 1;
94
95    dp->drv.p = malloc(sizeof(struct driver_private));
96
97    gl_d = dp;
98
99#if defined(HAVE_GETENV)
100    geometry = getenv("CACA_GEOMETRY");
101    if(geometry && *geometry)
102        sscanf(geometry, "%ux%u", &width, &height);
103#endif
104
105    dp->resize.allow = 1;
106    cucul_set_canvas_size(dp->cv, width ? width : 80, height ? height : 32);
107    dp->resize.allow = 0;
108
109    /* Load a libcucul internal font */
110    fonts = cucul_get_font_list();
111    if(fonts[0] == NULL)
112    {
113        fprintf(stderr, "error: libcucul was compiled without any fonts\n");
114        return -1;
115    }
116    dp->drv.p->f = cucul_load_font(fonts[0], 0);
117    if(dp->drv.p->f == NULL)
118    {
119        fprintf(stderr, "error: could not load font \"%s\"\n", fonts[0]);
120        return -1;
121    }
122
123    dp->drv.p->font_width = cucul_get_font_width(dp->drv.p->f);
124    dp->drv.p->font_height = cucul_get_font_height(dp->drv.p->f);
125
126    dp->drv.p->width = cucul_get_canvas_width(dp->cv) * dp->drv.p->font_width;
127    dp->drv.p->height = cucul_get_canvas_height(dp->cv) * dp->drv.p->font_height;
128
129#ifdef HAVE_GLUTCLOSEFUNC
130    dp->drv.p->close = 0;
131#endif
132    dp->drv.p->bit = 0;
133
134    dp->drv.p->mouse_changed = dp->drv.p->mouse_clicked = 0;
135    dp->drv.p->mouse_button = dp->drv.p->mouse_state = 0;
136
137    dp->drv.p->key = 0;
138    dp->drv.p->special_key = 0;
139
140    dp->drv.p->sw = ((float)dp->drv.p->font_width) / 16.0f;
141    dp->drv.p->sh = ((float)dp->drv.p->font_height) / 16.0f;
142
143    if(!glut_init)
144    {
145        glut_init = 1;
146        glutInit(&argc, argv);
147    }
148
149    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
150    glutInitWindowSize(dp->drv.p->width, dp->drv.p->height);
151    dp->drv.p->window = glutCreateWindow("caca for GL");
152
153    gluOrtho2D(0, dp->drv.p->width, dp->drv.p->height, 0);
154
155    glDisable(GL_CULL_FACE);
156    glDisable(GL_DEPTH_TEST);
157
158    glutKeyboardFunc(gl_handle_keyboard);
159    glutSpecialFunc(gl_handle_special_key);
160    glutReshapeFunc(gl_handle_reshape);
161    glutDisplayFunc(_display);
162
163#ifdef HAVE_GLUTCLOSEFUNC
164    glutCloseFunc(gl_handle_close);
165#endif
166
167    glutMouseFunc(gl_handle_mouse);
168    glutMotionFunc(gl_handle_mouse_motion);
169    glutPassiveMotionFunc(gl_handle_mouse_motion);
170
171    glLoadIdentity();
172
173    glMatrixMode(GL_PROJECTION);
174    glPushMatrix();
175    glLoadIdentity();
176    gluOrtho2D(0, dp->drv.p->width, dp->drv.p->height, 0);
177
178    glMatrixMode(GL_MODELVIEW);
179
180    glClear(GL_COLOR_BUFFER_BIT);
181    glEnable(GL_TEXTURE_2D);
182    glEnable(GL_BLEND);
183    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
184
185    glEnable(GL_TEXTURE_2D);
186
187    gl_compute_font(dp);
188
189    return 0;
190}
191
192static int gl_end_graphics(caca_display_t *dp)
193{
194    glutHideWindow();
195    glutDestroyWindow(dp->drv.p->window);
196    cucul_free_font(dp->drv.p->f);
197    free(dp->drv.p->txid);
198    free(dp->drv.p);
199    return 0;
200}
201
202static int gl_set_display_title(caca_display_t *dp, char const *title)
203{
204    glutSetWindowTitle(title);
205    return 0;
206}
207
208static unsigned int gl_get_display_width(caca_display_t const *dp)
209{
210    return dp->drv.p->width;
211}
212
213static unsigned int gl_get_display_height(caca_display_t const *dp)
214{
215    return dp->drv.p->height;
216}
217
218static void gl_display(caca_display_t *dp)
219{
220    uint32_t const *cvchars = (uint32_t const *)cucul_get_canvas_chars(dp->cv);
221    uint32_t const *cvattrs = (uint32_t const *)cucul_get_canvas_attrs(dp->cv);
222    unsigned int width = cucul_get_canvas_width(dp->cv);
223    unsigned int x, y, line;
224
225    glClear(GL_COLOR_BUFFER_BIT);
226    glDisable(GL_TEXTURE_2D);
227    glDisable(GL_BLEND);
228    line = 0;
229    for(y = 0; y < dp->drv.p->height; y += dp->drv.p->font_height)
230    {
231        uint32_t const *attrs = cvattrs + line * width;
232
233        /* FIXME: optimise using stride */
234        for(x = 0; x < dp->drv.p->width; x += dp->drv.p->font_width)
235        {
236            uint16_t bg = cucul_attr_to_rgb12_bg(*attrs++);
237
238            glColor4b(((bg & 0xf00) >> 8) * 8,
239                      ((bg & 0x0f0) >> 4) * 8,
240                      (bg & 0x00f) * 8,
241                      0xff);
242            glBegin(GL_QUADS);
243            glVertex2f(x, y);
244            glVertex2f(x + dp->drv.p->font_width, y);
245            glVertex2f(x + dp->drv.p->font_width,
246                       y + dp->drv.p->font_height);
247            glVertex2f(x, y + dp->drv.p->font_height);
248            glEnd();
249        }
250
251        line++;
252    }
253
254    /* 2nd pass, avoids changing render state too much */
255    glEnable(GL_TEXTURE_2D);
256    glEnable(GL_BLEND);
257
258    line = 0;
259    for(y = 0; y < dp->drv.p->height; y += dp->drv.p->font_height, line++)
260    {
261        uint32_t const *attrs = cvattrs + line * width;
262        uint32_t const *chars = cvchars + line * width;
263
264        for(x = 0; x < dp->drv.p->width; x += dp->drv.p->font_width, attrs++)
265        {
266            uint32_t ch = *chars++;
267            uint16_t fg;
268            int i, b, fullwidth;
269
270            fullwidth = cucul_utf32_is_fullwidth(ch);
271
272            for(b = 0, i = 0; dp->drv.p->blocks[i + 1]; i += 2)
273            {
274                if(ch < (uint32_t)dp->drv.p->blocks[i])
275                     break;
276
277                if(ch >= (uint32_t)dp->drv.p->blocks[i + 1])
278                {
279                    b += (uint32_t)(dp->drv.p->blocks[i + 1]
280                                     - dp->drv.p->blocks[i]);
281                    continue;
282                }
283
284                glBindTexture(GL_TEXTURE_2D,
285                              dp->drv.p->txid[b + ch
286                                        - (uint32_t)dp->drv.p->blocks[i]]);
287
288                fg = cucul_attr_to_rgb12_fg(*attrs);
289                glColor3b(((fg & 0xf00) >> 8) * 8,
290                          ((fg & 0x0f0) >> 4) * 8,
291                          (fg & 0x00f) * 8);
292                /* FIXME: handle fullwidth glyphs here */
293                glBegin(GL_QUADS);
294                glTexCoord2f(0, dp->drv.p->sh);
295                glVertex2f(x, y);
296                glTexCoord2f(dp->drv.p->sw, dp->drv.p->sh);
297                glVertex2f(x + dp->drv.p->font_width * (fullwidth ? 2 : 1), y);
298                glTexCoord2f(dp->drv.p->sw, 0);
299                glVertex2f(x + dp->drv.p->font_width * (fullwidth ? 2 : 1),
300                           y + dp->drv.p->font_height);
301                glTexCoord2f(0, 0);
302                glVertex2f(x, y + dp->drv.p->font_height);
303                glEnd();
304            }
305
306            if(fullwidth)
307            {
308                chars++; attrs++; x += dp->drv.p->font_width;
309            }
310        }
311    }
312
313#ifdef HAVE_GLUTCHECKLOOP
314    glutCheckLoop();
315#else
316    glutMainLoopEvent();
317#endif
318    glutSwapBuffers();
319    glutPostRedisplay();
320}
321
322static void gl_handle_resize(caca_display_t *dp)
323{
324    dp->drv.p->width = dp->drv.p->new_width;
325    dp->drv.p->height = dp->drv.p->new_height;
326
327    glMatrixMode(GL_PROJECTION);
328    glPushMatrix();
329    glLoadIdentity();
330
331    glViewport(0, 0, dp->drv.p->width, dp->drv.p->height);
332    gluOrtho2D(0, dp->drv.p->width, dp->drv.p->height, 0);
333    glMatrixMode(GL_MODELVIEW);
334}
335
336static int gl_get_event(caca_display_t *dp, caca_privevent_t *ev)
337{
338#ifdef HAVE_GLUTCHECKLOOP
339    glutCheckLoop();
340#else
341    glutMainLoopEvent();
342#endif
343
344#ifdef HAVE_GLUTCLOSEFUNC
345    if(dp->drv.p->close)
346    {
347        dp->drv.p->close = 0;
348        ev->type = CACA_EVENT_QUIT;
349        return 1;
350    }
351#endif
352
353    if(dp->resize.resized)
354    {
355        ev->type = CACA_EVENT_RESIZE;
356        ev->data.resize.w = cucul_get_canvas_width(dp->cv);
357        ev->data.resize.h = cucul_get_canvas_height(dp->cv);
358        return 1;
359    }
360
361    if(dp->drv.p->mouse_changed)
362    {
363        ev->type = CACA_EVENT_MOUSE_MOTION;
364        ev->data.mouse.x = dp->mouse.x;
365        ev->data.mouse.y = dp->mouse.y;
366        dp->drv.p->mouse_changed = 0;
367
368        if(dp->drv.p->mouse_clicked)
369        {
370            _push_event(dp, ev);
371            ev->type = CACA_EVENT_MOUSE_PRESS;
372            ev->data.mouse.button = dp->drv.p->mouse_button;
373            dp->drv.p->mouse_clicked = 0;
374        }
375
376        return 1;
377    }
378
379    if(dp->drv.p->key != 0)
380    {
381        ev->type = CACA_EVENT_KEY_PRESS;
382        ev->data.key.ch = dp->drv.p->key;
383        ev->data.key.utf32 = (uint32_t)dp->drv.p->key;
384        ev->data.key.utf8[0] = dp->drv.p->key;
385        ev->data.key.utf8[1] = '\0';
386        dp->drv.p->key = 0;
387        return 1;
388    }
389
390    if(dp->drv.p->special_key != 0)
391    {
392        switch(dp->drv.p->special_key)
393        {
394            case GLUT_KEY_F1 : ev->data.key.ch = CACA_KEY_F1; break;
395            case GLUT_KEY_F2 : ev->data.key.ch = CACA_KEY_F2; break;
396            case GLUT_KEY_F3 : ev->data.key.ch = CACA_KEY_F3; break;
397            case GLUT_KEY_F4 : ev->data.key.ch = CACA_KEY_F4; break;
398            case GLUT_KEY_F5 : ev->data.key.ch = CACA_KEY_F5; break;
399            case GLUT_KEY_F6 : ev->data.key.ch = CACA_KEY_F6; break;
400            case GLUT_KEY_F7 : ev->data.key.ch = CACA_KEY_F7; break;
401            case GLUT_KEY_F8 : ev->data.key.ch = CACA_KEY_F8; break;
402            case GLUT_KEY_F9 : ev->data.key.ch = CACA_KEY_F9; break;
403            case GLUT_KEY_F10: ev->data.key.ch = CACA_KEY_F10; break;
404            case GLUT_KEY_F11: ev->data.key.ch = CACA_KEY_F11; break;
405            case GLUT_KEY_F12: ev->data.key.ch = CACA_KEY_F12; break;
406            case GLUT_KEY_LEFT : ev->data.key.ch = CACA_KEY_LEFT; break;
407            case GLUT_KEY_RIGHT: ev->data.key.ch = CACA_KEY_RIGHT; break;
408            case GLUT_KEY_UP   : ev->data.key.ch = CACA_KEY_UP; break;
409            case GLUT_KEY_DOWN : ev->data.key.ch = CACA_KEY_DOWN; break;
410            case GLUT_KEY_PAGE_UP : ev->data.key.ch = CACA_KEY_PAGEUP; break;
411            case GLUT_KEY_PAGE_DOWN  : ev->data.key.ch = CACA_KEY_PAGEDOWN;
412                break;
413            case GLUT_KEY_HOME : ev->data.key.ch = CACA_KEY_HOME; break;
414            case GLUT_KEY_END : ev->data.key.ch = CACA_KEY_END; break;
415            case GLUT_KEY_INSERT : ev->data.key.ch = CACA_KEY_INSERT; break;
416
417            default: ev->type = CACA_EVENT_NONE; return 0;
418        }
419
420        ev->type = CACA_EVENT_KEY_PRESS;
421        ev->data.key.utf32 = 0;
422        ev->data.key.utf8[0] = '\0';
423
424        dp->drv.p->special_key = 0;
425        return 1;
426    }
427
428    ev->type = CACA_EVENT_NONE;
429    return 0;
430}
431
432
433static void gl_set_mouse(caca_display_t *dp, int flag)
434{
435    if(flag)
436        glutSetCursor(GLUT_CURSOR_RIGHT_ARROW);
437    else
438        glutSetCursor(GLUT_CURSOR_NONE);
439}
440
441/*
442 * XXX: following functions are local
443 */
444
445static void gl_handle_keyboard(unsigned char key, int x, int y)
446{
447    caca_display_t *dp = gl_d;
448
449    dp->drv.p->key = key;
450}
451
452static void gl_handle_special_key(int key, int x, int y)
453{
454    caca_display_t *dp = gl_d;
455
456    dp->drv.p->special_key = key;
457}
458
459static void gl_handle_reshape(int w, int h)
460{
461    caca_display_t *dp = gl_d;
462
463    if(dp->drv.p->bit) /* Do not handle reshaping at the first time */
464    {
465        dp->drv.p->new_width = w;
466        dp->drv.p->new_height = h;
467
468        dp->resize.w = w / dp->drv.p->font_width;
469        dp->resize.h = (h / dp->drv.p->font_height) + 1;
470
471        dp->resize.resized = 1;
472    }
473    else
474        dp->drv.p->bit = 1;
475}
476
477static void gl_handle_mouse(int button, int state, int x, int y)
478{
479    caca_display_t *dp = gl_d;
480
481    dp->drv.p->mouse_clicked = 1;
482    dp->drv.p->mouse_button = button;
483    dp->drv.p->mouse_state = state;
484    dp->drv.p->mouse_x = x / dp->drv.p->font_width;
485    dp->drv.p->mouse_y = y / dp->drv.p->font_height;
486    dp->mouse.x = dp->drv.p->mouse_x;
487    dp->mouse.y = dp->drv.p->mouse_y;
488    dp->drv.p->mouse_changed = 1;
489}
490
491static void gl_handle_mouse_motion(int x, int y)
492{
493    caca_display_t *dp = gl_d;
494    dp->drv.p->mouse_x = x / dp->drv.p->font_width;
495    dp->drv.p->mouse_y = y / dp->drv.p->font_height;
496    dp->mouse.x = dp->drv.p->mouse_x;
497    dp->mouse.y = dp->drv.p->mouse_y;
498    dp->drv.p->mouse_changed = 1;
499}
500
501#ifdef HAVE_GLUTCLOSEFUNC
502static void gl_handle_close(void)
503{
504    caca_display_t *dp = gl_d;
505    dp->drv.p->close = 1;
506}
507#endif
508
509static void _display(void)
510{
511    caca_display_t *dp = gl_d;
512    gl_display(dp);
513}
514
515static void gl_compute_font(caca_display_t *dp)
516{
517    cucul_canvas_t *cv;
518    uint32_t *image;
519    int i, b, w, h, x, y;
520
521    /* Count how many glyphs this font has */
522    dp->drv.p->blocks = cucul_get_font_blocks(dp->drv.p->f);
523
524    for(b = 0, i = 0; dp->drv.p->blocks[i + 1]; i += 2)
525        b += (int)(dp->drv.p->blocks[i + 1] - dp->drv.p->blocks[i]);
526
527    /* Allocate a libcucul canvas and print all the glyphs on it */
528    cv = cucul_create_canvas(2, b);
529    cucul_set_color_ansi(cv, CUCUL_WHITE, CUCUL_BLACK);
530
531    for(b = 0, i = 0; dp->drv.p->blocks[i + 1]; i += 2)
532    {
533        int j, n = (int)(dp->drv.p->blocks[i + 1] - dp->drv.p->blocks[i]);
534
535        for(j = 0; j < n; j++)
536            cucul_put_char(cv, 0, b + j, dp->drv.p->blocks[i] + j);
537
538        b += n;
539    }
540
541    /* Draw the cucul canvas onto an image buffer */
542    image = malloc(b * dp->drv.p->font_height *
543                   2 * dp->drv.p->font_width * sizeof(uint32_t));
544    cucul_render_canvas(cv, dp->drv.p->f, image, 2 * dp->drv.p->font_width,
545                        b * dp->drv.p->font_height, 8 * dp->drv.p->font_width);
546    cucul_free_canvas(cv);
547
548    /* Convert all glyphs in the image buffer to GL textures */
549    dp->drv.p->txid = malloc(b * sizeof(int));
550
551    w = dp->drv.p->font_width <= 16 ? dp->drv.p->font_width : 16;
552    h = dp->drv.p->font_height <= 16 ? dp->drv.p->font_height : 16;
553
554    for(b = 0, i = 0; dp->drv.p->blocks[i + 1]; i += 2)
555    {
556        int j, n = (int)(dp->drv.p->blocks[i + 1] - dp->drv.p->blocks[i]);
557
558        for(j = 0; j < n; j++)
559        {
560            uint8_t tmp[16 * 8 * 16];
561            uint32_t *glyph = image + (int)((b + j) * dp->drv.p->font_width * 2
562                                            * dp->drv.p->font_height);
563            int fullwidth =
564                    cucul_utf32_is_fullwidth(dp->drv.p->blocks[i] + j);
565
566            memset(tmp, 0, 16 * 8 * 16);
567           
568            for(y = 0; y < h; y++)
569            {
570                for(x = 0; x < w * (fullwidth ? 2 : 1); x++)
571                {
572                    int offset = x + (15 - y) * (fullwidth ? 32 : 16);
573                    uint8_t c = glyph[x + y * 2 * (int)dp->drv.p->font_width]
574                                 >> 8;
575                    tmp[offset * 4] = c;
576                    tmp[offset * 4 + 1] = c;
577                    tmp[offset * 4 + 2] = c;
578                    tmp[offset * 4 + 3] = c;
579                }
580            }
581           
582            glGenTextures(1, (GLuint*)&dp->drv.p->txid[b + j]);
583            glBindTexture(GL_TEXTURE_2D, dp->drv.p->txid[b + j]);
584            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
585            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
586            glTexImage2D(GL_TEXTURE_2D, 0, 4, (fullwidth ? 32 : 16), 16, 0,
587                         GL_RGBA, GL_UNSIGNED_BYTE, tmp);
588        }
589
590        b += n;
591    }
592
593    free(image);
594}
595
596/*
597 * Driver initialisation
598 */
599
600int gl_install(caca_display_t *dp)
601{
602#if defined(HAVE_GETENV) && defined(GLUT_XLIB_IMPLEMENTATION)
603    if(!getenv("DISPLAY") || !*(getenv("DISPLAY")))
604        return -1;
605#endif
606
607    dp->drv.id = CACA_DRIVER_GL;
608    dp->drv.driver = "gl";
609
610    dp->drv.init_graphics = gl_init_graphics;
611    dp->drv.end_graphics = gl_end_graphics;
612    dp->drv.set_display_title = gl_set_display_title;
613    dp->drv.get_display_width = gl_get_display_width;
614    dp->drv.get_display_height = gl_get_display_height;
615    dp->drv.display = gl_display;
616    dp->drv.handle_resize = gl_handle_resize;
617    dp->drv.get_event = gl_get_event;
618    dp->drv.set_mouse = gl_set_mouse;
619    dp->drv.set_cursor = NULL;
620
621    return 0;
622}
623
624#endif /* USE_GL */
625
Note: See TracBrowser for help on using the repository browser.