1 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2 * Mupen64plus - osd.cpp *
3 * Mupen64Plus homepage: http://code.google.com/p/mupen64plus/ *
4 * Copyright (C) 2008 Nmn Ebenblues *
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. *
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. *
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 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
24 #include <SDL_opengles2.h>
26 #include <SDL_opengl.h>
28 #include <SDL_thread.h>
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"
49 #define FONT_FILENAME "font.ttf"
51 typedef void (APIENTRYP PTRGLACTIVETEXTURE)(GLenum texture);
52 static PTRGLACTIVETEXTURE pglActiveTexture = NULL;
54 // static variables for OSD
55 static int l_OsdInitialized = 0;
57 static LIST_HEAD(l_messageQueue);
58 static OGLFT::Monochrome *l_font;
59 static float l_fLineHeight = -1.0;
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);
66 static float fCornerScroll[OSD_NUM_CORNERS];
68 static SDL_mutex *osd_list_lock;
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
77 // draw message on screen
78 static void draw_message(osd_message_t *msg, int width, int height)
83 if(!l_font || !l_font->isValid())
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);
90 // set justification based on corner
94 l_font->setVerticalJustification(OGLFT::Face::TOP);
95 l_font->setHorizontalJustification(OGLFT::Face::LEFT);
100 l_font->setVerticalJustification(OGLFT::Face::TOP);
101 l_font->setHorizontalJustification(OGLFT::Face::CENTER);
102 x = ((float)width)/2.0f;
106 l_font->setVerticalJustification(OGLFT::Face::TOP);
107 l_font->setHorizontalJustification(OGLFT::Face::RIGHT);
111 case OSD_MIDDLE_LEFT:
112 l_font->setVerticalJustification(OGLFT::Face::MIDDLE);
113 l_font->setHorizontalJustification(OGLFT::Face::LEFT);
115 y = ((float)height)/2.0f;
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;
123 case OSD_MIDDLE_RIGHT:
124 l_font->setVerticalJustification(OGLFT::Face::MIDDLE);
125 l_font->setHorizontalJustification(OGLFT::Face::RIGHT);
127 y = ((float)height)/2.0f;
129 case OSD_BOTTOM_LEFT:
130 l_font->setVerticalJustification(OGLFT::Face::BOTTOM);
131 l_font->setHorizontalJustification(OGLFT::Face::LEFT);
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;
141 case OSD_BOTTOM_RIGHT:
142 l_font->setVerticalJustification(OGLFT::Face::BOTTOM);
143 l_font->setHorizontalJustification(OGLFT::Face::RIGHT);
148 l_font->setVerticalJustification(OGLFT::Face::BOTTOM);
149 l_font->setHorizontalJustification(OGLFT::Face::LEFT);
155 // apply animation for current message state
156 (*l_animations[msg->animation[msg->state]])(msg);
158 // xoffset moves message left
160 // yoffset moves message up
163 // get the bounding box if invalid
164 if (msg->sizebox[0] == 0 && msg->sizebox[2] == 0) // xmin and xmax
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_;
173 // draw the text line
174 l_font->draw(x, y, msg->text, msg->sizebox);
177 // null animation handler
178 static void animation_none(osd_message_t *msg) { }
180 // fade in/out animation handler
181 static void animation_fade(osd_message_t *msg)
184 float elapsed_frames;
185 float total_frames = (float)msg->timeout[msg->state];
190 elapsed_frames = (float)(total_frames - msg->frames);
194 elapsed_frames = (float)msg->frames;
198 if(total_frames != 0.)
199 alpha = elapsed_frames / total_frames;
201 l_font->setForegroundColor(msg->color[R], msg->color[G], msg->color[B], alpha);
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)
207 float offset = (float) (l_font->height() * fLinePos);
224 void osd_init(int width, int height)
226 const char *fontpath;
228 osd_list_lock = SDL_CreateMutex();
229 if (!osd_list_lock) {
230 DebugMessage(M64MSG_ERROR, "Could not create osd list lock");
234 if (!OGLFT::Init_FT())
236 DebugMessage(M64MSG_ERROR, "Could not initialize freetype library.");
240 fontpath = ConfigGetSharedDataFilepath(FONT_FILENAME);
242 l_font = new OGLFT::Monochrome(fontpath, (float) height / 35.0f); // make font size proportional to screen height
244 if(!l_font || !l_font->isValid())
246 DebugMessage(M64MSG_ERROR, "Could not construct face from %s", fontpath);
251 for (int i = 0; i < OSD_NUM_CORNERS; i++)
252 fCornerScroll[i] = 0.0;
254 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
255 #if defined(GL_RASTER_POSITION_UNCLIPPED_IBM)
256 glEnable(GL_RASTER_POSITION_UNCLIPPED_IBM);
259 pglActiveTexture = (PTRGLACTIVETEXTURE) VidExt_GL_GetProcAddress("glActiveTexture");
260 if (pglActiveTexture == NULL)
262 DebugMessage(M64MSG_WARNING, "OpenGL function glActiveTexture() not supported. OSD deactivated.");
266 // set initialized flag
267 l_OsdInitialized = 1;
273 osd_message_t *msg, *safe;
275 // delete font renderer
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)
289 SDL_UnlockMutex(osd_list_lock);
291 // shut down the Freetype library
294 SDL_DestroyMutex(osd_list_lock);
296 // reset initialized flag
297 l_OsdInitialized = 0;
300 // renders the current osd message queue to the screen
304 osd_message_t *msg, *safe;
307 // if we're not initialized or list is empty, then just skip it all
308 if (!l_OsdInitialized || list_empty(&l_messageQueue))
311 // get the viewport dimensions
313 glGetIntegerv(GL_VIEWPORT, viewport);
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;
322 // deactivate all the texturing units
325 glGetIntegerv(GL_ACTIVE_TEXTURE_ARB, &iActiveTex);
326 for (i = 0; i < 8; i++)
328 pglActiveTexture(GL_TEXTURE0_ARB + i);
329 bTexture2D[i] = glIsEnabled(GL_TEXTURE_2D) != 0;
330 glDisable(GL_TEXTURE_2D);
333 // save the matrices and set up new ones
334 glMatrixMode(GL_PROJECTION);
337 gluOrtho2D(viewport[0],viewport[2],viewport[1],viewport[3]);
339 glMatrixMode(GL_MODELVIEW);
343 // setup for drawing text
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);
355 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
357 glDisableClientState(GL_COLOR_ARRAY);
358 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
359 glDisableClientState(GL_SECONDARY_COLOR_ARRAY);
360 glShadeModel(GL_FLAT);
362 // get line height if invalid
363 if (l_fLineHeight < 0.0)
365 OGLFT::BBox bbox = l_font->measure("01abjZpqRGB");
366 l_fLineHeight = (bbox.y_max_ - bbox.y_min_) / 30.0f;
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;
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])
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)
383 if (msg->user_managed) {
384 osd_remove_message(msg);
386 osd_remove_message(msg);
393 // go to next state and reset frame count
398 // offset y depending on how many other messages are in the same corner
400 if (msg->corner >= OSD_MIDDLE_LEFT && msg->corner <= OSD_MIDDLE_RIGHT) // don't scroll the middle messages
401 fStartOffset = fCornerPos[msg->corner];
403 fStartOffset = fCornerPos[msg->corner] + (fCornerScroll[msg->corner] * l_fLineHeight);
404 msg->yoffset += get_message_offset(msg, fStartOffset);
406 draw_message(msg, viewport[2], viewport[3]);
408 msg->yoffset -= get_message_offset(msg, fStartOffset);
409 fCornerPos[msg->corner] += l_fLineHeight;
411 SDL_UnlockMutex(osd_list_lock);
414 for (int i = 0; i < OSD_NUM_CORNERS; i++)
416 fCornerScroll[i] += 0.1f;
417 if (fCornerScroll[i] >= 0.0)
418 fCornerScroll[i] = 0.0;
421 // restore the matrices
422 glMatrixMode(GL_MODELVIEW);
424 glMatrixMode(GL_PROJECTION);
427 // restore the attributes
428 for (int i = 0; i < 8; i++)
430 pglActiveTexture(GL_TEXTURE0_ARB + i);
432 glEnable(GL_TEXTURE_2D);
434 glDisable(GL_TEXTURE_2D);
436 pglActiveTexture(iActiveTex);
439 glEnable(GL_FRAGMENT_PROGRAM_ARB);
441 glEnableClientState(GL_COLOR_ARRAY);
443 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
445 glEnableClientState(GL_SECONDARY_COLOR_ARRAY);
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,
454 osd_message_t * osd_new_message(enum osd_corner eCorner, const char *fmt, ...)
459 if (!l_OsdInitialized) return NULL;
461 osd_message_t *msg = (osd_message_t *)malloc(sizeof(*msg));
463 if (!msg) return NULL;
466 vsnprintf(buf, 1024, fmt, ap);
470 // set default values
471 memset(msg, 0, sizeof(osd_message_t));
472 msg->text = strdup(buf);
473 msg->user_managed = 0;
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;
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
488 msg->animation[OSD_APPEAR] = OSD_FADE;
489 msg->animation[OSD_DISPLAY] = OSD_NONE;
490 msg->animation[OSD_DISAPPEAR] = OSD_FADE;
492 if (eCorner >= OSD_MIDDLE_LEFT && eCorner <= OSD_MIDDLE_RIGHT)
494 msg->timeout[OSD_APPEAR] = 20;
495 msg->timeout[OSD_DISPLAY] = 60;
496 msg->timeout[OSD_DISAPPEAR] = 20;
500 msg->timeout[OSD_APPEAR] = 20;
501 msg->timeout[OSD_DISPLAY] = 180;
502 msg->timeout[OSD_DISAPPEAR] = 40;
505 // add to message queue
506 SDL_LockMutex(osd_list_lock);
507 list_add(&msg->list, &l_messageQueue);
508 SDL_UnlockMutex(osd_list_lock);
513 // update message string
515 void osd_update_message(osd_message_t *msg, const char *fmt, ...)
520 if (!l_OsdInitialized || !msg) return;
523 vsnprintf(buf, 1024, fmt, ap);
528 msg->text = strdup(buf);
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;
536 // reset display time counter
537 if (msg->state >= OSD_DISPLAY)
539 msg->state = OSD_DISPLAY;
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);
550 // remove message from message queue
551 static void osd_remove_message(osd_message_t *msg)
553 if (!l_OsdInitialized || !msg) return;
557 list_del(&msg->list);
560 // remove message from message queue and free it
562 void osd_delete_message(osd_message_t *msg)
564 if (!l_OsdInitialized || !msg) return;
566 SDL_LockMutex(osd_list_lock);
567 osd_remove_message(msg);
569 SDL_UnlockMutex(osd_list_lock);
572 // set message so it doesn't automatically expire in a certain number of frames.
574 void osd_message_set_static(osd_message_t *msg)
576 if (!l_OsdInitialized || !msg) return;
578 msg->timeout[OSD_DISPLAY] = OSD_INFINITE_TIMEOUT;
579 msg->state = OSD_DISPLAY;
583 // set message so it doesn't automatically get freed when finished transition.
585 void osd_message_set_user_managed(osd_message_t *msg)
587 if (!l_OsdInitialized || !msg) return;
589 msg->user_managed = 1;
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)
597 if (!l_OsdInitialized || !testmsg) return NULL;
599 list_for_each_entry(msg, &l_messageQueue, osd_message_t, list) {