Core commit. Compile and run on the OpenPandora
[mupen64plus-pandora.git] / source / mupen64plus-core / src / osd / osd.cpp
CommitLineData
451ab91e 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
35extern "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
51typedef void (APIENTRYP PTRGLACTIVETEXTURE)(GLenum texture);
52static PTRGLACTIVETEXTURE pglActiveTexture = NULL;
53
54// static variables for OSD
55static int l_OsdInitialized = 0;
56
57static LIST_HEAD(l_messageQueue);
58static OGLFT::Monochrome *l_font;
59static float l_fLineHeight = -1.0;
60
61static void animation_none(osd_message_t *);
62static void animation_fade(osd_message_t *);
63static void osd_remove_message(osd_message_t *msg);
64static osd_message_t * osd_message_valid(osd_message_t *testmsg);
65
66static float fCornerScroll[OSD_NUM_CORNERS];
67
68static SDL_mutex *osd_list_lock;
69
70// animation handlers
71static 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
78static 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
178static void animation_none(osd_message_t *msg) { }
179
180// fade in/out animation handler
181static 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
205static 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
223extern "C"
224void 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
270extern "C"
271void 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
301extern "C"
302void 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.
453extern "C"
454osd_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
514extern "C"
515void 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
551static 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
561extern "C"
562void 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.
573extern "C"
574void 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.
584extern "C"
585void 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
593static 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