/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include "common/scummsys.h"

#if defined(USE_OPENGL)

#include "backends/graphics/opengl/opengl-graphics.h"
#include "backends/graphics/opengl/glerrorcheck.h"
#include "common/config-manager.h"
#include "common/file.h"
#include "common/mutex.h"
#include "common/textconsole.h"
#include "common/translation.h"
#ifdef USE_OSD
#include "common/tokenizer.h"
#endif
#include "graphics/font.h"
#include "graphics/fontman.h"

OpenGLGraphicsManager::OpenGLGraphicsManager()
	:
#ifdef USE_OSD
	_osdTexture(0), _osdAlpha(0), _osdFadeStartTime(0), _requireOSDUpdate(false),
#endif
	_gameTexture(0), _overlayTexture(0), _cursorTexture(0),
	_screenChangeCount(1 << (sizeof(int) * 8 - 2)), _screenNeedsRedraw(false),
	_shakePos(0),
	_overlayVisible(false), _overlayNeedsRedraw(false),
	_transactionMode(kTransactionNone),
	_cursorNeedsRedraw(false), _cursorPaletteDisabled(true),
	_cursorVisible(false), _cursorKeyColor(0),
	_cursorTargetScale(1),
	_formatBGR(false),
	_displayX(0), _displayY(0), _displayWidth(0), _displayHeight(0) {

	memset(&_oldVideoMode, 0, sizeof(_oldVideoMode));
	memset(&_videoMode, 0, sizeof(_videoMode));
	memset(&_transactionDetails, 0, sizeof(_transactionDetails));

	_videoMode.mode = OpenGL::GFX_NORMAL;
	_videoMode.scaleFactor = 2;
	_videoMode.fullscreen = ConfMan.getBool("fullscreen");
	_videoMode.antialiasing = false;

	_gamePalette = (byte *)calloc(sizeof(byte) * 3, 256);
	_cursorPalette = (byte *)calloc(sizeof(byte) * 3, 256);
}

OpenGLGraphicsManager::~OpenGLGraphicsManager() {
	free(_gamePalette);
	free(_cursorPalette);

	delete _gameTexture;
	delete _overlayTexture;
	delete _cursorTexture;
}

//
// Feature
//

bool OpenGLGraphicsManager::hasFeature(OSystem::Feature f) {
	return
		(f == OSystem::kFeatureAspectRatioCorrection) ||
		(f == OSystem::kFeatureCursorPalette);
}

void OpenGLGraphicsManager::setFeatureState(OSystem::Feature f, bool enable) {
	switch (f) {
	case OSystem::kFeatureFullscreenMode:
		setFullscreenMode(enable);
		break;

	case OSystem::kFeatureAspectRatioCorrection:
		_videoMode.aspectRatioCorrection = enable;
		_transactionDetails.needRefresh = true;
		break;

	case OSystem::kFeatureCursorPalette:
		_cursorPaletteDisabled = !enable;
		_cursorNeedsRedraw = true;
		break;

	default:
		break;
	}
}

bool OpenGLGraphicsManager::getFeatureState(OSystem::Feature f) {
	switch (f) {
	case OSystem::kFeatureFullscreenMode:
		return _videoMode.fullscreen;

	case OSystem::kFeatureAspectRatioCorrection:
		return _videoMode.aspectRatioCorrection;

	case OSystem::kFeatureCursorPalette:
		return !_cursorPaletteDisabled;

	default:
		return false;
	}
}

//
// Screen format and modes
//

static const OSystem::GraphicsMode s_supportedGraphicsModes[] = {
	{"gl1", _s("OpenGL Normal"), OpenGL::GFX_NORMAL},
	{"gl2", _s("OpenGL Conserve"), OpenGL::GFX_CONSERVE},
	{"gl4", _s("OpenGL Original"), OpenGL::GFX_ORIGINAL},
	{0, 0, 0}
};

const OSystem::GraphicsMode *OpenGLGraphicsManager::supportedGraphicsModes() {
	return s_supportedGraphicsModes;
}

const OSystem::GraphicsMode *OpenGLGraphicsManager::getSupportedGraphicsModes() const {
	return s_supportedGraphicsModes;
}

int OpenGLGraphicsManager::getDefaultGraphicsMode() const {
	return OpenGL::GFX_NORMAL;
}

bool OpenGLGraphicsManager::setGraphicsMode(int mode) {
	assert(_transactionMode == kTransactionActive);

	setScale(2);

	if (_oldVideoMode.setup && _oldVideoMode.mode == mode)
		return true;

	switch (mode) {
	case OpenGL::GFX_NORMAL:
	case OpenGL::GFX_CONSERVE:
	case OpenGL::GFX_ORIGINAL:
		break;
	default:
		warning("Unknown gfx mode %d", mode);
		return false;
	}

	_videoMode.mode = mode;
	_transactionDetails.needRefresh = true;

	return true;
}

int OpenGLGraphicsManager::getGraphicsMode() const {
	assert(_transactionMode == kTransactionNone);
	return _videoMode.mode;
}

void OpenGLGraphicsManager::resetGraphicsScale() {
	setScale(1);
}

#ifdef USE_RGB_COLOR
Graphics::PixelFormat OpenGLGraphicsManager::getScreenFormat() const {
	return _screenFormat;
}
#endif

void OpenGLGraphicsManager::initSize(uint width, uint height, const Graphics::PixelFormat *format) {
	assert(_transactionMode == kTransactionActive);

#ifdef USE_RGB_COLOR
	Graphics::PixelFormat newFormat;
	if (!format)
		newFormat = Graphics::PixelFormat::createFormatCLUT8();
	else
		newFormat = *format;

	assert(newFormat.bytesPerPixel > 0);

	// Avoid redundant format changes
	if (newFormat != _videoMode.format) {
		_videoMode.format = newFormat;
		_transactionDetails.formatChanged = true;
		_screenFormat = newFormat;
	}
#endif

	// Avoid redundant res changes
	if ((int)width == _videoMode.screenWidth && (int)height == _videoMode.screenHeight)
		return;

	_videoMode.screenWidth = width;
	_videoMode.screenHeight = height;

	_transactionDetails.sizeChanged = true;
}

int OpenGLGraphicsManager::getScreenChangeID() const {
	return _screenChangeCount;
}

//
// GFX
//

void OpenGLGraphicsManager::beginGFXTransaction() {
	assert(_transactionMode == kTransactionNone);

	_transactionMode = kTransactionActive;
	_transactionDetails.sizeChanged = false;
	_transactionDetails.needRefresh = false;
	_transactionDetails.needUpdatescreen = false;
	_transactionDetails.filterChanged = false;
#ifdef USE_RGB_COLOR
	_transactionDetails.formatChanged = false;
#endif

	_oldVideoMode = _videoMode;
}

OSystem::TransactionError OpenGLGraphicsManager::endGFXTransaction() {
	int errors = OSystem::kTransactionSuccess;

	assert(_transactionMode != kTransactionNone);

	if (_transactionMode == kTransactionRollback) {
		if (_videoMode.fullscreen != _oldVideoMode.fullscreen) {
			errors |= OSystem::kTransactionFullscreenFailed;

			_videoMode.fullscreen = _oldVideoMode.fullscreen;
		} else if (_videoMode.mode != _oldVideoMode.mode) {
			errors |= OSystem::kTransactionModeSwitchFailed;

			_videoMode.mode = _oldVideoMode.mode;
			_videoMode.scaleFactor = _oldVideoMode.scaleFactor;
#ifdef USE_RGB_COLOR
		} else if (_videoMode.format != _oldVideoMode.format) {
			errors |= OSystem::kTransactionFormatNotSupported;

			_videoMode.format = _oldVideoMode.format;
			_screenFormat = _videoMode.format;
#endif
		} else if (_videoMode.screenWidth != _oldVideoMode.screenWidth || _videoMode.screenHeight != _oldVideoMode.screenHeight) {
			errors |= OSystem::kTransactionSizeChangeFailed;

			_videoMode.screenWidth = _oldVideoMode.screenWidth;
			_videoMode.screenHeight = _oldVideoMode.screenHeight;
			_videoMode.overlayWidth = _oldVideoMode.overlayWidth;
			_videoMode.overlayHeight = _oldVideoMode.overlayHeight;
		}

		if (_videoMode.fullscreen == _oldVideoMode.fullscreen &&
			_videoMode.mode == _oldVideoMode.mode &&
			_videoMode.screenWidth == _oldVideoMode.screenWidth &&
			_videoMode.screenHeight == _oldVideoMode.screenHeight) {

			_oldVideoMode.setup = false;
		}
	}

	if (_transactionDetails.sizeChanged || _transactionDetails.needRefresh) {
		unloadGFXMode();
		if (!loadGFXMode()) {
			if (_oldVideoMode.setup) {
				_transactionMode = kTransactionRollback;
				errors |= endGFXTransaction();
			}
		} else {
			clearOverlay();

			_videoMode.setup = true;
			_screenChangeCount++;
		}
#ifdef USE_RGB_COLOR
	} else if (_transactionDetails.filterChanged || _transactionDetails.formatChanged) {
#else
	} else if (_transactionDetails.filterChanged) {
#endif
		loadTextures();
		internUpdateScreen();
	} else if (_transactionDetails.needUpdatescreen) {
		internUpdateScreen();
	}

	_transactionMode = kTransactionNone;
	return (OSystem::TransactionError)errors;
}

//
// Screen
//

int16 OpenGLGraphicsManager::getHeight() {
	return _videoMode.screenHeight;
}

int16 OpenGLGraphicsManager::getWidth() {
	return _videoMode.screenWidth;
}

void OpenGLGraphicsManager::setPalette(const byte *colors, uint start, uint num) {
	assert(colors);

#ifdef USE_RGB_COLOR
	assert(_screenFormat.bytesPerPixel == 1);
#endif

	// Save the screen palette
	memcpy(_gamePalette + start * 3, colors, num * 3);

	_screenNeedsRedraw = true;

	if (_cursorPaletteDisabled)
		_cursorNeedsRedraw = true;
}

void OpenGLGraphicsManager::grabPalette(byte *colors, uint start, uint num) {
	assert(colors);

#ifdef USE_RGB_COLOR
	assert(_screenFormat.bytesPerPixel == 1);
#endif

	// Copies current palette to buffer
	memcpy(colors, _gamePalette + start * 3, num * 3);
}

void OpenGLGraphicsManager::copyRectToScreen(const byte *buf, int pitch, int x, int y, int w, int h) {
	assert(x >= 0 && x < _screenData.w);
	assert(y >= 0 && y < _screenData.h);
	assert(h > 0 && y + h <= _screenData.h);
	assert(w > 0 && x + w <= _screenData.w);

	// Copy buffer data to game screen internal buffer
	const byte *src = buf;
	byte *dst = (byte *)_screenData.pixels + y * _screenData.pitch + x * _screenData.format.bytesPerPixel;
	for (int i = 0; i < h; i++) {
		memcpy(dst, src, w * _screenData.format.bytesPerPixel);
		src += pitch;
		dst += _screenData.pitch;
	}

	// Extend dirty area if not full screen redraw is flagged
	if (!_screenNeedsRedraw) {
		const Common::Rect dirtyRect(x, y, x + w, y + h);
		_screenDirtyRect.extend(dirtyRect);
	}
}

Graphics::Surface *OpenGLGraphicsManager::lockScreen() {
	return &_screenData;
}

void OpenGLGraphicsManager::unlockScreen() {
	_screenNeedsRedraw = true;
}

void OpenGLGraphicsManager::fillScreen(uint32 col) {
	if (_gameTexture == NULL)
		return;

#ifdef USE_RGB_COLOR
	if (_screenFormat.bytesPerPixel == 1) {
		memset(_screenData.pixels, col, _screenData.h * _screenData.pitch);
	} else if (_screenFormat.bytesPerPixel == 2) {
		uint16 *pixels = (uint16 *)_screenData.pixels;
		uint16 col16 = (uint16)col;
		for (int i = 0; i < _screenData.w * _screenData.h; i++) {
			pixels[i] = col16;
		}
	} else if (_screenFormat.bytesPerPixel == 3) {
		uint8 *pixels = (uint8 *)_screenData.pixels;
		byte r = (col >> 16) & 0xFF;
		byte g = (col >> 8) & 0xFF;
		byte b = col & 0xFF;
		for (int i = 0; i < _screenData.w * _screenData.h; i++) {
			pixels[0] = r;
			pixels[1] = g;
			pixels[2] = b;
			pixels += 3;
		}
	} else if (_screenFormat.bytesPerPixel == 4) {
		uint32 *pixels = (uint32 *)_screenData.pixels;
		for (int i = 0; i < _screenData.w * _screenData.h; i++) {
			pixels[i] = col;
		}
	}
#else
	memset(_screenData.pixels, col, _screenData.h * _screenData.pitch);
#endif
	_screenNeedsRedraw = true;
}

void OpenGLGraphicsManager::updateScreen() {
	assert(_transactionMode == kTransactionNone);
	internUpdateScreen();
}

void OpenGLGraphicsManager::setShakePos(int shakeOffset) {
	assert(_transactionMode == kTransactionNone);
	_shakePos = shakeOffset;
}

void OpenGLGraphicsManager::setFocusRectangle(const Common::Rect& rect) {
}

void OpenGLGraphicsManager::clearFocusRectangle() {
}

//
// Overlay
//

void OpenGLGraphicsManager::showOverlay() {
	assert(_transactionMode == kTransactionNone);

	if (_overlayVisible)
		return;

	_overlayVisible = true;

	clearOverlay();
}

void OpenGLGraphicsManager::hideOverlay() {
	assert(_transactionMode == kTransactionNone);

	if (!_overlayVisible)
		return;

	_overlayVisible = false;

	clearOverlay();
}

Graphics::PixelFormat OpenGLGraphicsManager::getOverlayFormat() const {
	return _overlayFormat;
}

void OpenGLGraphicsManager::clearOverlay() {
	// Set all pixels to 0
	memset(_overlayData.pixels, 0, _overlayData.h * _overlayData.pitch);
	_overlayNeedsRedraw = true;
}

void OpenGLGraphicsManager::grabOverlay(OverlayColor *buf, int pitch) {
	assert(_overlayData.format.bytesPerPixel == sizeof(buf[0]));
	const byte *src = (byte *)_overlayData.pixels;
	for (int i = 0; i < _overlayData.h; i++) {
		// Copy overlay data to buffer
		memcpy(buf, src, _overlayData.pitch);
		buf += pitch;
		src += _overlayData.pitch;
	}
}

void OpenGLGraphicsManager::copyRectToOverlay(const OverlayColor *buf, int pitch, int x, int y, int w, int h) {
	assert(_transactionMode == kTransactionNone);

	if (_overlayTexture == NULL)
		return;

	// Clip the coordinates
	if (x < 0) {
		w += x;
		buf -= x;
		x = 0;
	}

	if (y < 0) {
		h += y; buf -= y * pitch;
		y = 0;
	}

	if (w > _overlayData.w - x)
		w = _overlayData.w - x;

	if (h > _overlayData.h - y)
		h = _overlayData.h - y;

	if (w <= 0 || h <= 0)
		return;

	if (_overlayFormat.aBits() == 1) {
		// Copy buffer with the alpha bit on for all pixels for correct
		// overlay drawing.
		const uint16 *src = (const uint16 *)buf;
		uint16 *dst = (uint16 *)_overlayData.pixels + y * _overlayData.w + x;
		for (int i = 0; i < h; i++) {
			for (int e = 0; e < w; e++)
				dst[e] = src[e] | 0x1;
			src += pitch;
			dst += _overlayData.w;
		}
	} else {
		// Copy buffer data to internal overlay surface
		const byte *src = (const byte *)buf;
		byte *dst = (byte *)_overlayData.pixels + y * _overlayData.pitch;
		for (int i = 0; i < h; i++) {
			memcpy(dst + x * _overlayData.format.bytesPerPixel, src, w * _overlayData.format.bytesPerPixel);
			src += pitch * sizeof(buf[0]);
			dst += _overlayData.pitch;
		}
	}

	// Extend dirty area if not full screen redraw is flagged
	if (!_overlayNeedsRedraw) {
		const Common::Rect dirtyRect(x, y, x + w, y + h);
		_overlayDirtyRect.extend(dirtyRect);
	}
}

int16 OpenGLGraphicsManager::getOverlayHeight() {
	return _videoMode.overlayHeight;
}

int16 OpenGLGraphicsManager::getOverlayWidth() {
	return _videoMode.overlayWidth;
}

//
// Cursor
//

bool OpenGLGraphicsManager::showMouse(bool visible) {
	if (_cursorVisible == visible)
		return visible;

	bool last = _cursorVisible;
	_cursorVisible = visible;

	return last;
}

void OpenGLGraphicsManager::warpMouse(int x, int y) {
	int scaledX = x;
	int scaledY = y;

	int16 currentX = _cursorState.x;
	int16 currentY = _cursorState.y;

	adjustMousePosition(currentX, currentY);

	// Do not adjust the real screen position, when the current game / overlay
	// coordinates match the requested coordinates. This avoids a slight
	// movement which might occur otherwise when the mouse is at a subpixel
	// position.
	if (x == currentX && y == currentY)
		return;

	if (_videoMode.mode == OpenGL::GFX_NORMAL) {
		if (_videoMode.hardwareWidth != _videoMode.overlayWidth)
			scaledX = scaledX * _videoMode.hardwareWidth / _videoMode.overlayWidth;
		if (_videoMode.hardwareHeight != _videoMode.overlayHeight)
			scaledY = scaledY * _videoMode.hardwareHeight / _videoMode.overlayHeight;

		if (!_overlayVisible) {
			scaledX *= _videoMode.scaleFactor;
			scaledY *= _videoMode.scaleFactor;
		}
	} else {
		if (_overlayVisible) {
			if (_displayWidth != _videoMode.overlayWidth)
				scaledX = scaledX * _displayWidth / _videoMode.overlayWidth;
			if (_displayHeight != _videoMode.overlayHeight)
				scaledY = scaledY * _displayHeight / _videoMode.overlayHeight;
		} else {
			if (_displayWidth != _videoMode.screenWidth)
				scaledX = scaledX * _displayWidth / _videoMode.screenWidth;
			if (_displayHeight != _videoMode.screenHeight)
				scaledY = scaledY * _displayHeight / _videoMode.screenHeight;
		}

		scaledX += _displayX;
		scaledY += _displayY;
	}

	setInternalMousePosition(scaledX, scaledY);

	_cursorState.x = scaledX;
	_cursorState.y = scaledY;
}

void OpenGLGraphicsManager::setMouseCursor(const byte *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, int cursorTargetScale, const Graphics::PixelFormat *format) {
#ifdef USE_RGB_COLOR
	if (format)
		_cursorFormat = *format;
	else
		_cursorFormat = Graphics::PixelFormat::createFormatCLUT8();
#else
	assert(keycolor <= 255);
	_cursorFormat = Graphics::PixelFormat::createFormatCLUT8();
#endif

	// Allocate space for cursor data
	if (_cursorData.w != w || _cursorData.h != h ||
			_cursorData.format.bytesPerPixel != _cursorFormat.bytesPerPixel)
		_cursorData.create(w, h, _cursorFormat);

	// Save cursor data
	memcpy(_cursorData.pixels, buf, h * _cursorData.pitch);

	// Set cursor info
	_cursorState.w = w;
	_cursorState.h = h;
	_cursorState.hotX = hotspotX;
	_cursorState.hotY = hotspotY;
	_cursorKeyColor = keycolor;
	_cursorTargetScale = cursorTargetScale;
	_cursorNeedsRedraw = true;

	refreshCursorScale();
}

void OpenGLGraphicsManager::setCursorPalette(const byte *colors, uint start, uint num) {
	assert(colors);

	// Save the cursor palette
	memcpy(_cursorPalette + start * 3, colors, num * 3);

	_cursorPaletteDisabled = false;
	_cursorNeedsRedraw = true;
}

//
// Misc
//

void OpenGLGraphicsManager::displayMessageOnOSD(const char *msg) {
	assert(_transactionMode == kTransactionNone);
	assert(msg);

#ifdef USE_OSD
	// Split the message into separate lines.
	_osdLines.clear();

	Common::StringTokenizer tokenizer(msg, "\n");
	while (!tokenizer.empty())
		_osdLines.push_back(tokenizer.nextToken());

	// Request update of the texture
	_requireOSDUpdate = true;

	// Init the OSD display parameters, and the fade out
	_osdAlpha = kOSDInitialAlpha;
	_osdFadeStartTime = g_system->getMillis() + kOSDFadeOutDelay;
#endif
}

//
// Intern
//

void OpenGLGraphicsManager::setFullscreenMode(bool enable) {
	assert(_transactionMode == kTransactionActive);

	if (_oldVideoMode.setup && _oldVideoMode.fullscreen == enable)
		return;

	if (_transactionMode == kTransactionActive) {
		_videoMode.fullscreen = enable;
		_transactionDetails.needRefresh = true;
	}
}

void OpenGLGraphicsManager::refreshGameScreen() {
	if (_screenNeedsRedraw)
		_screenDirtyRect = Common::Rect(0, 0, _screenData.w, _screenData.h);

	int x = _screenDirtyRect.left;
	int y = _screenDirtyRect.top;
	int w = _screenDirtyRect.width();
	int h = _screenDirtyRect.height();

	if (_screenData.format.bytesPerPixel == 1) {
		// Create a temporary RGB888 surface
		byte *surface = new byte[w * h * 3];

		// Convert the paletted buffer to RGB888
		const byte *src = (byte *)_screenData.pixels + y * _screenData.pitch;
		src += x * _screenData.format.bytesPerPixel;
		byte *dst = surface;
		for (int i = 0; i < h; i++) {
			for (int j = 0; j < w; j++) {
				dst[0] = _gamePalette[src[j] * 3];
				dst[1] = _gamePalette[src[j] * 3 + 1];
				dst[2] = _gamePalette[src[j] * 3 + 2];
				dst += 3;
			}
			src += _screenData.pitch;
		}

		// Update the texture
		_gameTexture->updateBuffer(surface, w * 3, x, y, w, h);

		// Free the temp surface
		delete[] surface;
	} else {
		// Update the texture
		_gameTexture->updateBuffer((byte *)_screenData.pixels + y * _screenData.pitch +
			x * _screenData.format.bytesPerPixel, _screenData.pitch, x, y, w, h);
	}

	_screenNeedsRedraw = false;
	_screenDirtyRect = Common::Rect();
}

void OpenGLGraphicsManager::refreshOverlay() {
	if (_overlayNeedsRedraw)
		_overlayDirtyRect = Common::Rect(0, 0, _overlayData.w, _overlayData.h);

	int x = _overlayDirtyRect.left;
	int y = _overlayDirtyRect.top;
	int w = _overlayDirtyRect.width();
	int h = _overlayDirtyRect.height();

	if (_overlayData.format.bytesPerPixel == 1) {
		// Create a temporary RGB888 surface
		byte *surface = new byte[w * h * 3];

		// Convert the paletted buffer to RGB888
		const byte *src = (byte *)_overlayData.pixels + y * _overlayData.pitch;
		src += x * _overlayData.format.bytesPerPixel;
		byte *dst = surface;
		for (int i = 0; i < h; i++) {
			for (int j = 0; j < w; j++) {
				dst[0] = _gamePalette[src[j] * 3];
				dst[1] = _gamePalette[src[j] * 3 + 1];
				dst[2] = _gamePalette[src[j] * 3 + 2];
				dst += 3;
			}
			src += _screenData.pitch;
		}

		// Update the texture
		_overlayTexture->updateBuffer(surface, w * 3, x, y, w, h);

		// Free the temp surface
		delete[] surface;
	} else {
		// Update the texture
		_overlayTexture->updateBuffer((byte *)_overlayData.pixels + y * _overlayData.pitch +
			x * _overlayData.format.bytesPerPixel, _overlayData.pitch, x, y, w, h);
	}

	_overlayNeedsRedraw = false;
	_overlayDirtyRect = Common::Rect();
}

void OpenGLGraphicsManager::refreshCursor() {
	_cursorNeedsRedraw = false;

	// Allocate a texture big enough for cursor
	_cursorTexture->allocBuffer(_cursorState.w, _cursorState.h);

	// Create a temporary RGBA8888 surface
	byte *surface = new byte[_cursorState.w * _cursorState.h * 4];
	memset(surface, 0, _cursorState.w * _cursorState.h * 4);

	byte *dst = surface;

	// Convert the paletted cursor to RGBA8888
	if (_cursorFormat.bytesPerPixel == 1) {
		// Select palette
		byte *palette;
		if (_cursorPaletteDisabled)
			palette = _gamePalette;
		else
			palette = _cursorPalette;

		// Convert the paletted cursor to RGBA8888
		const byte *src = (byte *)_cursorData.pixels;
		for (int i = 0; i < _cursorState.w * _cursorState.h; i++) {
			// Check for keycolor
			if (src[i] != _cursorKeyColor) {
				dst[0] = palette[src[i] * 3];
				dst[1] = palette[src[i] * 3 + 1];
				dst[2] = palette[src[i] * 3 + 2];
				dst[3] = 255;
			}
			dst += 4;
		}
	} else {
		const bool gotNoAlpha = (_cursorFormat.aLoss == 8);

		// Convert the RGB cursor to RGBA8888
		if (_cursorFormat.bytesPerPixel == 2) {
			const uint16 *src = (uint16 *)_cursorData.pixels;
			for (int i = 0; i < _cursorState.w * _cursorState.h; i++) {
				// Check for keycolor
				if (src[i] != _cursorKeyColor) {
					_cursorFormat.colorToARGB(src[i], dst[3], dst[0], dst[1], dst[2]);

					if (gotNoAlpha)
						dst[3] = 255;
				}
				dst += 4;
			}
		} else if (_cursorFormat.bytesPerPixel == 4) {
			const uint32 *src = (uint32 *)_cursorData.pixels;
			for (int i = 0; i < _cursorState.w * _cursorState.h; i++) {
				// Check for keycolor
				if (src[i] != _cursorKeyColor) {
					_cursorFormat.colorToARGB(src[i], dst[3], dst[0], dst[1], dst[2]);

					if (gotNoAlpha)
						dst[3] = 255;
				}
				dst += 4;
			}
		}
	}

	// Update the texture with new cursor
	_cursorTexture->updateBuffer(surface, _cursorState.w * 4, 0, 0, _cursorState.w, _cursorState.h);

	// Free the temp surface
	delete[] surface;
}

void OpenGLGraphicsManager::refreshCursorScale() {
	// Get the window minimum scale factor. The cursor will mantain its original aspect
	// ratio, and we do not want it to get too big if only one dimension is resized
	uint screenScaleFactor = MIN(_videoMode.hardwareWidth * 10000 / _videoMode.screenWidth,
		_videoMode.hardwareHeight * 10000 / _videoMode.screenHeight);

	// Do not scale cursor if original size is used
	if (_videoMode.mode == OpenGL::GFX_ORIGINAL)
		screenScaleFactor = _videoMode.scaleFactor * 10000;

	if ((uint)_cursorTargetScale * 10000 >= screenScaleFactor && (uint)_videoMode.scaleFactor * 10000 >= screenScaleFactor) {
		// If the cursor target scale and the video mode scale factor are bigger than
		// the current window scale, do not scale the cursor for the overlay
		_cursorState.rW = _cursorState.w;
		_cursorState.rH = _cursorState.h;
		_cursorState.rHotX = _cursorState.hotX;
		_cursorState.rHotY = _cursorState.hotY;
	} else {
		// Otherwise, scale the cursor for the overlay
		int targetScaleFactor = MIN(_cursorTargetScale, _videoMode.scaleFactor);
		// We limit the maximum scale to 3 here to avoid too big cursors, for large overlay resolutions
		int actualFactor = MIN<uint>(3, screenScaleFactor - (targetScaleFactor - 1)) * 10000;
		_cursorState.rW = (int16)(_cursorState.w * actualFactor / 10000);
		_cursorState.rH = (int16)(_cursorState.h * actualFactor / 10000);
		_cursorState.rHotX = (int16)(_cursorState.hotX * actualFactor / 10000);
		_cursorState.rHotY = (int16)(_cursorState.hotY * actualFactor / 10000);
	}

	// Always scale the cursor for the game
	_cursorState.vW = (int16)(_cursorState.w * screenScaleFactor / 10000);
	_cursorState.vH = (int16)(_cursorState.h * screenScaleFactor / 10000);
	_cursorState.vHotX = (int16)(_cursorState.hotX * screenScaleFactor / 10000);
	_cursorState.vHotY = (int16)(_cursorState.hotY * screenScaleFactor / 10000);
}

void OpenGLGraphicsManager::calculateDisplaySize(int &width, int &height) {
	if (_videoMode.mode == OpenGL::GFX_ORIGINAL) {
		width = _videoMode.screenWidth;
		height = _videoMode.screenHeight;
	} else {
		width = _videoMode.hardwareWidth;
		height = _videoMode.hardwareHeight;

		uint aspectRatio = (_videoMode.hardwareWidth * 10000 + 5000) / _videoMode.hardwareHeight;
		uint desiredAspectRatio = getAspectRatio();

		// Adjust one screen dimension for mantaining the aspect ratio
		if (aspectRatio < desiredAspectRatio)
			height = (width * 10000 + 5000) / desiredAspectRatio;
		else if (aspectRatio > desiredAspectRatio)
			width = (height * desiredAspectRatio + 5000) / 10000;
	}
}

void OpenGLGraphicsManager::refreshDisplaySize() {
	calculateDisplaySize(_displayWidth, _displayHeight);

	// Adjust x and y for centering the screen
	_displayX = (_videoMode.hardwareWidth - _displayWidth) / 2;
	_displayY = (_videoMode.hardwareHeight - _displayHeight) / 2;
}

void OpenGLGraphicsManager::getGLPixelFormat(Graphics::PixelFormat pixelFormat, byte &bpp, GLenum &intFormat, GLenum &glFormat, GLenum &gltype) {
	if (pixelFormat == Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0)) { // RGBA8888
		bpp = 4;
		intFormat = GL_RGBA;
		glFormat = GL_RGBA;
		gltype = GL_UNSIGNED_BYTE;
	} else if (pixelFormat == Graphics::PixelFormat(3, 8, 8, 8, 0, 16, 8, 0, 0)) { // RGB888
		bpp = 3;
		intFormat = GL_RGB;
		glFormat = GL_RGB;
		gltype = GL_UNSIGNED_BYTE;
	} else if (pixelFormat == Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0)) { // RGB565
		bpp = 2;
		intFormat = GL_RGB;
		glFormat = GL_RGB;
		gltype = GL_UNSIGNED_SHORT_5_6_5;
	} else if (pixelFormat == Graphics::PixelFormat(2, 5, 5, 5, 1, 11, 6, 1, 0)) { // RGB5551
		bpp = 2;
		intFormat = GL_RGBA;
		glFormat = GL_RGBA;
		gltype = GL_UNSIGNED_SHORT_5_5_5_1;
	} else if (pixelFormat == Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0)) { // RGB555
		bpp = 2;
		intFormat = GL_RGB;
		glFormat = GL_BGRA;
		gltype = GL_UNSIGNED_SHORT_1_5_5_5_REV;
	} else if (pixelFormat == Graphics::PixelFormat(2, 4, 4, 4, 4, 12, 8, 4, 0)) { // RGBA4444
		bpp = 2;
		intFormat = GL_RGBA;
		glFormat = GL_RGBA;
		gltype = GL_UNSIGNED_SHORT_4_4_4_4;
	} else if (pixelFormat.bytesPerPixel == 1) { // CLUT8
		// If uses a palette, create texture as RGB888. The pixel data will be converted
		// later.
		bpp = 3;
		intFormat = GL_RGB;
		glFormat = GL_RGB;
		gltype = GL_UNSIGNED_BYTE;
#ifndef USE_GLES
	} else if (pixelFormat == Graphics::PixelFormat(4, 8, 8, 8, 8, 16, 8, 0, 24)) { // ARGB8888
		bpp = 4;
		intFormat = GL_RGBA;
		glFormat = GL_BGRA;
		gltype = GL_UNSIGNED_INT_8_8_8_8_REV;
	} else if (pixelFormat == Graphics::PixelFormat(2, 4, 4, 4, 4, 8, 4, 0, 12)) { // ARGB4444
		bpp = 2;
		intFormat = GL_RGBA;
		glFormat = GL_BGRA;
		gltype = GL_UNSIGNED_SHORT_4_4_4_4_REV;
	} else if (pixelFormat == Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24)) { // ABGR8888
		bpp = 4;
		intFormat = GL_RGBA;
		glFormat = GL_RGBA;
		gltype = GL_UNSIGNED_INT_8_8_8_8_REV;
	} else if (pixelFormat == Graphics::PixelFormat(4, 8, 8, 8, 8, 8, 16, 24, 0)) { // BGRA8888
		bpp = 4;
		intFormat = GL_RGBA;
		glFormat = GL_BGRA;
		gltype = GL_UNSIGNED_BYTE;
	} else if (pixelFormat == Graphics::PixelFormat(3, 8, 8, 8, 0, 0, 8, 16, 0)) { // BGR888
		bpp = 3;
		intFormat = GL_RGB;
		glFormat = GL_BGR;
		gltype = GL_UNSIGNED_BYTE;
	} else if (pixelFormat == Graphics::PixelFormat(2, 5, 6, 5, 0, 0, 5, 11, 0)) { // BGR565
		bpp = 2;
		intFormat = GL_RGB;
		glFormat = GL_BGR;
		gltype = GL_UNSIGNED_SHORT_5_6_5;
	} else if (pixelFormat == Graphics::PixelFormat(2, 5, 5, 5, 1, 1, 6, 11, 0)) { // BGRA5551
		bpp = 2;
		intFormat = GL_RGBA;
		glFormat = GL_BGRA;
		gltype = GL_UNSIGNED_SHORT_5_5_5_1;
	} else if (pixelFormat == Graphics::PixelFormat(2, 4, 4, 4, 4, 0, 4, 8, 12)) { // ABGR4444
		bpp = 2;
		intFormat = GL_RGBA;
		glFormat = GL_RGBA;
		gltype = GL_UNSIGNED_SHORT_4_4_4_4_REV;
	} else if (pixelFormat == Graphics::PixelFormat(2, 4, 4, 4, 4, 4, 8, 12, 0)) { // BGRA4444
		bpp = 2;
		intFormat = GL_RGBA;
		glFormat = GL_BGRA;
		gltype = GL_UNSIGNED_SHORT_4_4_4_4;
#endif
	} else {
		error("OpenGLGraphicsManager: Pixel format not supported");
	}
}

void OpenGLGraphicsManager::internUpdateScreen() {
	// Clear the screen buffer
	glClear(GL_COLOR_BUFFER_BIT); CHECK_GL_ERROR();

	if (_screenNeedsRedraw || !_screenDirtyRect.isEmpty())
		// Refresh texture if dirty
		refreshGameScreen();

	int scaleFactor = _videoMode.hardwareHeight / _videoMode.screenHeight;

	glPushMatrix();

	// Adjust game screen shake position
	glTranslatef(0, _shakePos * scaleFactor, 0); CHECK_GL_ERROR();

	// Draw the game screen
	_gameTexture->drawTexture(_displayX, _displayY, _displayWidth, _displayHeight);

	glPopMatrix();

	if (_overlayVisible) {
		if (_overlayNeedsRedraw || !_overlayDirtyRect.isEmpty())
			// Refresh texture if dirty
			refreshOverlay();

		// Draw the overlay
		_overlayTexture->drawTexture(0, 0, _videoMode.overlayWidth, _videoMode.overlayHeight);
	}

	if (_cursorVisible) {
		if (_cursorNeedsRedraw)
			// Refresh texture if dirty
			refreshCursor();

		glPushMatrix();

		// Adjust mouse shake position, unless the overlay is visible
		glTranslatef(0, _overlayVisible ? 0 : _shakePos * scaleFactor, 0); CHECK_GL_ERROR();

		// Draw the cursor
		if (_overlayVisible)
			_cursorTexture->drawTexture(_cursorState.x - _cursorState.rHotX,
				_cursorState.y - _cursorState.rHotY, _cursorState.rW, _cursorState.rH);
		else
			_cursorTexture->drawTexture(_cursorState.x - _cursorState.vHotX,
				_cursorState.y - _cursorState.vHotY, _cursorState.vW, _cursorState.vH);

		glPopMatrix();
	}

#ifdef USE_OSD
	if (_osdAlpha > 0) {
		if (_requireOSDUpdate) {
			updateOSD();
			_requireOSDUpdate = false;
		}

		// Update alpha value
		const int diff = g_system->getMillis() - _osdFadeStartTime;
		if (diff > 0) {
			if (diff >= kOSDFadeOutDuration) {
				// Back to full transparency
				_osdAlpha = 0;
			} else {
				// Do a fade out
				_osdAlpha = kOSDInitialAlpha - diff * kOSDInitialAlpha / kOSDFadeOutDuration;
			}
		}
		// Set the osd transparency
		glColor4f(1.0f, 1.0f, 1.0f, _osdAlpha / 100.0f); CHECK_GL_ERROR();

		// Draw the osd texture
		_osdTexture->drawTexture(0, 0, _videoMode.hardwareWidth, _videoMode.hardwareHeight);

		// Reset color
		glColor4f(1.0f, 1.0f, 1.0f, 1.0f); CHECK_GL_ERROR();
	}
#endif
}

void OpenGLGraphicsManager::initGL() {
	// Check available GL Extensions
	GLTexture::initGLExtensions();

	// Disable 3D properties
	glDisable(GL_CULL_FACE); CHECK_GL_ERROR();
	glDisable(GL_DEPTH_TEST); CHECK_GL_ERROR();
	glDisable(GL_LIGHTING); CHECK_GL_ERROR();
	glDisable(GL_FOG); CHECK_GL_ERROR();
	glDisable(GL_DITHER); CHECK_GL_ERROR();
	glShadeModel(GL_FLAT); CHECK_GL_ERROR();
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST); CHECK_GL_ERROR();

	// Setup alpha blend (For overlay and cursor)
	glEnable(GL_BLEND); CHECK_GL_ERROR();
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); CHECK_GL_ERROR();

	// Enable rendering with vertex and coord arrays
	glEnableClientState(GL_VERTEX_ARRAY); CHECK_GL_ERROR();
	glEnableClientState(GL_TEXTURE_COORD_ARRAY); CHECK_GL_ERROR();

	glEnable(GL_TEXTURE_2D); CHECK_GL_ERROR();

	// Setup the GL viewport
	glViewport(0, 0, _videoMode.hardwareWidth, _videoMode.hardwareHeight); CHECK_GL_ERROR();

	// Setup coordinates system
	glMatrixMode(GL_PROJECTION); CHECK_GL_ERROR();
	glLoadIdentity(); CHECK_GL_ERROR();
#ifdef USE_GLES
	glOrthof(0, _videoMode.hardwareWidth, _videoMode.hardwareHeight, 0, -1, 1); CHECK_GL_ERROR();
#else
	glOrtho(0, _videoMode.hardwareWidth, _videoMode.hardwareHeight, 0, -1, 1); CHECK_GL_ERROR();
#endif
	glMatrixMode(GL_MODELVIEW); CHECK_GL_ERROR();
	glLoadIdentity(); CHECK_GL_ERROR();
}

void OpenGLGraphicsManager::loadTextures() {
#ifdef USE_RGB_COLOR
	if (_transactionDetails.formatChanged && _gameTexture) {
		delete _gameTexture;
		_gameTexture = 0;
	}
#endif

	uint gameScreenBPP = 0;

	if (!_gameTexture) {
		byte bpp;
		GLenum intformat;
		GLenum format;
		GLenum type;
#ifdef USE_RGB_COLOR
		getGLPixelFormat(_screenFormat, bpp, intformat, format, type);
#else
		getGLPixelFormat(Graphics::PixelFormat::createFormatCLUT8(), bpp, intformat, format, type);
#endif
		gameScreenBPP = bpp;
		_gameTexture = new GLTexture(bpp, intformat, format, type);
	} else
		_gameTexture->refresh();

	_overlayFormat = Graphics::PixelFormat(2, 5, 5, 5, 1, 11, 6, 1, 0);

	if (!_overlayTexture) {
		byte bpp;
		GLenum intformat;
		GLenum format;
		GLenum type;
		getGLPixelFormat(_overlayFormat, bpp, intformat, format, type);
		_overlayTexture = new GLTexture(bpp, intformat, format, type);
	} else
		_overlayTexture->refresh();

	if (!_cursorTexture)
		_cursorTexture = new GLTexture(4, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE);
	else
		_cursorTexture->refresh();

	GLint filter = _videoMode.antialiasing ? GL_LINEAR : GL_NEAREST;
	_gameTexture->setFilter(filter);
	_overlayTexture->setFilter(filter);
	_cursorTexture->setFilter(filter);

	// Allocate texture memory and finish refreshing
	_gameTexture->allocBuffer(_videoMode.screenWidth, _videoMode.screenHeight);
	_overlayTexture->allocBuffer(_videoMode.overlayWidth, _videoMode.overlayHeight);
	_cursorTexture->allocBuffer(_cursorState.w, _cursorState.h);

	if (
#ifdef USE_RGB_COLOR
			_transactionDetails.formatChanged ||
#endif
			_oldVideoMode.screenWidth != _videoMode.screenWidth ||
			_oldVideoMode.screenHeight != _videoMode.screenHeight)
		_screenData.create(_videoMode.screenWidth, _videoMode.screenHeight,
#ifdef USE_RGB_COLOR
			_screenFormat
#else
			Graphics::PixelFormat::createFormatCLUT8()
#endif
			);


	if (_oldVideoMode.overlayWidth != _videoMode.overlayWidth ||
		_oldVideoMode.overlayHeight != _videoMode.overlayHeight)
		_overlayData.create(_videoMode.overlayWidth, _videoMode.overlayHeight,
			_overlayFormat);

	_screenNeedsRedraw = true;
	_overlayNeedsRedraw = true;
	_cursorNeedsRedraw = true;

	// We need to setup a proper unpack alignment value here, else we will
	// get problems with the texture updates, in case the surface data is
	// not properly aligned.
	// For now we use the gcd of the game screen format and 2, since 2 is
	// the BPP value for the overlay and the OSD.
	if (gameScreenBPP)
		glPixelStorei(GL_UNPACK_ALIGNMENT, Common::gcd<uint>(gameScreenBPP, 2));

	// We use a "pack" alignment (when reading from textures) to 4 here,
	// since the only place where we really use it is the BMP screenshot
	// code and that requires the same alignment too.
	glPixelStorei(GL_PACK_ALIGNMENT, 4);

#ifdef USE_OSD
	if (!_osdTexture)
		_osdTexture = new GLTexture(2, GL_RGBA, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1);
	else
		_osdTexture->refresh();

	_osdTexture->allocBuffer(_videoMode.overlayWidth, _videoMode.overlayHeight);

	// Update the OSD in case it is used right now
	_requireOSDUpdate = true;
#endif
}

bool OpenGLGraphicsManager::loadGFXMode() {
	// Initialize OpenGL settings
	initGL();

	loadTextures();

	refreshCursorScale();

	refreshDisplaySize();

	internUpdateScreen();

	return true;
}

void OpenGLGraphicsManager::unloadGFXMode() {

}

void OpenGLGraphicsManager::setScale(int newScale) {
	assert(_transactionMode == kTransactionActive);

	if (newScale == _videoMode.scaleFactor)
		return;

	_videoMode.scaleFactor = newScale;
	_transactionDetails.sizeChanged = true;
}

void OpenGLGraphicsManager::toggleAntialiasing() {
	assert(_transactionMode == kTransactionActive);

	_videoMode.antialiasing = !_videoMode.antialiasing;
	_transactionDetails.filterChanged = true;
}

uint OpenGLGraphicsManager::getAspectRatio() const {
	// In case we enable aspect ratio correction we force a 4/3 ratio.
	// But just for 320x200 and 640x400 games, since other games do not need
	// this.
	// TODO: This makes OpenGL Normal behave like OpenGL Conserve, when aspect
	// ratio correction is enabled, but it's better than the previous 4/3 mode
	// mess at least...
	if (_videoMode.aspectRatioCorrection
	    && ((_videoMode.screenWidth == 320 && _videoMode.screenHeight == 200)
	    || (_videoMode.screenWidth == 640 && _videoMode.screenHeight == 400)))
		return 13333;
	else if (_videoMode.mode == OpenGL::GFX_NORMAL)
		return _videoMode.hardwareWidth * 10000 / _videoMode.hardwareHeight;
	else
		return _videoMode.screenWidth * 10000 / _videoMode.screenHeight;
}

void OpenGLGraphicsManager::adjustMousePosition(int16 &x, int16 &y) {
	if (_overlayVisible)
		return;

	if (!_overlayVisible) {
		x -= _displayX;
		y -= _displayY;

		if (_displayWidth != _videoMode.screenWidth)
			x = x * _videoMode.screenWidth / _displayWidth;
		if (_displayHeight != _videoMode.screenHeight)
			y = y * _videoMode.screenHeight / _displayHeight;
	}
}

bool OpenGLGraphicsManager::saveScreenshot(const char *filename) {
	int width = _videoMode.hardwareWidth;
	int height = _videoMode.hardwareHeight;

	// A line of a BMP image must have a size divisible by 4.
	// We calculate the padding bytes needed here.
	// Since we use a 3 byte per pixel mode, we can use width % 4 here, since
	// it is equal to 4 - (width * 3) % 4. (4 - (width * Bpp) % 4, is the
	// usual way of computing the padding bytes required).
	const int linePaddingSize = width % 4;
	const int lineSize = width * 3 + linePaddingSize;

	// Allocate memory for screenshot
	uint8 *pixels = new uint8[lineSize * height];

	// Get pixel data from OpenGL buffer
#ifdef USE_GLES
	glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels); CHECK_GL_ERROR();
#else
	if (_formatBGR) {
		glReadPixels(0, 0, width, height, GL_BGR, GL_UNSIGNED_BYTE, pixels); CHECK_GL_ERROR();
	} else {
		glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels); CHECK_GL_ERROR();
	}
#endif

	// Open file
	Common::DumpFile out;
	out.open(filename);

	// Write BMP header
	out.writeByte('B');
	out.writeByte('M');
	out.writeUint32LE(height * lineSize + 54);
	out.writeUint32LE(0);
	out.writeUint32LE(54);
	out.writeUint32LE(40);
	out.writeUint32LE(width);
	out.writeUint32LE(height);
	out.writeUint16LE(1);
	out.writeUint16LE(24);
	out.writeUint32LE(0);
	out.writeUint32LE(0);
	out.writeUint32LE(0);
	out.writeUint32LE(0);
	out.writeUint32LE(0);
	out.writeUint32LE(0);

	// Write pixel data to BMP
	out.write(pixels, lineSize * height);

	// Free allocated memory
	delete[] pixels;

	return true;
}

const char *OpenGLGraphicsManager::getCurrentModeName() {
	const char *modeName = 0;
	const OSystem::GraphicsMode *g = getSupportedGraphicsModes();
	while (g->name) {
		if (g->id == _videoMode.mode) {
			modeName = g->description;
			break;
		}
		g++;
	}
	return modeName;
}

#ifdef USE_OSD
const Graphics::Font *OpenGLGraphicsManager::getFontOSD() {
  return FontMan.getFontByUsage(Graphics::FontManager::kLocalizedFont);
}

void OpenGLGraphicsManager::updateOSD() {
	// The font we are going to use:
	const Graphics::Font *font = getFontOSD();

	if (_osdSurface.w != _osdTexture->getWidth() || _osdSurface.h != _osdTexture->getHeight())
		_osdSurface.create(_osdTexture->getWidth(), _osdTexture->getHeight(), _overlayFormat);
	else
		// Clear everything
		memset(_osdSurface.pixels, 0, _osdSurface.h * _osdSurface.pitch);

	// Determine a rect which would contain the message string (clipped to the
	// screen dimensions).
	const int vOffset = 6;
	const int lineSpacing = 1;
	const int lineHeight = font->getFontHeight() + 2 * lineSpacing;
	int width = 0;
	int height = lineHeight * _osdLines.size() + 2 * vOffset;
	for (uint i = 0; i < _osdLines.size(); i++) {
		width = MAX(width, font->getStringWidth(_osdLines[i]) + 14);
	}

	// Clip the rect
	if (width > _osdSurface.w)
		width = _osdSurface.w;
	if (height > _osdSurface.h)
		height = _osdSurface.h;

	int dstX = (_osdSurface.w - width) / 2;
	int dstY = (_osdSurface.h - height) / 2;

	// Draw a dark gray rect (R = 40, G = 40, B = 40)
	const uint16 color = 0x294B;
	_osdSurface.fillRect(Common::Rect(dstX, dstY, dstX + width, dstY + height), color);

	// Render the message, centered, and in white
	for (uint i = 0; i < _osdLines.size(); i++) {
		font->drawString(&_osdSurface, _osdLines[i],
		                 dstX, dstY + i * lineHeight + vOffset + lineSpacing, width,
		                 0xFFFF, Graphics::kTextAlignCenter);
	}

	// Update the texture
	_osdTexture->updateBuffer(_osdSurface.pixels, _osdSurface.pitch, 0, 0,
	                          _osdSurface.w, _osdSurface.h);
}
#endif

#endif
