Core commit. Compile and run on the OpenPandora
[mupen64plus-pandora.git] / source / mupen64plus-core / src / osd / osd.cpp
1 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2  *   Mupen64plus - osd.cpp                                                 *
3  *   Mupen64Plus homepage: http://code.google.com/p/mupen64plus/           *
4  *   Copyright (C) 2008 Nmn Ebenblues                                      *
5  *                                                                         *
6  *   This program is free software; you can redistribute it and/or modify  *
7  *   it under the terms of the GNU General Public License as published by  *
8  *   the Free Software Foundation; either version 2 of the License, or     *
9  *   (at your option) any later version.                                   *
10  *                                                                         *
11  *   This program is distributed in the hope that it will be useful,       *
12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
14  *   GNU General Public License for more details.                          *
15  *                                                                         *
16  *   You should have received a copy of the GNU General Public License     *
17  *   along with this program; if not, write to the                         *
18  *   Free Software Foundation, Inc.,                                       *
19  *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.          *
20  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
21
22 // On-screen Display
23 #ifdef PANDORA
24 #include <SDL_opengles2.h>
25 #else
26 #include <SDL_opengl.h>
27 #endif
28 #include <SDL_thread.h>
29
30 #ifndef PANDORA
31 #include "OGLFT.h"
32 #endif
33 #include "osd.h"
34
35 extern "C" {
36     #define M64P_CORE_PROTOTYPES 1
37     #include "api/m64p_config.h"
38     #include "api/config.h"
39     #include "api/callbacks.h"
40     #include "api/m64p_vidext.h"
41     #include "api/vidext.h"
42     #include "main/main.h"
43     #include "main/list.h"
44     #include "osal/files.h"
45     #include "osal/preproc.h"
46     #include "plugin/plugin.h"
47 }
48
49 #define FONT_FILENAME "font.ttf"
50
51 typedef void (APIENTRYP PTRGLACTIVETEXTURE)(GLenum texture);
52 static PTRGLACTIVETEXTURE pglActiveTexture = NULL;
53
54 // static variables for OSD
55 static int l_OsdInitialized = 0;
56
57 static LIST_HEAD(l_messageQueue);
58 static OGLFT::Monochrome *l_font;
59 static float l_fLineHeight = -1.0;
60
61 static void animation_none(osd_message_t *);
62 static void animation_fade(osd_message_t *);
63 static void osd_remove_message(osd_message_t *msg);
64 static osd_message_t * osd_message_valid(osd_message_t *testmsg);
65
66 static float fCornerScroll[OSD_NUM_CORNERS];
67
68 static SDL_mutex *osd_list_lock;
69
70 // animation handlers
71 static void (*l_animations[OSD_NUM_ANIM_TYPES])(osd_message_t *) = {
72     animation_none, // animation handler for OSD_NONE
73     animation_fade  // animation handler for OSD_FADE
74 };
75
76 // private functions
77 // draw message on screen
78 static void draw_message(osd_message_t *msg, int width, int height)
79 {
80     float x = 0.,
81           y = 0.;
82
83     if(!l_font || !l_font->isValid())
84         return;
85
86     // set color. alpha is hard coded to 1. animation can change this
87     l_font->setForegroundColor(msg->color[R], msg->color[G], msg->color[B], 1.0);
88     l_font->setBackgroundColor(0.0, 0.0, 0.0, 0.0);
89
90     // set justification based on corner
91     switch(msg->corner)
92     {
93         case OSD_TOP_LEFT:
94             l_font->setVerticalJustification(OGLFT::Face::TOP);
95             l_font->setHorizontalJustification(OGLFT::Face::LEFT);
96             x = 0.;
97             y = (float)height;
98             break;
99         case OSD_TOP_CENTER:
100             l_font->setVerticalJustification(OGLFT::Face::TOP);
101             l_font->setHorizontalJustification(OGLFT::Face::CENTER);
102             x = ((float)width)/2.0f;
103             y = (float)height;
104             break;
105         case OSD_TOP_RIGHT:
106             l_font->setVerticalJustification(OGLFT::Face::TOP);
107             l_font->setHorizontalJustification(OGLFT::Face::RIGHT);
108             x = (float)width;
109             y = (float)height;
110             break;
111         case OSD_MIDDLE_LEFT:
112             l_font->setVerticalJustification(OGLFT::Face::MIDDLE);
113             l_font->setHorizontalJustification(OGLFT::Face::LEFT);
114             x = 0.;
115             y = ((float)height)/2.0f;
116             break;
117         case OSD_MIDDLE_CENTER:
118             l_font->setVerticalJustification(OGLFT::Face::MIDDLE);
119             l_font->setHorizontalJustification(OGLFT::Face::CENTER);
120             x = ((float)width)/2.0f;
121             y = ((float)height)/2.0f;
122             break;
123         case OSD_MIDDLE_RIGHT:
124             l_font->setVerticalJustification(OGLFT::Face::MIDDLE);
125             l_font->setHorizontalJustification(OGLFT::Face::RIGHT);
126             x = (float)width;
127             y = ((float)height)/2.0f;
128             break;
129         case OSD_BOTTOM_LEFT:
130             l_font->setVerticalJustification(OGLFT::Face::BOTTOM);
131             l_font->setHorizontalJustification(OGLFT::Face::LEFT);
132             x = 0.;
133             y = 0.;
134             break;
135         case OSD_BOTTOM_CENTER:
136             l_font->setVerticalJustification(OGLFT::Face::BOTTOM);
137             l_font->setHorizontalJustification(OGLFT::Face::CENTER);
138             x = ((float)width)/2.0f;
139             y = 0.;
140             break;
141         case OSD_BOTTOM_RIGHT:
142             l_font->setVerticalJustification(OGLFT::Face::BOTTOM);
143             l_font->setHorizontalJustification(OGLFT::Face::RIGHT);
144             x = (float)width;
145             y = 0.;
146             break;
147         default:
148             l_font->setVerticalJustification(OGLFT::Face::BOTTOM);
149             l_font->setHorizontalJustification(OGLFT::Face::LEFT);
150             x = 0.;
151             y = 0.;
152             break;
153     }
154
155     // apply animation for current message state
156     (*l_animations[msg->animation[msg->state]])(msg);
157
158     // xoffset moves message left
159     x -= msg->xoffset;
160     // yoffset moves message up
161     y += msg->yoffset;
162
163     // get the bounding box if invalid
164     if (msg->sizebox[0] == 0 && msg->sizebox[2] == 0)  // xmin and xmax
165     {
166         OGLFT::BBox bbox = l_font->measure_nominal(msg->text);
167         msg->sizebox[0] = bbox.x_min_;
168         msg->sizebox[1] = bbox.y_min_;
169         msg->sizebox[2] = bbox.x_max_;
170         msg->sizebox[3] = bbox.y_max_;
171     }
172
173     // draw the text line
174     l_font->draw(x, y, msg->text, msg->sizebox);
175 }
176
177 // null animation handler
178 static void animation_none(osd_message_t *msg) { }
179
180 // fade in/out animation handler
181 static void animation_fade(osd_message_t *msg)
182 {
183     float alpha = 1.;
184     float elapsed_frames;
185     float total_frames = (float)msg->timeout[msg->state];
186
187     switch(msg->state)
188     {
189         case OSD_DISAPPEAR:
190             elapsed_frames = (float)(total_frames - msg->frames);
191             break;
192         case OSD_APPEAR:
193         default:
194             elapsed_frames = (float)msg->frames;
195             break;
196     }
197
198     if(total_frames != 0.)
199         alpha = elapsed_frames / total_frames;
200
201     l_font->setForegroundColor(msg->color[R], msg->color[G], msg->color[B], alpha);
202 }
203
204 // sets message Y offset depending on where they are in the message queue
205 static float get_message_offset(osd_message_t *msg, float fLinePos)
206 {
207     float offset = (float) (l_font->height() * fLinePos);
208
209     switch(msg->corner)
210     {
211         case OSD_TOP_LEFT:
212         case OSD_TOP_CENTER:
213         case OSD_TOP_RIGHT:
214             return -offset;
215             break;
216         default:
217             return offset;
218             break;
219     }
220 }
221
222 // public functions
223 extern "C"
224 void osd_init(int width, int height)
225 {
226     const char *fontpath;
227
228     osd_list_lock = SDL_CreateMutex();
229     if (!osd_list_lock) {
230         DebugMessage(M64MSG_ERROR, "Could not create osd list lock");
231         return;
232     }
233
234     if (!OGLFT::Init_FT())
235     {
236         DebugMessage(M64MSG_ERROR, "Could not initialize freetype library.");
237         return;
238     }
239
240     fontpath = ConfigGetSharedDataFilepath(FONT_FILENAME);
241
242     l_font = new OGLFT::Monochrome(fontpath, (float) height / 35.0f);  // make font size proportional to screen height
243
244     if(!l_font || !l_font->isValid())
245     {
246         DebugMessage(M64MSG_ERROR, "Could not construct face from %s", fontpath);
247         return;
248     }
249
250     // clear statics
251     for (int i = 0; i < OSD_NUM_CORNERS; i++)
252         fCornerScroll[i] = 0.0;
253
254     glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
255 #if defined(GL_RASTER_POSITION_UNCLIPPED_IBM)
256     glEnable(GL_RASTER_POSITION_UNCLIPPED_IBM);
257 #endif
258
259     pglActiveTexture = (PTRGLACTIVETEXTURE) VidExt_GL_GetProcAddress("glActiveTexture");
260     if (pglActiveTexture == NULL)
261     {
262         DebugMessage(M64MSG_WARNING, "OpenGL function glActiveTexture() not supported.  OSD deactivated.");
263         return;
264     }
265
266     // set initialized flag
267     l_OsdInitialized = 1;
268 }
269
270 extern "C"
271 void osd_exit(void)
272 {
273     osd_message_t *msg, *safe;
274
275     // delete font renderer
276     if (l_font)
277     {
278         delete l_font;
279         l_font = NULL;
280     }
281
282     // delete message queue
283     SDL_LockMutex(osd_list_lock);
284     list_for_each_entry_safe(msg, safe, &l_messageQueue, osd_message_t, list) {
285         osd_remove_message(msg);
286         if (!msg->user_managed)
287             free(msg);
288     }
289     SDL_UnlockMutex(osd_list_lock);
290
291     // shut down the Freetype library
292     OGLFT::Uninit_FT();
293
294     SDL_DestroyMutex(osd_list_lock);
295
296     // reset initialized flag
297     l_OsdInitialized = 0;
298 }
299
300 // renders the current osd message queue to the screen
301 extern "C"
302 void osd_render()
303 {
304     osd_message_t *msg, *safe;
305     int i;
306
307     // if we're not initialized or list is empty, then just skip it all
308     if (!l_OsdInitialized || list_empty(&l_messageQueue))
309         return;
310
311     // get the viewport dimensions
312     GLint viewport[4];
313     glGetIntegerv(GL_VIEWPORT, viewport);
314
315     // save all the attributes
316     glPushAttrib(GL_ALL_ATTRIB_BITS);
317     bool bFragmentProg = glIsEnabled(GL_FRAGMENT_PROGRAM_ARB) != 0;
318     bool bColorArray = glIsEnabled(GL_COLOR_ARRAY) != 0;
319     bool bTexCoordArray = glIsEnabled(GL_TEXTURE_COORD_ARRAY) != 0;
320     bool bSecColorArray = glIsEnabled(GL_SECONDARY_COLOR_ARRAY) != 0;
321
322     // deactivate all the texturing units
323     GLint  iActiveTex;
324     bool bTexture2D[8];
325     glGetIntegerv(GL_ACTIVE_TEXTURE_ARB, &iActiveTex);
326     for (i = 0; i < 8; i++)
327     {
328         pglActiveTexture(GL_TEXTURE0_ARB + i);
329         bTexture2D[i] = glIsEnabled(GL_TEXTURE_2D) != 0;
330         glDisable(GL_TEXTURE_2D);
331     }
332
333     // save the matrices and set up new ones
334     glMatrixMode(GL_PROJECTION);
335     glPushMatrix();
336     glLoadIdentity();
337     gluOrtho2D(viewport[0],viewport[2],viewport[1],viewport[3]);
338
339     glMatrixMode(GL_MODELVIEW);
340     glPushMatrix();
341     glLoadIdentity();
342
343     // setup for drawing text
344     glDisable(GL_FOG);
345     glDisable(GL_LIGHTING);
346     glDisable(GL_ALPHA_TEST);
347     glDisable(GL_DEPTH_TEST);
348     glDisable(GL_CULL_FACE);
349     glDisable(GL_SCISSOR_TEST);
350     glDisable(GL_STENCIL_TEST);
351     glDisable(GL_FRAGMENT_PROGRAM_ARB);
352     glDisable(GL_COLOR_MATERIAL);
353
354     glEnable(GL_BLEND);
355     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
356
357     glDisableClientState(GL_COLOR_ARRAY);
358     glDisableClientState(GL_TEXTURE_COORD_ARRAY);
359     glDisableClientState(GL_SECONDARY_COLOR_ARRAY);
360     glShadeModel(GL_FLAT);
361
362     // get line height if invalid
363     if (l_fLineHeight < 0.0)
364     {
365         OGLFT::BBox bbox = l_font->measure("01abjZpqRGB");
366         l_fLineHeight = (bbox.y_max_ - bbox.y_min_) / 30.0f;
367     }
368
369     // keeps track of next message position for each corner
370     float fCornerPos[OSD_NUM_CORNERS];
371     for (i = 0; i < OSD_NUM_CORNERS; i++)
372         fCornerPos[i] = 0.5f * l_fLineHeight;
373
374     SDL_LockMutex(osd_list_lock);
375     list_for_each_entry_safe(msg, safe, &l_messageQueue, osd_message_t, list) {
376         // update message state
377         if(msg->timeout[msg->state] != OSD_INFINITE_TIMEOUT &&
378            ++msg->frames >= msg->timeout[msg->state])
379         {
380             // if message is in last state, mark it for deletion and continue to the next message
381             if(msg->state >= OSD_NUM_STATES - 1)
382             {
383                 if (msg->user_managed) {
384                     osd_remove_message(msg);
385                 } else {
386                     osd_remove_message(msg);
387                     free(msg);
388                 }
389
390                 continue;
391             }
392
393             // go to next state and reset frame count
394             msg->state++;
395             msg->frames = 0;
396         }
397
398         // offset y depending on how many other messages are in the same corner
399         float fStartOffset;
400         if (msg->corner >= OSD_MIDDLE_LEFT && msg->corner <= OSD_MIDDLE_RIGHT)  // don't scroll the middle messages
401             fStartOffset = fCornerPos[msg->corner];
402         else
403             fStartOffset = fCornerPos[msg->corner] + (fCornerScroll[msg->corner] * l_fLineHeight);
404         msg->yoffset += get_message_offset(msg, fStartOffset);
405
406         draw_message(msg, viewport[2], viewport[3]);
407
408         msg->yoffset -= get_message_offset(msg, fStartOffset);
409         fCornerPos[msg->corner] += l_fLineHeight;
410     }
411     SDL_UnlockMutex(osd_list_lock);
412
413     // do the scrolling
414     for (int i = 0; i < OSD_NUM_CORNERS; i++)
415     {
416         fCornerScroll[i] += 0.1f;
417         if (fCornerScroll[i] >= 0.0)
418             fCornerScroll[i] = 0.0;
419     }
420
421     // restore the matrices
422     glMatrixMode(GL_MODELVIEW);
423     glPopMatrix();
424     glMatrixMode(GL_PROJECTION);
425     glPopMatrix();
426
427     // restore the attributes
428     for (int i = 0; i < 8; i++)
429     {
430         pglActiveTexture(GL_TEXTURE0_ARB + i);
431         if (bTexture2D[i])
432             glEnable(GL_TEXTURE_2D);
433         else
434             glDisable(GL_TEXTURE_2D);
435     }
436     pglActiveTexture(iActiveTex);
437     glPopAttrib();
438     if (bFragmentProg)
439         glEnable(GL_FRAGMENT_PROGRAM_ARB);
440     if (bColorArray)
441         glEnableClientState(GL_COLOR_ARRAY);
442     if (bTexCoordArray)
443         glEnableClientState(GL_TEXTURE_COORD_ARRAY);
444     if (bSecColorArray)
445         glEnableClientState(GL_SECONDARY_COLOR_ARRAY);
446
447     glFinish();
448 }
449
450 // creates a new osd_message_t, adds it to the message queue and returns it in case
451 // the user wants to modify its parameters. Note, if the message can't be created,
452 // NULL is returned.
453 extern "C"
454 osd_message_t * osd_new_message(enum osd_corner eCorner, const char *fmt, ...)
455 {
456     va_list ap;
457     char buf[1024];
458
459     if (!l_OsdInitialized) return NULL;
460
461     osd_message_t *msg = (osd_message_t *)malloc(sizeof(*msg));
462
463     if (!msg) return NULL;
464
465     va_start(ap, fmt);
466     vsnprintf(buf, 1024, fmt, ap);
467     buf[1023] = 0;
468     va_end(ap);
469
470     // set default values
471     memset(msg, 0, sizeof(osd_message_t));
472     msg->text = strdup(buf);
473     msg->user_managed = 0;
474     // default to white
475     msg->color[R] = 1.;
476     msg->color[G] = 1.;
477     msg->color[B] = 1.;
478
479     msg->sizebox[0] = 0.0;  // set a null bounding box
480     msg->sizebox[1] = 0.0;
481     msg->sizebox[2] = 0.0;
482     msg->sizebox[3] = 0.0;
483
484     msg->corner = eCorner;
485     msg->state = OSD_APPEAR;
486     fCornerScroll[eCorner] -= 1.0;  // start this one before the beginning of the list and scroll it in
487
488     msg->animation[OSD_APPEAR] = OSD_FADE;
489     msg->animation[OSD_DISPLAY] = OSD_NONE;
490     msg->animation[OSD_DISAPPEAR] = OSD_FADE;
491
492     if (eCorner >= OSD_MIDDLE_LEFT && eCorner <= OSD_MIDDLE_RIGHT)
493     {
494         msg->timeout[OSD_APPEAR] = 20;
495         msg->timeout[OSD_DISPLAY] = 60;
496         msg->timeout[OSD_DISAPPEAR] = 20;
497     }
498     else
499     {
500         msg->timeout[OSD_APPEAR] = 20;
501         msg->timeout[OSD_DISPLAY] = 180;
502         msg->timeout[OSD_DISAPPEAR] = 40;
503     }
504
505     // add to message queue
506     SDL_LockMutex(osd_list_lock);
507     list_add(&msg->list, &l_messageQueue);
508     SDL_UnlockMutex(osd_list_lock);
509
510     return msg;
511 }
512
513 // update message string
514 extern "C"
515 void osd_update_message(osd_message_t *msg, const char *fmt, ...)
516 {
517     va_list ap;
518     char buf[1024];
519
520     if (!l_OsdInitialized || !msg) return;
521
522     va_start(ap, fmt);
523     vsnprintf(buf, 1024, fmt, ap);
524     buf[1023] = 0;
525     va_end(ap);
526
527     free(msg->text);
528     msg->text = strdup(buf);
529
530     // reset bounding box
531     msg->sizebox[0] = 0.0;
532     msg->sizebox[1] = 0.0;
533     msg->sizebox[2] = 0.0;
534     msg->sizebox[3] = 0.0;
535
536     // reset display time counter
537     if (msg->state >= OSD_DISPLAY)
538     {
539         msg->state = OSD_DISPLAY;
540         msg->frames = 0;
541     }
542
543     SDL_LockMutex(osd_list_lock);
544     if (!osd_message_valid(msg))
545         list_add(&msg->list, &l_messageQueue);
546     SDL_UnlockMutex(osd_list_lock);
547
548 }
549
550 // remove message from message queue
551 static void osd_remove_message(osd_message_t *msg)
552 {
553     if (!l_OsdInitialized || !msg) return;
554
555     free(msg->text);
556     msg->text = NULL;
557     list_del(&msg->list);
558 }
559
560 // remove message from message queue and free it
561 extern "C"
562 void osd_delete_message(osd_message_t *msg)
563 {
564     if (!l_OsdInitialized || !msg) return;
565
566     SDL_LockMutex(osd_list_lock);
567     osd_remove_message(msg);
568     free(msg);
569     SDL_UnlockMutex(osd_list_lock);
570 }
571
572 // set message so it doesn't automatically expire in a certain number of frames.
573 extern "C"
574 void osd_message_set_static(osd_message_t *msg)
575 {
576     if (!l_OsdInitialized || !msg) return;
577
578     msg->timeout[OSD_DISPLAY] = OSD_INFINITE_TIMEOUT;
579     msg->state = OSD_DISPLAY;
580     msg->frames = 0;
581 }
582
583 // set message so it doesn't automatically get freed when finished transition.
584 extern "C"
585 void osd_message_set_user_managed(osd_message_t *msg)
586 {
587     if (!l_OsdInitialized || !msg) return;
588
589     msg->user_managed = 1;
590 }
591
592 // return message pointer if valid (in the OSD list), otherwise return NULL
593 static osd_message_t * osd_message_valid(osd_message_t *testmsg)
594 {
595     osd_message_t *msg;
596
597     if (!l_OsdInitialized || !testmsg) return NULL;
598
599     list_for_each_entry(msg, &l_messageQueue, osd_message_t, list) {
600         if (msg == testmsg)
601             return testmsg;
602     }
603
604     return NULL;
605 }
606