mirror of https://github.com/stella-emu/stella.git
1149 lines
33 KiB
C++
1149 lines
33 KiB
C++
|
|
//
|
|
// SSSS tt lll lll
|
|
// SS SS tt ll ll
|
|
// SS tttttt eeee ll ll aaaa
|
|
// SSSS tt ee ee ll ll aa
|
|
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
|
|
// SS SS tt ee ll ll aa aa
|
|
// SSSS ttt eeeee llll llll aaaaa
|
|
//
|
|
// Copyright (c) 1995-2008 by Bradford W. Mott and the Stella team
|
|
//
|
|
// See the file "license" for information on usage and redistribution of
|
|
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
|
//
|
|
// $Id: FrameBuffer.cxx,v 1.143 2008-12-12 15:51:07 stephena Exp $
|
|
//============================================================================
|
|
|
|
#include <algorithm>
|
|
#include <sstream>
|
|
|
|
#include "bspf.hxx"
|
|
|
|
#include "CommandMenu.hxx"
|
|
#include "Console.hxx"
|
|
#include "EventHandler.hxx"
|
|
#include "Event.hxx"
|
|
#include "Font.hxx"
|
|
#include "Launcher.hxx"
|
|
#include "MediaSrc.hxx"
|
|
#include "Menu.hxx"
|
|
#include "OSystem.hxx"
|
|
#include "Settings.hxx"
|
|
|
|
#include "FrameBuffer.hxx"
|
|
|
|
#ifdef DEBUGGER_SUPPORT
|
|
#include "Debugger.hxx"
|
|
#endif
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
FrameBuffer::FrameBuffer(OSystem* osystem)
|
|
: myOSystem(osystem),
|
|
myScreen(0),
|
|
myRedrawEntireFrame(true),
|
|
myUsePhosphor(false),
|
|
myPhosphorBlend(77),
|
|
myInitializedCount(0),
|
|
myPausedCount(0),
|
|
mySurfaceCount(0)
|
|
{
|
|
myMsg.surface = myStatsMsg.surface = 0;
|
|
myMsg.surfaceID = myStatsMsg.surfaceID = -1;
|
|
myMsg.enabled = myStatsMsg.enabled = false;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
FrameBuffer::~FrameBuffer(void)
|
|
{
|
|
freeSurface(myMsg.surfaceID);
|
|
freeSurface(myStatsMsg.surfaceID);
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
bool FrameBuffer::initialize(const string& title, uInt32 width, uInt32 height)
|
|
{
|
|
// Now (re)initialize the SDL video system
|
|
// These things only have to be done one per FrameBuffer creation
|
|
if(SDL_WasInit(SDL_INIT_VIDEO) == 0)
|
|
{
|
|
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0)
|
|
{
|
|
cerr << "ERROR: Couldn't initialize SDL: " << SDL_GetError() << endl;
|
|
return false;
|
|
}
|
|
}
|
|
myInitializedCount++;
|
|
|
|
// Set fullscreen flag
|
|
#ifdef WINDOWED_SUPPORT
|
|
mySDLFlags = myOSystem->settings().getBool("fullscreen") ? SDL_FULLSCREEN : 0;
|
|
#else
|
|
mySDLFlags = 0;
|
|
#endif
|
|
|
|
cerr << " <== FrameBuffer::initialize: w = " << width << ", h = " << height << endl;
|
|
|
|
// Set the available video modes for this framebuffer
|
|
setAvailableVidModes(width, height);
|
|
|
|
// Initialize video subsystem (make sure we get a valid mode)
|
|
VideoMode mode = getSavedVidMode();
|
|
if(width <= mode.screen_w && height <= mode.screen_h)
|
|
{
|
|
// Set window title and icon
|
|
setWindowTitle(title);
|
|
if(myInitializedCount == 1) setWindowIcon();
|
|
|
|
if(!initSubsystem(mode))
|
|
{
|
|
cerr << "ERROR: Couldn't initialize video subsystem" << endl;
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
myImageRect.setWidth(mode.image_w);
|
|
myImageRect.setHeight(mode.image_h);
|
|
myImageRect.moveTo(mode.image_x, mode.image_y);
|
|
|
|
myScreenRect.setWidth(mode.screen_w);
|
|
myScreenRect.setHeight(mode.screen_h);
|
|
}
|
|
}
|
|
else
|
|
return false;
|
|
|
|
// And refresh the display
|
|
myOSystem->eventHandler().refreshDisplay();
|
|
|
|
// Enable unicode so we can see translated key events
|
|
// (lowercase vs. uppercase characters)
|
|
SDL_EnableUNICODE(1);
|
|
|
|
// Erase any messages from a previous run
|
|
myMsg.counter = 0;
|
|
|
|
// Create surfaces for TIA statistics and general messages
|
|
myStatsMsg.color = kBtnTextColor;
|
|
myStatsMsg.w = myOSystem->consoleFont().getStringWidth("000 LINES %00.00 FPS");
|
|
myStatsMsg.h = myOSystem->consoleFont().getFontHeight();
|
|
if(myStatsMsg.surfaceID < 0)
|
|
{
|
|
myStatsMsg.surfaceID = allocateSurface(myStatsMsg.w, myStatsMsg.h);
|
|
myStatsMsg.surface = surface(myStatsMsg.surfaceID);
|
|
}
|
|
if(!myMsg.surface) // TODO - change this to the font we'll really use
|
|
{
|
|
myMsg.surfaceID = allocateSurface(320, myOSystem->consoleFont().getFontHeight()+10);
|
|
myMsg.surface = surface(myMsg.surfaceID);
|
|
}
|
|
|
|
// Finally, show some information about the framebuffer,
|
|
// but only on the first initialization
|
|
if(myInitializedCount == 1 && myOSystem->settings().getBool("showinfo"))
|
|
cout << about() << endl;
|
|
|
|
return true;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::update()
|
|
{
|
|
// Determine which mode we are in (from the EventHandler)
|
|
// Take care of S_EMULATE mode here, otherwise let the GUI
|
|
// figure out what to draw
|
|
switch(myOSystem->eventHandler().state())
|
|
{
|
|
case EventHandler::S_EMULATE:
|
|
{
|
|
// Run the console for one frame
|
|
myOSystem->console().mediaSource().update();
|
|
if(myOSystem->eventHandler().frying())
|
|
myOSystem->console().fry();
|
|
|
|
// And update the screen
|
|
drawMediaSource();
|
|
|
|
// Show frame statistics
|
|
if(myStatsMsg.enabled)
|
|
{
|
|
// FIXME - sizes hardcoded for now; fix during UI refactoring
|
|
char msg[30];
|
|
sprintf(msg, "%u LINES %2.2f FPS",
|
|
myOSystem->console().mediaSource().scanlines(),
|
|
myOSystem->console().getFramerate());
|
|
myStatsMsg.surface->fillRect(0, 0, myStatsMsg.w, myStatsMsg.h, kBGColor);
|
|
myStatsMsg.surface->drawString(&myOSystem->consoleFont(), msg, 0, 0,
|
|
myStatsMsg.w, myStatsMsg.color, kTextAlignLeft);
|
|
myStatsMsg.surface->addDirtyRect(0, 0, 0, 0); // force a full draw
|
|
myStatsMsg.surface->setPos(myImageRect.x() + 3, myImageRect.y() + 3);
|
|
myStatsMsg.surface->update();
|
|
}
|
|
break; // S_EMULATE
|
|
}
|
|
|
|
case EventHandler::S_PAUSE:
|
|
{
|
|
// Only update the screen if it's been invalidated
|
|
if(myRedrawEntireFrame)
|
|
drawMediaSource();
|
|
|
|
// Show a pause message every 5 seconds
|
|
if(myPausedCount++ >= 7*myOSystem->frameRate())
|
|
{
|
|
myPausedCount = 0;
|
|
showMessage("Paused", kMiddleCenter);
|
|
}
|
|
break; // S_PAUSE
|
|
}
|
|
|
|
case EventHandler::S_MENU:
|
|
{
|
|
// Only update the screen if it's been invalidated
|
|
if(myRedrawEntireFrame)
|
|
drawMediaSource();
|
|
|
|
myOSystem->menu().draw();
|
|
break; // S_MENU
|
|
}
|
|
|
|
case EventHandler::S_CMDMENU:
|
|
{
|
|
// Only update the screen if it's been invalidated
|
|
if(myRedrawEntireFrame)
|
|
drawMediaSource();
|
|
|
|
myOSystem->commandMenu().draw();
|
|
break; // S_CMDMENU
|
|
}
|
|
|
|
case EventHandler::S_LAUNCHER:
|
|
{
|
|
myOSystem->launcher().draw();
|
|
break; // S_LAUNCHER
|
|
}
|
|
|
|
#ifdef DEBUGGER_SUPPORT
|
|
case EventHandler::S_DEBUGGER:
|
|
{
|
|
myOSystem->debugger().draw();
|
|
break; // S_DEBUGGER
|
|
}
|
|
#endif
|
|
|
|
default:
|
|
return;
|
|
break;
|
|
}
|
|
|
|
// Draw any pending messages
|
|
if(myMsg.counter > 0)
|
|
drawMessage();
|
|
|
|
// Do any post-frame stuff
|
|
postFrameUpdate();
|
|
|
|
// The frame doesn't need to be completely redrawn anymore
|
|
myRedrawEntireFrame = false;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::showMessage(const string& message, MessagePosition position,
|
|
int color)
|
|
{
|
|
// Erase old messages on the screen
|
|
if(myMsg.counter > 0)
|
|
{
|
|
myRedrawEntireFrame = true;
|
|
myOSystem->eventHandler().refreshDisplay();
|
|
}
|
|
|
|
// Precompute the message coordinates
|
|
myMsg.text = message;
|
|
myMsg.counter = uInt32(myOSystem->frameRate()) << 1; // Show message for 2 seconds
|
|
myMsg.color = color;
|
|
|
|
myMsg.w = myOSystem->font().getStringWidth(myMsg.text) + 10;
|
|
myMsg.h = myOSystem->font().getFontHeight() + 8;
|
|
myMsg.surface->setWidth(myMsg.w);
|
|
myMsg.surface->setHeight(myMsg.h);
|
|
|
|
switch(position)
|
|
{
|
|
case kTopLeft:
|
|
myMsg.x = 5;
|
|
myMsg.y = 5;
|
|
break;
|
|
|
|
case kTopCenter:
|
|
myMsg.x = (myImageRect.width() - myMsg.w) >> 1;
|
|
myMsg.y = 5;
|
|
break;
|
|
|
|
case kTopRight:
|
|
myMsg.x = myImageRect.width() - myMsg.w - 5;
|
|
myMsg.y = 5;
|
|
break;
|
|
|
|
case kMiddleLeft:
|
|
myMsg.x = 5;
|
|
myMsg.y = (myImageRect.height() - myMsg.h) >> 1;
|
|
break;
|
|
|
|
case kMiddleCenter:
|
|
myMsg.x = (myImageRect.width() - myMsg.w) >> 1;
|
|
myMsg.y = (myImageRect.height() - myMsg.h) >> 1;
|
|
break;
|
|
|
|
case kMiddleRight:
|
|
myMsg.x = myImageRect.width() - myMsg.w - 5;
|
|
myMsg.y = (myImageRect.height() - myMsg.h) >> 1;
|
|
break;
|
|
|
|
case kBottomLeft:
|
|
myMsg.x = 5;
|
|
myMsg.y = myImageRect.height() - myMsg.h - 5;
|
|
break;
|
|
|
|
case kBottomCenter:
|
|
myMsg.x = (myImageRect.width() - myMsg.w) >> 1;
|
|
myMsg.y = myImageRect.height() - myMsg.h - 5;
|
|
break;
|
|
|
|
case kBottomRight:
|
|
myMsg.x = myImageRect.width() - myMsg.w - 5;
|
|
myMsg.y = myImageRect.height() - myMsg.h - 5;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::toggleFrameStats()
|
|
{
|
|
showFrameStats(!myOSystem->settings().getBool("stats"));
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::showFrameStats(bool enable)
|
|
{
|
|
myOSystem->settings().setBool("stats", enable);
|
|
myStatsMsg.enabled = enable;
|
|
myOSystem->eventHandler().refreshDisplay();
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::enableMessages(bool enable)
|
|
{
|
|
if(enable)
|
|
{
|
|
// Only re-anable frame stats if they were already enabled before
|
|
myStatsMsg.enabled = myOSystem->settings().getBool("stats");
|
|
}
|
|
else
|
|
{
|
|
// Temporarily disable frame stats
|
|
myStatsMsg.enabled = false;
|
|
|
|
// Erase old messages on the screen
|
|
myMsg.counter = 0;
|
|
|
|
myOSystem->eventHandler().refreshDisplay(true); // Do this twice for
|
|
// myOSystem->eventHandler().refreshDisplay(true); // double-buffered modes
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
inline void FrameBuffer::drawMessage()
|
|
{
|
|
// Draw the bounded box and text
|
|
myMsg.surface->setPos(myMsg.x + myImageRect.x(), myMsg.y + myImageRect.y());
|
|
myMsg.surface->fillRect(0, 0, myMsg.w-2, myMsg.h-4, kBGColor);
|
|
myMsg.surface->box(0, 0, myMsg.w, myMsg.h-2, kColor, kShadowColor);
|
|
myMsg.surface->drawString(&myOSystem->font(), myMsg.text, 4, 4,
|
|
myMsg.w, myMsg.color, kTextAlignLeft);
|
|
myMsg.counter--;
|
|
|
|
// Either erase the entire message (when time is reached),
|
|
// or show again this frame
|
|
if(myMsg.counter == 0) // Force an immediate update
|
|
myOSystem->eventHandler().refreshDisplay(true);
|
|
else
|
|
{
|
|
myMsg.surface->addDirtyRect(0, 0, 0, 0); // force a full draw
|
|
myMsg.surface->update();
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::refresh()
|
|
{
|
|
myRedrawEntireFrame = true;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
int FrameBuffer::allocateSurface(int w, int h, bool useBase)
|
|
{
|
|
// Create a new surface
|
|
FBSurface* surface = createSurface(w, h, useBase);
|
|
|
|
// Add it to the list
|
|
mySurfaceList.insert(make_pair(mySurfaceCount, surface));
|
|
mySurfaceCount++;
|
|
|
|
// Return a reference to it
|
|
return mySurfaceCount - 1;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
int FrameBuffer::freeSurface(int id)
|
|
{
|
|
// Really delete the surface this time
|
|
// That means actually deleting the FBSurface object, and removing it
|
|
// from the list
|
|
map<int,FBSurface*>::iterator iter = mySurfaceList.find(id);
|
|
if(iter != mySurfaceList.end())
|
|
{
|
|
cerr << " delete id = " << iter->first << ", " << iter->second << endl;
|
|
delete iter->second;
|
|
mySurfaceList.erase(iter);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
FBSurface* FrameBuffer::surface(int id) const
|
|
{
|
|
map<int,FBSurface*>::const_iterator iter = mySurfaceList.find(id);
|
|
return iter != mySurfaceList.end() ? iter->second : NULL;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::reloadSurfaces()
|
|
{
|
|
map<int,FBSurface*>::iterator iter;
|
|
for(iter = mySurfaceList.begin(); iter != mySurfaceList.end(); ++iter)
|
|
iter->second->reload();
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::setTIAPalette(const uInt32* palette)
|
|
{
|
|
int i, j;
|
|
|
|
// Set palette for normal fill
|
|
for(i = 0; i < 256; ++i)
|
|
{
|
|
Uint8 r = (palette[i] >> 16) & 0xff;
|
|
Uint8 g = (palette[i] >> 8) & 0xff;
|
|
Uint8 b = palette[i] & 0xff;
|
|
|
|
myDefPalette[i] = mapRGB(r, g, b);
|
|
}
|
|
|
|
// Set palette for phosphor effect
|
|
for(i = 0; i < 256; ++i)
|
|
{
|
|
for(j = 0; j < 256; ++j)
|
|
{
|
|
uInt8 ri = (palette[i] >> 16) & 0xff;
|
|
uInt8 gi = (palette[i] >> 8) & 0xff;
|
|
uInt8 bi = palette[i] & 0xff;
|
|
uInt8 rj = (palette[j] >> 16) & 0xff;
|
|
uInt8 gj = (palette[j] >> 8) & 0xff;
|
|
uInt8 bj = palette[j] & 0xff;
|
|
|
|
Uint8 r = (Uint8) getPhosphor(ri, rj);
|
|
Uint8 g = (Uint8) getPhosphor(gi, gj);
|
|
Uint8 b = (Uint8) getPhosphor(bi, bj);
|
|
|
|
myAvgPalette[i][j] = mapRGB(r, g, b);
|
|
}
|
|
}
|
|
|
|
myRedrawEntireFrame = true;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::setUIPalette(const uInt32* palette)
|
|
{
|
|
// Set palette for GUI
|
|
for(int i = 0; i < kNumColors-256; ++i)
|
|
{
|
|
Uint8 r = (palette[i] >> 16) & 0xff;
|
|
Uint8 g = (palette[i] >> 8) & 0xff;
|
|
Uint8 b = palette[i] & 0xff;
|
|
myDefPalette[i+256] = mapRGB(r, g, b);
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::toggleFullscreen()
|
|
{
|
|
setFullscreen(!myOSystem->settings().getBool("fullscreen"));
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::setFullscreen(bool enable)
|
|
{
|
|
#ifdef WINDOWED_SUPPORT
|
|
// Update the settings
|
|
myOSystem->settings().setBool("fullscreen", enable);
|
|
if(enable)
|
|
mySDLFlags |= SDL_FULLSCREEN;
|
|
else
|
|
mySDLFlags &= ~SDL_FULLSCREEN;
|
|
|
|
// Do a dummy call to getSavedVidMode to set up the modelists
|
|
// and have it point to the correct 'current' mode
|
|
getSavedVidMode();
|
|
|
|
// Do a mode change to the 'current' mode by not passing a '+1' or '-1'
|
|
// to changeVidMode()
|
|
changeVidMode(0);
|
|
#endif
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
bool FrameBuffer::changeVidMode(int direction)
|
|
{
|
|
EventHandler::State state = myOSystem->eventHandler().state();
|
|
bool inUIMode = (state == EventHandler::S_DEBUGGER ||
|
|
state == EventHandler::S_LAUNCHER);
|
|
|
|
// Ignore any attempts to change video size while in UI mode
|
|
if(inUIMode && direction != 0)
|
|
return false;
|
|
|
|
// Only save mode changes in TIA mode with a valid selector
|
|
bool saveModeChange = !inUIMode && (direction == -1 || direction == +1);
|
|
|
|
if(direction == +1)
|
|
myCurrentModeList->next();
|
|
else if(direction == -1)
|
|
myCurrentModeList->previous();
|
|
|
|
VideoMode vidmode = myCurrentModeList->current(myOSystem->settings());
|
|
if(setVidMode(vidmode))
|
|
{
|
|
myImageRect.setWidth(vidmode.image_w);
|
|
myImageRect.setHeight(vidmode.image_h);
|
|
myImageRect.moveTo(vidmode.image_x, vidmode.image_y);
|
|
|
|
myScreenRect.setWidth(vidmode.screen_w);
|
|
myScreenRect.setHeight(vidmode.screen_h);
|
|
|
|
if(!inUIMode)
|
|
{
|
|
myOSystem->eventHandler().handleResizeEvent();
|
|
myOSystem->eventHandler().refreshDisplay(true);
|
|
setCursorState();
|
|
showMessage(vidmode.gfxmode.description);
|
|
}
|
|
if(saveModeChange)
|
|
myOSystem->settings().setString("tia_filter", vidmode.gfxmode.name);
|
|
}
|
|
else
|
|
return false;
|
|
|
|
return true;
|
|
/*
|
|
cerr << "New mode:" << endl
|
|
<< " screen w = " << newmode.screen_w << endl
|
|
<< " screen h = " << newmode.screen_h << endl
|
|
<< " image x = " << newmode.image_x << endl
|
|
<< " image y = " << newmode.image_y << endl
|
|
<< " image w = " << newmode.image_w << endl
|
|
<< " image h = " << newmode.image_h << endl
|
|
<< " zoom = " << newmode.zoom << endl
|
|
<< " name = " << newmode.name << endl
|
|
<< endl;
|
|
*/
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::setCursorState()
|
|
{
|
|
bool isFullscreen = myOSystem->settings().getBool("fullscreen");
|
|
|
|
if(isFullscreen)
|
|
grabMouse(true);
|
|
else
|
|
grabMouse(myOSystem->settings().getBool("grabmouse"));
|
|
|
|
switch(myOSystem->eventHandler().state())
|
|
{
|
|
case EventHandler::S_EMULATE:
|
|
case EventHandler::S_PAUSE:
|
|
showCursor(false);
|
|
break;
|
|
default:
|
|
showCursor(true);
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::showCursor(bool show)
|
|
{
|
|
SDL_ShowCursor(show ? SDL_ENABLE : SDL_DISABLE);
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::grabMouse(bool grab)
|
|
{
|
|
SDL_WM_GrabInput(grab ? SDL_GRAB_ON : SDL_GRAB_OFF);
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
bool FrameBuffer::fullScreen() const
|
|
{
|
|
#ifdef WINDOWED_SUPPORT
|
|
return myOSystem->settings().getBool("fullscreen");
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::setWindowTitle(const string& title)
|
|
{
|
|
SDL_WM_SetCaption(title.c_str(), "stella");
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::setWindowIcon()
|
|
{
|
|
#ifndef MAC_OSX
|
|
#include "stella.xpm" // The Stella icon
|
|
|
|
// Set the window icon
|
|
uInt32 w, h, ncols, nbytes;
|
|
uInt32 rgba[256], icon[32 * 32];
|
|
uInt8 mask[32][4];
|
|
|
|
sscanf(stella_icon[0], "%u %u %u %u", &w, &h, &ncols, &nbytes);
|
|
if((w != 32) || (h != 32) || (ncols > 255) || (nbytes > 1))
|
|
{
|
|
cerr << "ERROR: Couldn't load the icon.\n";
|
|
return;
|
|
}
|
|
|
|
for(uInt32 i = 0; i < ncols; i++)
|
|
{
|
|
unsigned char code;
|
|
char color[32];
|
|
uInt32 col;
|
|
|
|
sscanf(stella_icon[1 + i], "%c c %s", &code, color);
|
|
if(!strcmp(color, "None"))
|
|
col = 0x00000000;
|
|
else if(!strcmp(color, "black"))
|
|
col = 0xFF000000;
|
|
else if (color[0] == '#')
|
|
{
|
|
sscanf(color + 1, "%06x", &col);
|
|
col |= 0xFF000000;
|
|
}
|
|
else
|
|
{
|
|
cerr << "ERROR: Couldn't load the icon.\n";
|
|
return;
|
|
}
|
|
rgba[code] = col;
|
|
}
|
|
|
|
memset(mask, 0, sizeof(mask));
|
|
for(h = 0; h < 32; h++)
|
|
{
|
|
const char* line = stella_icon[1 + ncols + h];
|
|
for(w = 0; w < 32; w++)
|
|
{
|
|
icon[w + 32 * h] = rgba[(int)line[w]];
|
|
if(rgba[(int)line[w]] & 0xFF000000)
|
|
mask[h][w >> 3] |= 1 << (7 - (w & 0x07));
|
|
}
|
|
}
|
|
|
|
SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(icon, 32, 32, 32,
|
|
32 * 4, 0xFF0000, 0x00FF00, 0x0000FF, 0xFF000000);
|
|
SDL_WM_SetIcon(surface, (unsigned char *) mask);
|
|
SDL_FreeSurface(surface);
|
|
#endif
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
uInt8 FrameBuffer::getPhosphor(uInt8 c1, uInt8 c2)
|
|
{
|
|
if(c2 > c1)
|
|
BSPF_swap(c1, c2);
|
|
|
|
return ((c1 - c2) * myPhosphorBlend)/100 + c2;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
const StringMap& FrameBuffer::supportedTIAFilters(const string& type)
|
|
{
|
|
uInt32 max_zoom = maxWindowSizeForScreen(320, 210,
|
|
myOSystem->desktopWidth(), myOSystem->desktopHeight());
|
|
uInt8 mask = (type == "soft" ? 0x1 : 0x2);
|
|
|
|
#ifdef SMALL_SCREEN
|
|
uInt32 firstmode = 0;
|
|
#else
|
|
uInt32 firstmode = 1;
|
|
#endif
|
|
myTIAFilters.clear();
|
|
for(uInt32 i = firstmode; i < GFX_NumModes; ++i)
|
|
{
|
|
// For now, just include all filters
|
|
// This will change once OpenGL-only filters are added
|
|
if((ourGraphicsModes[i].avail & mask) && ourGraphicsModes[i].zoom <= max_zoom)
|
|
{
|
|
myTIAFilters.push_back(ourGraphicsModes[i].description,
|
|
ourGraphicsModes[i].name);
|
|
}
|
|
}
|
|
return myTIAFilters;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
uInt32 FrameBuffer::maxWindowSizeForScreen(uInt32 baseWidth, uInt32 baseHeight,
|
|
uInt32 screenWidth, uInt32 screenHeight)
|
|
{
|
|
uInt32 multiplier = 1;
|
|
for(;;)
|
|
{
|
|
// Figure out the zoomed size of the window
|
|
uInt32 width = baseWidth * multiplier;
|
|
uInt32 height = baseHeight * multiplier;
|
|
|
|
if((width > screenWidth) || (height > screenHeight))
|
|
break;
|
|
|
|
++multiplier;
|
|
}
|
|
return multiplier > 1 ? multiplier - 1 : 1;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::setAvailableVidModes(uInt32 baseWidth, uInt32 baseHeight)
|
|
{
|
|
// Modelists are different depending on what state we're in
|
|
EventHandler::State state = myOSystem->eventHandler().state();
|
|
bool inUIMode = (state == EventHandler::S_DEBUGGER ||
|
|
state == EventHandler::S_LAUNCHER);
|
|
|
|
myWindowedModeList.clear();
|
|
myFullscreenModeList.clear();
|
|
|
|
// In UI/windowed mode, there's only one valid video mode we can use
|
|
if(inUIMode)
|
|
{
|
|
VideoMode m;
|
|
m.image_x = m.image_y = 0;
|
|
m.image_w = m.screen_w = baseWidth;
|
|
m.image_h = m.screen_h = baseHeight;
|
|
m.gfxmode = ourGraphicsModes[0]; // this should be zoom1x
|
|
|
|
addVidMode(m);
|
|
}
|
|
else
|
|
{
|
|
// Scan list of filters, adding only those which are appropriate
|
|
// for the given dimensions
|
|
uInt32 max_zoom = maxWindowSizeForScreen(baseWidth, baseHeight,
|
|
myOSystem->desktopWidth(), myOSystem->desktopHeight());
|
|
#ifdef SMALL_SCREEN
|
|
uInt32 firstmode = 0;
|
|
#else
|
|
uInt32 firstmode = 1;
|
|
#endif
|
|
for(uInt32 i = firstmode; i < GFX_NumModes; ++i)
|
|
{
|
|
uInt32 zoom = ourGraphicsModes[i].zoom;
|
|
if(zoom <= max_zoom)
|
|
{
|
|
VideoMode m;
|
|
m.image_x = m.image_y = 0;
|
|
m.image_w = m.screen_w = baseWidth * zoom;
|
|
m.image_h = m.screen_h = baseHeight * zoom;
|
|
m.gfxmode = ourGraphicsModes[i];
|
|
|
|
addVidMode(m);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::addVidMode(VideoMode& mode)
|
|
{
|
|
// Windowed modes can be sized exactly as required, since there's normally
|
|
// no restriction on window size (up the maximum size)
|
|
myWindowedModeList.add(mode);
|
|
|
|
// There are often stricter requirements on fullscreen modes, and they're
|
|
// normally different depending on the OSystem in use
|
|
// As well, we usually can't get fullscreen modes in the exact size
|
|
// we want, so we need to calculate image offsets
|
|
const ResolutionList& res = myOSystem->supportedResolutions();
|
|
for(uInt32 i = 0; i < res.size(); ++i)
|
|
{
|
|
if(mode.screen_w <= res[i].width && mode.screen_h <= res[i].height)
|
|
{
|
|
// Auto-calculate 'smart' centering; platform-specific framebuffers are
|
|
// free to ignore or augment it
|
|
mode.screen_w = res[i].width;
|
|
mode.screen_h = res[i].height;
|
|
mode.image_x = (mode.screen_w - mode.image_w) >> 1;
|
|
mode.image_y = (mode.screen_h - mode.image_h) >> 1;
|
|
break;
|
|
}
|
|
}
|
|
myFullscreenModeList.add(mode);
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
FrameBuffer::VideoMode FrameBuffer::getSavedVidMode()
|
|
{
|
|
EventHandler::State state = myOSystem->eventHandler().state();
|
|
|
|
if(myOSystem->settings().getBool("fullscreen"))
|
|
myCurrentModeList = &myFullscreenModeList;
|
|
else
|
|
myCurrentModeList = &myWindowedModeList;
|
|
|
|
// Now select the best resolution depending on the state
|
|
// UI modes (launcher and debugger) have only one supported resolution
|
|
// so the 'current' one is the only valid one
|
|
if(state == EventHandler::S_DEBUGGER || state == EventHandler::S_LAUNCHER)
|
|
{
|
|
myCurrentModeList->setByGfxMode(GFX_Zoom1x);
|
|
}
|
|
else
|
|
{
|
|
const string& name = myOSystem->settings().getString("tia_filter");
|
|
myCurrentModeList->setByGfxMode(name);
|
|
}
|
|
|
|
return myCurrentModeList->current(myOSystem->settings());
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
FrameBuffer::VideoModeList::VideoModeList()
|
|
{
|
|
myIdx = -1;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
FrameBuffer::VideoModeList::~VideoModeList()
|
|
{
|
|
clear();
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::VideoModeList::add(VideoMode mode)
|
|
{
|
|
myModeList.push_back(mode);
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::VideoModeList::clear()
|
|
{
|
|
myModeList.clear();
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
bool FrameBuffer::VideoModeList::isEmpty() const
|
|
{
|
|
return myModeList.isEmpty();
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
uInt32 FrameBuffer::VideoModeList::size() const
|
|
{
|
|
return myModeList.size();
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::VideoModeList::previous()
|
|
{
|
|
--myIdx;
|
|
if(myIdx < 0) myIdx = myModeList.size() - 1;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
const FrameBuffer::VideoMode FrameBuffer::
|
|
VideoModeList::current(const Settings& settings) const
|
|
{
|
|
// Fullscreen modes are related to the 'fullres' setting
|
|
// If it's 'auto', we just use the mode as already previously defined
|
|
// If it's not 'auto', attempt to fit the mode into the resolution
|
|
// specified by 'fullres' (if possible)
|
|
if(settings.getBool("fullscreen") &&
|
|
BSPF_tolower(settings.getString("fullres")) != "auto")
|
|
{
|
|
// Only use 'fullres' if it's *bigger* than the requested mode
|
|
int w, h;
|
|
settings.getSize("fullres", w, h);
|
|
|
|
if(w != -1 && h != -1 && (uInt32)w > myModeList[myIdx].screen_w &&
|
|
(uInt32)h > myModeList[myIdx].screen_h)
|
|
{
|
|
VideoMode mode = myModeList[myIdx];
|
|
mode.screen_w = w;
|
|
mode.screen_h = h;
|
|
mode.image_x = (mode.screen_w - mode.image_w) >> 1;
|
|
mode.image_y = (mode.screen_h - mode.image_h) >> 1;
|
|
|
|
return mode;
|
|
}
|
|
}
|
|
|
|
// Otherwise, we just use the mode has it was defined in ::addVidMode()
|
|
return myModeList[myIdx];
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::VideoModeList::next()
|
|
{
|
|
myIdx = (myIdx + 1) % myModeList.size();
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::VideoModeList::setByGfxMode(GfxID id)
|
|
{
|
|
// First we determine which graphics mode is being requested
|
|
bool found = false;
|
|
GraphicsMode gfxmode;
|
|
for(uInt32 i = 0; i < GFX_NumModes; ++i)
|
|
{
|
|
if(ourGraphicsModes[i].type == id)
|
|
{
|
|
gfxmode = ourGraphicsModes[i];
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!found) gfxmode = ourGraphicsModes[0];
|
|
|
|
// Now we scan the list for the applicable video mode
|
|
set(gfxmode);
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::VideoModeList::setByGfxMode(const string& name)
|
|
{
|
|
// First we determine which graphics mode is being requested
|
|
bool found = false;
|
|
GraphicsMode gfxmode;
|
|
for(uInt32 i = 0; i < GFX_NumModes; ++i)
|
|
{
|
|
if(ourGraphicsModes[i].name == BSPF_tolower(name) ||
|
|
ourGraphicsModes[i].description == BSPF_tolower(name))
|
|
{
|
|
gfxmode = ourGraphicsModes[i];
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!found) gfxmode = ourGraphicsModes[0];
|
|
|
|
// Now we scan the list for the applicable video mode
|
|
set(gfxmode);
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::VideoModeList::set(const GraphicsMode& gfxmode)
|
|
{
|
|
// Attempt to point the current mode to the one given
|
|
myIdx = -1;
|
|
|
|
// First search for the given gfx id
|
|
for(unsigned int i = 0; i < myModeList.size(); ++i)
|
|
{
|
|
if(myModeList[i].gfxmode.type == gfxmode.type)
|
|
{
|
|
myIdx = i;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If we get here, then the gfx type couldn't be found, so we search
|
|
// for the first mode with the same zoomlevel
|
|
for(unsigned int i = 0; i < myModeList.size(); ++i)
|
|
{
|
|
if(myModeList[i].gfxmode.zoom == gfxmode.zoom)
|
|
{
|
|
myIdx = i;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Finally, just pick the lowes video mode
|
|
myIdx = 0;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::VideoModeList::print()
|
|
{
|
|
cerr << "VideoModeList: " << endl << endl;
|
|
for(Common::Array<VideoMode>::const_iterator i = myModeList.begin();
|
|
i != myModeList.end(); ++i)
|
|
{
|
|
cerr << " Mode " << i << endl
|
|
<< " screen w = " << i->screen_w << endl
|
|
<< " screen h = " << i->screen_h << endl
|
|
<< " image x = " << i->image_x << endl
|
|
<< " image y = " << i->image_y << endl
|
|
<< " image w = " << i->image_w << endl
|
|
<< " image h = " << i->image_h << endl
|
|
<< " gfx id = " << i->gfxmode.type << endl
|
|
<< " gfx name = " << i->gfxmode.name << endl
|
|
<< " gfx desc = " << i->gfxmode.description << endl
|
|
<< " gfx zoom = " << i->gfxmode.zoom << endl
|
|
<< endl;
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FBSurface::box(uInt32 x, uInt32 y, uInt32 w, uInt32 h,
|
|
int colorA, int colorB)
|
|
{
|
|
hLine(x + 1, y, x + w - 2, colorA);
|
|
hLine(x, y + 1, x + w - 1, colorA);
|
|
vLine(x, y + 1, y + h - 2, colorA);
|
|
vLine(x + 1, y, y + h - 1, colorA);
|
|
|
|
hLine(x + 1, y + h - 2, x + w - 1, colorB);
|
|
hLine(x + 1, y + h - 1, x + w - 2, colorB);
|
|
vLine(x + w - 1, y + 1, y + h - 2, colorB);
|
|
vLine(x + w - 2, y + 1, y + h - 1, colorB);
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FBSurface::frameRect(uInt32 x, uInt32 y, uInt32 w, uInt32 h,
|
|
int color, FrameStyle style)
|
|
{
|
|
switch(style)
|
|
{
|
|
case kSolidLine:
|
|
hLine(x, y, x + w - 1, color);
|
|
hLine(x, y + h - 1, x + w - 1, color);
|
|
vLine(x, y, y + h - 1, color);
|
|
vLine(x + w - 1, y, y + h - 1, color);
|
|
break;
|
|
|
|
case kDashLine:
|
|
unsigned int i, skip, lwidth = 1;
|
|
|
|
for(i = x, skip = 1; i < x+w-1; i=i+lwidth+1, ++skip)
|
|
{
|
|
if(skip % 2)
|
|
{
|
|
hLine(i, y, i + lwidth, color);
|
|
hLine(i, y + h - 1, i + lwidth, color);
|
|
}
|
|
}
|
|
for(i = y, skip = 1; i < y+h-1; i=i+lwidth+1, ++skip)
|
|
{
|
|
if(skip % 2)
|
|
{
|
|
vLine(x, i, i + lwidth, color);
|
|
vLine(x + w - 1, i, i + lwidth, color);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FBSurface::drawString(const GUI::Font* font, const string& s,
|
|
int x, int y, int w,
|
|
int color, TextAlignment align,
|
|
int deltax, bool useEllipsis)
|
|
{
|
|
const int leftX = x, rightX = x + w;
|
|
unsigned int i;
|
|
int width = font->getStringWidth(s);
|
|
string str;
|
|
|
|
if(useEllipsis && width > w)
|
|
{
|
|
// String is too wide. So we shorten it "intelligently", by replacing
|
|
// parts of it by an ellipsis ("..."). There are three possibilities
|
|
// for this: replace the start, the end, or the middle of the string.
|
|
// What is best really depends on the context; but unless we want to
|
|
// make this configurable, replacing the middle probably is a good
|
|
// compromise.
|
|
const int ellipsisWidth = font->getStringWidth("...");
|
|
|
|
// SLOW algorithm to remove enough of the middle. But it is good enough for now.
|
|
const int halfWidth = (w - ellipsisWidth) / 2;
|
|
int w2 = 0;
|
|
|
|
for(i = 0; i < s.size(); ++i)
|
|
{
|
|
int charWidth = font->getCharWidth(s[i]);
|
|
if(w2 + charWidth > halfWidth)
|
|
break;
|
|
|
|
w2 += charWidth;
|
|
str += s[i];
|
|
}
|
|
|
|
// At this point we know that the first 'i' chars are together 'w2'
|
|
// pixels wide. We took the first i-1, and add "..." to them.
|
|
str += "...";
|
|
|
|
// The original string is width wide. Of those we already skipped past
|
|
// w2 pixels, which means (width - w2) remain.
|
|
// The new str is (w2+ellipsisWidth) wide, so we can accomodate about
|
|
// (w - (w2+ellipsisWidth)) more pixels.
|
|
// Thus we skip ((width - w2) - (w - (w2+ellipsisWidth))) =
|
|
// (width + ellipsisWidth - w)
|
|
int skip = width + ellipsisWidth - w;
|
|
for(; i < s.size() && skip > 0; ++i)
|
|
skip -= font->getCharWidth(s[i]);
|
|
|
|
// Append the remaining chars, if any
|
|
for(; i < s.size(); ++i)
|
|
str += s[i];
|
|
|
|
width = font->getStringWidth(str);
|
|
}
|
|
else
|
|
str = s;
|
|
|
|
if(align == kTextAlignCenter)
|
|
x = x + (w - width - 1)/2;
|
|
else if(align == kTextAlignRight)
|
|
x = x + w - width;
|
|
|
|
x += deltax;
|
|
for(i = 0; i < str.size(); ++i)
|
|
{
|
|
w = font->getCharWidth(str[i]);
|
|
if(x+w > rightX)
|
|
break;
|
|
if(x >= leftX)
|
|
drawChar(font, str[i], x, y, color);
|
|
|
|
x += w;
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
FrameBuffer::GraphicsMode FrameBuffer::ourGraphicsModes[GFX_NumModes] = {
|
|
{ GFX_Zoom1x, "zoom1x", "Zoom 1x", 1, 0x3 },
|
|
{ GFX_Zoom2x, "zoom2x", "Zoom 2x", 2, 0x3 },
|
|
{ GFX_Zoom3x, "zoom3x", "Zoom 3x", 3, 0x3 },
|
|
{ GFX_Zoom4x, "zoom4x", "Zoom 4x", 4, 0x3 },
|
|
{ GFX_Zoom5x, "zoom5x", "Zoom 5x", 5, 0x3 },
|
|
{ GFX_Zoom6x, "zoom6x", "Zoom 6x", 6, 0x3 },
|
|
{ GFX_Zoom7x, "zoom7x", "Zoom 7x", 7, 0x3 },
|
|
{ GFX_Zoom8x, "zoom8x", "Zoom 8x", 8, 0x3 },
|
|
{ GFX_Zoom9x, "zoom9x", "Zoom 9x", 9, 0x3 },
|
|
{ GFX_Zoom10x, "zoom10x", "Zoom 10x", 10, 0x3 }
|
|
};
|