Path: blob/master/libmupen64plus/mupen64plus-core/src/osd/osd.cpp
2 views
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *1* Mupen64plus - osd.cpp *2* Mupen64Plus homepage: http://code.google.com/p/mupen64plus/ *3* Copyright (C) 2008 Nmn Ebenblues *4* *5* This program is free software; you can redistribute it and/or modify *6* it under the terms of the GNU General Public License as published by *7* the Free Software Foundation; either version 2 of the License, or *8* (at your option) any later version. *9* *10* This program is distributed in the hope that it will be useful, *11* but WITHOUT ANY WARRANTY; without even the implied warranty of *12* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *13* GNU General Public License for more details. *14* *15* You should have received a copy of the GNU General Public License *16* along with this program; if not, write to the *17* Free Software Foundation, Inc., *18* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *19* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */2021// On-screen Display22#include <SDL_opengl.h>23#include <SDL_thread.h>2425#include "OGLFT.h"26#include "osd.h"2728extern "C" {29#define M64P_CORE_PROTOTYPES 130#include "api/m64p_config.h"31#include "api/config.h"32#include "api/callbacks.h"33#include "api/m64p_vidext.h"34#include "api/vidext.h"35#include "main/main.h"36#include "main/list.h"37#include "osal/files.h"38#include "osal/preproc.h"39#include "plugin/plugin.h"40}4142#define FONT_FILENAME "font.ttf"4344typedef void (APIENTRYP PTRGLACTIVETEXTURE)(GLenum texture);45static PTRGLACTIVETEXTURE pglActiveTexture = NULL;4647// static variables for OSD48static int l_OsdInitialized = 0;4950static LIST_HEAD(l_messageQueue);51static OGLFT::Monochrome *l_font;52static float l_fLineHeight = -1.0;5354static void animation_none(osd_message_t *);55static void animation_fade(osd_message_t *);56static void osd_remove_message(osd_message_t *msg);57static osd_message_t * osd_message_valid(osd_message_t *testmsg);5859static float fCornerScroll[OSD_NUM_CORNERS];6061static SDL_mutex *osd_list_lock;6263// animation handlers64static void (*l_animations[OSD_NUM_ANIM_TYPES])(osd_message_t *) = {65animation_none, // animation handler for OSD_NONE66animation_fade // animation handler for OSD_FADE67};6869// private functions70// draw message on screen71static void draw_message(osd_message_t *msg, int width, int height)72{73float x = 0.,74y = 0.;7576if(!l_font || !l_font->isValid())77return;7879// set color. alpha is hard coded to 1. animation can change this80l_font->setForegroundColor(msg->color[R], msg->color[G], msg->color[B], 1.0);81l_font->setBackgroundColor(0.0, 0.0, 0.0, 0.0);8283// set justification based on corner84switch(msg->corner)85{86case OSD_TOP_LEFT:87l_font->setVerticalJustification(OGLFT::Face::TOP);88l_font->setHorizontalJustification(OGLFT::Face::LEFT);89x = 0.;90y = (float)height;91break;92case OSD_TOP_CENTER:93l_font->setVerticalJustification(OGLFT::Face::TOP);94l_font->setHorizontalJustification(OGLFT::Face::CENTER);95x = ((float)width)/2.0f;96y = (float)height;97break;98case OSD_TOP_RIGHT:99l_font->setVerticalJustification(OGLFT::Face::TOP);100l_font->setHorizontalJustification(OGLFT::Face::RIGHT);101x = (float)width;102y = (float)height;103break;104case OSD_MIDDLE_LEFT:105l_font->setVerticalJustification(OGLFT::Face::MIDDLE);106l_font->setHorizontalJustification(OGLFT::Face::LEFT);107x = 0.;108y = ((float)height)/2.0f;109break;110case OSD_MIDDLE_CENTER:111l_font->setVerticalJustification(OGLFT::Face::MIDDLE);112l_font->setHorizontalJustification(OGLFT::Face::CENTER);113x = ((float)width)/2.0f;114y = ((float)height)/2.0f;115break;116case OSD_MIDDLE_RIGHT:117l_font->setVerticalJustification(OGLFT::Face::MIDDLE);118l_font->setHorizontalJustification(OGLFT::Face::RIGHT);119x = (float)width;120y = ((float)height)/2.0f;121break;122case OSD_BOTTOM_LEFT:123l_font->setVerticalJustification(OGLFT::Face::BOTTOM);124l_font->setHorizontalJustification(OGLFT::Face::LEFT);125x = 0.;126y = 0.;127break;128case OSD_BOTTOM_CENTER:129l_font->setVerticalJustification(OGLFT::Face::BOTTOM);130l_font->setHorizontalJustification(OGLFT::Face::CENTER);131x = ((float)width)/2.0f;132y = 0.;133break;134case OSD_BOTTOM_RIGHT:135l_font->setVerticalJustification(OGLFT::Face::BOTTOM);136l_font->setHorizontalJustification(OGLFT::Face::RIGHT);137x = (float)width;138y = 0.;139break;140default:141l_font->setVerticalJustification(OGLFT::Face::BOTTOM);142l_font->setHorizontalJustification(OGLFT::Face::LEFT);143x = 0.;144y = 0.;145break;146}147148// apply animation for current message state149(*l_animations[msg->animation[msg->state]])(msg);150151// xoffset moves message left152x -= msg->xoffset;153// yoffset moves message up154y += msg->yoffset;155156// get the bounding box if invalid157if (msg->sizebox[0] == 0 && msg->sizebox[2] == 0) // xmin and xmax158{159OGLFT::BBox bbox = l_font->measure_nominal(msg->text);160msg->sizebox[0] = bbox.x_min_;161msg->sizebox[1] = bbox.y_min_;162msg->sizebox[2] = bbox.x_max_;163msg->sizebox[3] = bbox.y_max_;164}165166// draw the text line167l_font->draw(x, y, msg->text, msg->sizebox);168}169170// null animation handler171static void animation_none(osd_message_t *msg) { }172173// fade in/out animation handler174static void animation_fade(osd_message_t *msg)175{176float alpha = 1.;177float elapsed_frames;178float total_frames = (float)msg->timeout[msg->state];179180switch(msg->state)181{182case OSD_DISAPPEAR:183elapsed_frames = (float)(total_frames - msg->frames);184break;185case OSD_APPEAR:186default:187elapsed_frames = (float)msg->frames;188break;189}190191if(total_frames != 0.)192alpha = elapsed_frames / total_frames;193194l_font->setForegroundColor(msg->color[R], msg->color[G], msg->color[B], alpha);195}196197// sets message Y offset depending on where they are in the message queue198static float get_message_offset(osd_message_t *msg, float fLinePos)199{200float offset = (float) (l_font->height() * fLinePos);201202switch(msg->corner)203{204case OSD_TOP_LEFT:205case OSD_TOP_CENTER:206case OSD_TOP_RIGHT:207return -offset;208break;209default:210return offset;211break;212}213}214215// public functions216extern "C"217void osd_init(int width, int height)218{219const char *fontpath;220221osd_list_lock = SDL_CreateMutex();222if (!osd_list_lock) {223DebugMessage(M64MSG_ERROR, "Could not create osd list lock");224return;225}226227if (!OGLFT::Init_FT())228{229DebugMessage(M64MSG_ERROR, "Could not initialize freetype library.");230return;231}232233fontpath = ConfigGetSharedDataFilepath(FONT_FILENAME);234235l_font = new OGLFT::Monochrome(fontpath, (float) height / 35.0f); // make font size proportional to screen height236237if(!l_font || !l_font->isValid())238{239DebugMessage(M64MSG_ERROR, "Could not construct face from %s", fontpath);240return;241}242243// clear statics244for (int i = 0; i < OSD_NUM_CORNERS; i++)245fCornerScroll[i] = 0.0;246247glPixelStorei(GL_UNPACK_ALIGNMENT, 1);248#if defined(GL_RASTER_POSITION_UNCLIPPED_IBM)249glEnable(GL_RASTER_POSITION_UNCLIPPED_IBM);250#endif251252pglActiveTexture = (PTRGLACTIVETEXTURE) VidExt_GL_GetProcAddress("glActiveTexture");253if (pglActiveTexture == NULL)254{255DebugMessage(M64MSG_WARNING, "OpenGL function glActiveTexture() not supported. OSD deactivated.");256return;257}258259// set initialized flag260l_OsdInitialized = 1;261}262263extern "C"264void osd_exit(void)265{266osd_message_t *msg, *safe;267268// delete font renderer269if (l_font)270{271delete l_font;272l_font = NULL;273}274275// delete message queue276SDL_LockMutex(osd_list_lock);277list_for_each_entry_safe(msg, safe, &l_messageQueue, osd_message_t, list) {278osd_remove_message(msg);279if (!msg->user_managed)280free(msg);281}282SDL_UnlockMutex(osd_list_lock);283284// shut down the Freetype library285OGLFT::Uninit_FT();286287SDL_DestroyMutex(osd_list_lock);288289// reset initialized flag290l_OsdInitialized = 0;291}292293// renders the current osd message queue to the screen294extern "C"295void osd_render()296{297osd_message_t *msg, *safe;298int i;299300// if we're not initialized or list is empty, then just skip it all301if (!l_OsdInitialized || list_empty(&l_messageQueue))302return;303304// get the viewport dimensions305GLint viewport[4];306glGetIntegerv(GL_VIEWPORT, viewport);307308// save all the attributes309glPushAttrib(GL_ALL_ATTRIB_BITS);310bool bFragmentProg = glIsEnabled(GL_FRAGMENT_PROGRAM_ARB) != 0;311bool bColorArray = glIsEnabled(GL_COLOR_ARRAY) != 0;312bool bTexCoordArray = glIsEnabled(GL_TEXTURE_COORD_ARRAY) != 0;313bool bSecColorArray = glIsEnabled(GL_SECONDARY_COLOR_ARRAY) != 0;314315// deactivate all the texturing units316GLint iActiveTex;317bool bTexture2D[8];318glGetIntegerv(GL_ACTIVE_TEXTURE_ARB, &iActiveTex);319for (i = 0; i < 8; i++)320{321pglActiveTexture(GL_TEXTURE0_ARB + i);322bTexture2D[i] = glIsEnabled(GL_TEXTURE_2D) != 0;323glDisable(GL_TEXTURE_2D);324}325326// save the matrices and set up new ones327glMatrixMode(GL_PROJECTION);328glPushMatrix();329glLoadIdentity();330gluOrtho2D(viewport[0],viewport[2],viewport[1],viewport[3]);331332glMatrixMode(GL_MODELVIEW);333glPushMatrix();334glLoadIdentity();335336// setup for drawing text337glDisable(GL_FOG);338glDisable(GL_LIGHTING);339glDisable(GL_ALPHA_TEST);340glDisable(GL_DEPTH_TEST);341glDisable(GL_CULL_FACE);342glDisable(GL_SCISSOR_TEST);343glDisable(GL_STENCIL_TEST);344glDisable(GL_FRAGMENT_PROGRAM_ARB);345glDisable(GL_COLOR_MATERIAL);346347glEnable(GL_BLEND);348glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);349350glDisableClientState(GL_COLOR_ARRAY);351glDisableClientState(GL_TEXTURE_COORD_ARRAY);352glDisableClientState(GL_SECONDARY_COLOR_ARRAY);353glShadeModel(GL_FLAT);354355// get line height if invalid356if (l_fLineHeight < 0.0)357{358OGLFT::BBox bbox = l_font->measure("01abjZpqRGB");359l_fLineHeight = (bbox.y_max_ - bbox.y_min_) / 30.0f;360}361362// keeps track of next message position for each corner363float fCornerPos[OSD_NUM_CORNERS];364for (i = 0; i < OSD_NUM_CORNERS; i++)365fCornerPos[i] = 0.5f * l_fLineHeight;366367SDL_LockMutex(osd_list_lock);368list_for_each_entry_safe(msg, safe, &l_messageQueue, osd_message_t, list) {369// update message state370if(msg->timeout[msg->state] != OSD_INFINITE_TIMEOUT &&371++msg->frames >= msg->timeout[msg->state])372{373// if message is in last state, mark it for deletion and continue to the next message374if(msg->state >= OSD_NUM_STATES - 1)375{376if (msg->user_managed) {377osd_remove_message(msg);378} else {379osd_remove_message(msg);380free(msg);381}382383continue;384}385386// go to next state and reset frame count387msg->state++;388msg->frames = 0;389}390391// offset y depending on how many other messages are in the same corner392float fStartOffset;393if (msg->corner >= OSD_MIDDLE_LEFT && msg->corner <= OSD_MIDDLE_RIGHT) // don't scroll the middle messages394fStartOffset = fCornerPos[msg->corner];395else396fStartOffset = fCornerPos[msg->corner] + (fCornerScroll[msg->corner] * l_fLineHeight);397msg->yoffset += get_message_offset(msg, fStartOffset);398399draw_message(msg, viewport[2], viewport[3]);400401msg->yoffset -= get_message_offset(msg, fStartOffset);402fCornerPos[msg->corner] += l_fLineHeight;403}404SDL_UnlockMutex(osd_list_lock);405406// do the scrolling407for (int i = 0; i < OSD_NUM_CORNERS; i++)408{409fCornerScroll[i] += 0.1f;410if (fCornerScroll[i] >= 0.0)411fCornerScroll[i] = 0.0;412}413414// restore the matrices415glMatrixMode(GL_MODELVIEW);416glPopMatrix();417glMatrixMode(GL_PROJECTION);418glPopMatrix();419420// restore the attributes421for (int i = 0; i < 8; i++)422{423pglActiveTexture(GL_TEXTURE0_ARB + i);424if (bTexture2D[i])425glEnable(GL_TEXTURE_2D);426else427glDisable(GL_TEXTURE_2D);428}429pglActiveTexture(iActiveTex);430glPopAttrib();431if (bFragmentProg)432glEnable(GL_FRAGMENT_PROGRAM_ARB);433if (bColorArray)434glEnableClientState(GL_COLOR_ARRAY);435if (bTexCoordArray)436glEnableClientState(GL_TEXTURE_COORD_ARRAY);437if (bSecColorArray)438glEnableClientState(GL_SECONDARY_COLOR_ARRAY);439440glFinish();441}442443// creates a new osd_message_t, adds it to the message queue and returns it in case444// the user wants to modify its parameters. Note, if the message can't be created,445// NULL is returned.446extern "C"447osd_message_t * osd_new_message(enum osd_corner eCorner, const char *fmt, ...)448{449va_list ap;450char buf[1024];451452if (!l_OsdInitialized) return NULL;453454osd_message_t *msg = (osd_message_t *)malloc(sizeof(*msg));455456if (!msg) return NULL;457458va_start(ap, fmt);459vsnprintf(buf, 1024, fmt, ap);460buf[1023] = 0;461va_end(ap);462463// set default values464memset(msg, 0, sizeof(osd_message_t));465msg->text = strdup(buf);466msg->user_managed = 0;467// default to white468msg->color[R] = 1.;469msg->color[G] = 1.;470msg->color[B] = 1.;471472msg->sizebox[0] = 0.0; // set a null bounding box473msg->sizebox[1] = 0.0;474msg->sizebox[2] = 0.0;475msg->sizebox[3] = 0.0;476477msg->corner = eCorner;478msg->state = OSD_APPEAR;479fCornerScroll[eCorner] -= 1.0; // start this one before the beginning of the list and scroll it in480481msg->animation[OSD_APPEAR] = OSD_FADE;482msg->animation[OSD_DISPLAY] = OSD_NONE;483msg->animation[OSD_DISAPPEAR] = OSD_FADE;484485if (eCorner >= OSD_MIDDLE_LEFT && eCorner <= OSD_MIDDLE_RIGHT)486{487msg->timeout[OSD_APPEAR] = 20;488msg->timeout[OSD_DISPLAY] = 60;489msg->timeout[OSD_DISAPPEAR] = 20;490}491else492{493msg->timeout[OSD_APPEAR] = 20;494msg->timeout[OSD_DISPLAY] = 180;495msg->timeout[OSD_DISAPPEAR] = 40;496}497498// add to message queue499SDL_LockMutex(osd_list_lock);500list_add(&msg->list, &l_messageQueue);501SDL_UnlockMutex(osd_list_lock);502503return msg;504}505506// update message string507extern "C"508void osd_update_message(osd_message_t *msg, const char *fmt, ...)509{510va_list ap;511char buf[1024];512513if (!l_OsdInitialized || !msg) return;514515va_start(ap, fmt);516vsnprintf(buf, 1024, fmt, ap);517buf[1023] = 0;518va_end(ap);519520free(msg->text);521msg->text = strdup(buf);522523// reset bounding box524msg->sizebox[0] = 0.0;525msg->sizebox[1] = 0.0;526msg->sizebox[2] = 0.0;527msg->sizebox[3] = 0.0;528529// reset display time counter530if (msg->state >= OSD_DISPLAY)531{532msg->state = OSD_DISPLAY;533msg->frames = 0;534}535536SDL_LockMutex(osd_list_lock);537if (!osd_message_valid(msg))538list_add(&msg->list, &l_messageQueue);539SDL_UnlockMutex(osd_list_lock);540541}542543// remove message from message queue544static void osd_remove_message(osd_message_t *msg)545{546if (!l_OsdInitialized || !msg) return;547548free(msg->text);549msg->text = NULL;550list_del(&msg->list);551}552553// remove message from message queue and free it554extern "C"555void osd_delete_message(osd_message_t *msg)556{557if (!l_OsdInitialized || !msg) return;558559SDL_LockMutex(osd_list_lock);560osd_remove_message(msg);561free(msg);562SDL_UnlockMutex(osd_list_lock);563}564565// set message so it doesn't automatically expire in a certain number of frames.566extern "C"567void osd_message_set_static(osd_message_t *msg)568{569if (!l_OsdInitialized || !msg) return;570571msg->timeout[OSD_DISPLAY] = OSD_INFINITE_TIMEOUT;572msg->state = OSD_DISPLAY;573msg->frames = 0;574}575576// set message so it doesn't automatically get freed when finished transition.577extern "C"578void osd_message_set_user_managed(osd_message_t *msg)579{580if (!l_OsdInitialized || !msg) return;581582msg->user_managed = 1;583}584585// return message pointer if valid (in the OSD list), otherwise return NULL586static osd_message_t * osd_message_valid(osd_message_t *testmsg)587{588osd_message_t *msg;589590if (!l_OsdInitialized || !testmsg) return NULL;591592list_for_each_entry(msg, &l_messageQueue, osd_message_t, list) {593if (msg == testmsg)594return testmsg;595}596597return NULL;598}599600601602