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