diff options
author | Max Horn | 2010-11-28 14:56:31 +0000 |
---|---|---|
committer | Max Horn | 2010-11-28 14:56:31 +0000 |
commit | 7760077cf530c35c969f9286145d9a36d0440d70 (patch) | |
tree | 34c67abbacefa26792ca77fc9f5360c77a34663d /backends/graphics/opengl | |
parent | 284b49aabc54590e1444f06561a815c2a3c5de7e (diff) | |
parent | 74a53df11b51fa4956745c086b2e6351b8383568 (diff) | |
download | scummvm-rg350-7760077cf530c35c969f9286145d9a36d0440d70.tar.gz scummvm-rg350-7760077cf530c35c969f9286145d9a36d0440d70.tar.bz2 scummvm-rg350-7760077cf530c35c969f9286145d9a36d0440d70.zip |
Merging the gsoc2010-opengl branch
svn-id: r54518
Diffstat (limited to 'backends/graphics/opengl')
-rw-r--r-- | backends/graphics/opengl/glerrorcheck.cpp | 69 | ||||
-rw-r--r-- | backends/graphics/opengl/glerrorcheck.h | 38 | ||||
-rw-r--r-- | backends/graphics/opengl/gltexture.cpp | 189 | ||||
-rw-r--r-- | backends/graphics/opengl/gltexture.h | 115 | ||||
-rw-r--r-- | backends/graphics/opengl/opengl-graphics.cpp | 1304 | ||||
-rw-r--r-- | backends/graphics/opengl/opengl-graphics.h | 293 |
6 files changed, 2008 insertions, 0 deletions
diff --git a/backends/graphics/opengl/glerrorcheck.cpp b/backends/graphics/opengl/glerrorcheck.cpp new file mode 100644 index 0000000000..ee177f0ac5 --- /dev/null +++ b/backends/graphics/opengl/glerrorcheck.cpp @@ -0,0 +1,69 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#if defined(DEBUG) && defined(USE_OPENGL) + +#include "backends/graphics/opengl/glerrorcheck.h" +#include "common/debug.h" + +#ifdef WIN32 +#if defined(ARRAYSIZE) && !defined(_WINDOWS_) +#undef ARRAYSIZE +#endif +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#undef ARRAYSIZE +#endif + +#ifdef MACOSX +#include <gl.h> +#elif defined(USE_GLES) +#include <GLES/gl.h> +#else +#include <GL/gl.h> +#endif + +static const char *getGlErrStr(GLenum error) { + switch (error) { + case GL_NO_ERROR: return "GL_NO_ERROR"; + case GL_INVALID_ENUM: return "GL_INVALID_ENUM"; + case GL_INVALID_OPERATION: return "GL_INVALID_OPERATION"; + case GL_STACK_OVERFLOW: return "GL_STACK_OVERFLOW"; + case GL_STACK_UNDERFLOW: return "GL_STACK_UNDERFLOW"; + case GL_OUT_OF_MEMORY: return "GL_OUT_OF_MEMORY"; + } + + static char buf[40]; + snprintf(buf, sizeof(buf), "(Unknown GL error code 0x%x)", error); + return buf; +} + +void checkGlError(const char *file, int line) { + GLenum error = glGetError(); + if (error != GL_NO_ERROR) + warning("%s:%d: GL error: %s", file, line, getGlErrStr(error)); +} + +#endif diff --git a/backends/graphics/opengl/glerrorcheck.h b/backends/graphics/opengl/glerrorcheck.h new file mode 100644 index 0000000000..a94699ce1d --- /dev/null +++ b/backends/graphics/opengl/glerrorcheck.h @@ -0,0 +1,38 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#if !defined(DEBUG) + +// If not in debug, do nothing +#define CHECK_GL_ERROR() do {} while (false) + +#else + +// If in debug, check for an error after a GL call +#define CHECK_GL_ERROR() checkGlError(__FILE__, __LINE__) + +void checkGlError(const char *file, int line); + +#endif diff --git a/backends/graphics/opengl/gltexture.cpp b/backends/graphics/opengl/gltexture.cpp new file mode 100644 index 0000000000..e43cbe2266 --- /dev/null +++ b/backends/graphics/opengl/gltexture.cpp @@ -0,0 +1,189 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#if defined(USE_OPENGL) + +#include "backends/graphics/opengl/gltexture.h" +#include "backends/graphics/opengl/glerrorcheck.h" + +#include "common/rect.h" +#include "common/array.h" +#include "common/util.h" +#include "common/tokenizer.h" + +// Supported GL extensions +static bool npot_supported = false; + +/*static inline GLint xdiv(int numerator, int denominator) { + assert(numerator < (1 << 16)); + return (numerator << 16) / denominator; +}*/ + +static GLuint nextHigher2(GLuint v) { + if (v == 0) + return 1; + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + return ++v; +} + +void GLTexture::initGLExtensions() { + static bool inited = false; + + // Return if extensions were already checked + if (inited) + return; + + // Get a string with all extensions + const char* ext_string = + reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS)); + CHECK_GL_ERROR(); + Common::StringTokenizer tokenizer(ext_string, " "); + // Iterate all string tokens + while (!tokenizer.empty()) { + Common::String token = tokenizer.nextToken(); + if (token == "GL_ARB_texture_non_power_of_two") + npot_supported = true; + } + + inited = true; +} + +GLTexture::GLTexture(byte bpp, GLenum internalFormat, GLenum format, GLenum type) + : + _bytesPerPixel(bpp), + _internalFormat(internalFormat), + _glFormat(format), + _glType(type), + _textureWidth(0), + _textureHeight(0), + _realWidth(0), + _realHeight(0), + _refresh(false), + _filter(GL_NEAREST) { + + // Generate the texture ID + glGenTextures(1, &_textureName); CHECK_GL_ERROR(); +} + +GLTexture::~GLTexture() { + // Delete the texture + glDeleteTextures(1, &_textureName); CHECK_GL_ERROR(); +} + +void GLTexture::refresh() { + // Delete previous texture + glDeleteTextures(1, &_textureName); CHECK_GL_ERROR(); + + // Generate the texture ID + glGenTextures(1, &_textureName); CHECK_GL_ERROR(); + _refresh = true; +} + +void GLTexture::allocBuffer(GLuint w, GLuint h) { + _realWidth = w; + _realHeight = h; + + if (w <= _textureWidth && h <= _textureHeight && !_refresh) + // Already allocated a sufficiently large buffer + return; + + if (npot_supported) { + _textureWidth = w; + _textureHeight = h; + } else { + _textureWidth = nextHigher2(w); + _textureHeight = nextHigher2(h); + } + + // Select this OpenGL texture + glBindTexture(GL_TEXTURE_2D, _textureName); CHECK_GL_ERROR(); + + // Set the texture parameters + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _filter); CHECK_GL_ERROR(); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _filter); CHECK_GL_ERROR(); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); CHECK_GL_ERROR(); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); CHECK_GL_ERROR(); + + // Allocate room for the texture + glTexImage2D(GL_TEXTURE_2D, 0, _internalFormat, + _textureWidth, _textureHeight, 0, _glFormat, _glType, NULL); CHECK_GL_ERROR(); + + _refresh = false; +} + +void GLTexture::updateBuffer(const void *buf, int pitch, GLuint x, GLuint y, GLuint w, GLuint h) { + // Select this OpenGL texture + glBindTexture(GL_TEXTURE_2D, _textureName); CHECK_GL_ERROR(); + + // Check if the buffer has its data contiguously + if (static_cast<int>(w) * _bytesPerPixel == pitch && w == _textureWidth) { + glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, + _glFormat, _glType, buf); CHECK_GL_ERROR(); + } else { + // Update the texture row by row + const byte *src = static_cast<const byte *>(buf); + do { + glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, + w, 1, _glFormat, _glType, src); CHECK_GL_ERROR(); + ++y; + src += pitch; + } while (--h); + } +} + +void GLTexture::drawTexture(GLshort x, GLshort y, GLshort w, GLshort h) { + // Select this OpenGL texture + glBindTexture(GL_TEXTURE_2D, _textureName); CHECK_GL_ERROR(); + + // Calculate the texture rect that will be drawn + const GLfloat texWidth = (GLfloat)_realWidth / _textureWidth;//xdiv(_surface.w, _textureWidth); + const GLfloat texHeight = (GLfloat)_realHeight / _textureHeight;//xdiv(_surface.h, _textureHeight); + const GLfloat texcoords[] = { + 0, 0, + texWidth, 0, + 0, texHeight, + texWidth, texHeight, + }; + glTexCoordPointer(2, GL_FLOAT, 0, texcoords); CHECK_GL_ERROR(); + + // Calculate the screen rect where the texture will be drawn + const GLshort vertices[] = { + x, y, + x + w, y, + x, y + h, + x + w, y + h, + }; + glVertexPointer(2, GL_SHORT, 0, vertices); CHECK_GL_ERROR(); + + // Draw the texture to the screen buffer + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); CHECK_GL_ERROR(); +} + +#endif diff --git a/backends/graphics/opengl/gltexture.h b/backends/graphics/opengl/gltexture.h new file mode 100644 index 0000000000..9864f6816c --- /dev/null +++ b/backends/graphics/opengl/gltexture.h @@ -0,0 +1,115 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifdef WIN32 +#if defined(ARRAYSIZE) && !defined(_WINDOWS_) +#undef ARRAYSIZE +#endif +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#undef ARRAYSIZE +#endif + +#if defined(USE_GLES) +#include <GLES/gl.h> +#include <GLES/glext.h> +#elif defined(MACOSX) +#include <gl.h> +#include <glext.h> +#else +#include <GL/gl.h> +#include <GL/glext.h> +#endif + +#include "graphics/surface.h" + +#include "common/rect.h" +#include "common/array.h" + +/** + * OpenGL texture manager class + */ +class GLTexture { +public: + /** + * Initialize OpenGL Extensions + */ + static void initGLExtensions(); + + GLTexture(byte bpp, GLenum internalFormat, GLenum format, GLenum type); + virtual ~GLTexture(); + + /** + * Refresh the texture after a context change. The + * process will be completed on next allocBuffer call. + */ + virtual void refresh(); + + /** + * Allocates memory needed for the given size. + */ + virtual void allocBuffer(GLuint width, GLuint height); + + /** + * Updates the texture pixels. + */ + virtual void updateBuffer(const void *buf, int pitch, GLuint x, GLuint y, + GLuint w, GLuint h); + + /** + * Draws the texture to the screen buffer. + */ + virtual void drawTexture(GLshort x, GLshort y, GLshort w, GLshort h); + + /** + * Get the texture width. + */ + GLuint getWidth() const { return _realWidth; } + + /** + * Get the texture height. + */ + GLuint getHeight() const { return _realHeight; } + + /** + * Set the texture filter. + * @filter the filter type, GL_NEAREST or GL_LINEAR + */ + void setFilter(GLint filter) { _filter = filter; } + +protected: + const byte _bytesPerPixel; + const GLenum _internalFormat; + const GLenum _glFormat; + const GLenum _glType; + + GLuint _realWidth; + GLuint _realHeight; + GLuint _textureName; + GLuint _textureWidth; + GLuint _textureHeight; + GLint _filter; + bool _refresh; +}; diff --git a/backends/graphics/opengl/opengl-graphics.cpp b/backends/graphics/opengl/opengl-graphics.cpp new file mode 100644 index 0000000000..8682c54921 --- /dev/null +++ b/backends/graphics/opengl/opengl-graphics.cpp @@ -0,0 +1,1304 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#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/translation.h" +#include "graphics/font.h" +#include "graphics/fontman.h" + +OpenGLGraphicsManager::OpenGLGraphicsManager() + : +#ifdef USE_OSD + _osdTexture(0), _osdAlpha(0), _osdFadeStartTime(0), +#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), + _aspectRatioCorrection(false) { + + 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) * 4, 256); + _cursorPalette = (byte *)calloc(sizeof(byte) * 4, 256); +} + +OpenGLGraphicsManager::~OpenGLGraphicsManager() { + // Unregister the event observer + if (g_system->getEventManager()->getEventDispatcher() != NULL) + g_system->getEventManager()->getEventDispatcher()->unregisterObserver(this); + + free(_gamePalette); + free(_cursorPalette); + + if (_gameTexture != NULL) + delete _gameTexture; + if (_overlayTexture != NULL) + delete _overlayTexture; + if (_cursorTexture != NULL) + delete _cursorTexture; +} + +void OpenGLGraphicsManager::initEventObserver() { + // Register the graphics manager as a event observer + g_system->getEventManager()->getEventDispatcher()->registerObserver(this, 10, false); +} + +// +// Feature +// + +bool OpenGLGraphicsManager::hasFeature(OSystem::Feature f) { + return + (f == OSystem::kFeatureAspectRatioCorrection) || + (f == OSystem::kFeatureCursorHasPalette); +} + +void OpenGLGraphicsManager::setFeatureState(OSystem::Feature f, bool enable) { + switch (f) { + case OSystem::kFeatureAspectRatioCorrection: + _videoMode.mode = OpenGL::GFX_4_3; + _aspectRatioCorrection = enable; + break; + default: + break; + } +} + +bool OpenGLGraphicsManager::getFeatureState(OSystem::Feature f) { + 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}, + {"gl3", _s("OpenGL 4/3"), OpenGL::GFX_4_3}, + {"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); + + if (_oldVideoMode.setup && _oldVideoMode.mode == mode) + return true; + + switch (mode) { + case OpenGL::GFX_NORMAL: + case OpenGL::GFX_CONSERVE: + case OpenGL::GFX_4_3: + 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 * 4, colors, num * 4); + + _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 * 4, num * 4); +} + +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; + for (int i = 0; i < h; i++) { + memcpy(dst + x * _screenData.bytesPerPixel, src, w * _screenData.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; + + 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; + } + } + + _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.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.bytesPerPixel, src, w * _overlayData.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::setMousePos(int x, int y) { + _cursorState.x = x; + _cursorState.y = y; +} + +void OpenGLGraphicsManager::warpMouse(int x, int y) { + setMousePos(x, y); +} + +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.create(w, h, _cursorFormat.bytesPerPixel); + + // 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 * 4, colors, num * 4); + + _cursorPaletteDisabled = false; + _cursorNeedsRedraw = true; +} + +void OpenGLGraphicsManager::disableCursorPalette(bool disable) { + _cursorPaletteDisabled = disable; + _cursorNeedsRedraw = true; +} + +// +// Misc +// + +void OpenGLGraphicsManager::displayMessageOnOSD(const char *msg) { + assert (_transactionMode == kTransactionNone); + assert(msg); + + // The font we are going to use: + const Graphics::Font *font = FontMan.getFontByUsage(Graphics::FontManager::kOSDFont); + + if (_osdSurface.w != _osdTexture->getWidth() || _osdSurface.h != _osdTexture->getHeight()) + _osdSurface.create(_osdTexture->getWidth(), _osdTexture->getHeight(), 2); + else + // Clear everything + memset(_osdSurface.pixels, 0, _osdSurface.h * _osdSurface.pitch); + + // Split the message into separate lines. + Common::Array<Common::String> lines; + const char *ptr; + for (ptr = msg; *ptr; ++ptr) { + if (*ptr == '\n') { + lines.push_back(Common::String(msg, ptr - msg)); + msg = ptr + 1; + } + } + lines.push_back(Common::String(msg, ptr - msg)); + + // 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 * lines.size() + 2 * vOffset; + for (uint i = 0; i < lines.size(); i++) { + width = MAX(width, font->getStringWidth(lines[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 + uint16 color = 0x294B; + uint16 *dst = (uint16 *)_osdSurface.pixels + dstY * _osdSurface.w + dstX; + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) + dst[j] = color; + dst += _osdSurface.w; + } + + // Render the message, centered, and in white + for (uint i = 0; i < lines.size(); i++) { + font->drawString(&_osdSurface, lines[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); + + // Init the OSD display parameters, and the fade out + _osdAlpha = kOSDInitialAlpha; + _osdFadeStartTime = g_system->getMillis() + kOSDFadeOutDelay; +} + +// +// Intern +// + +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.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.bytesPerPixel; + byte *dst = surface; + for (int i = 0; i < h; i++) { + for (int j = 0; j < w; j++) { + dst[0] = _gamePalette[src[j] * 4]; + dst[1] = _gamePalette[src[j] * 4 + 1]; + dst[2] = _gamePalette[src[j] * 4 + 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.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.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.bytesPerPixel; + byte *dst = surface; + for (int i = 0; i < h; i++) { + for (int j = 0; j < w; j++) { + dst[0] = _gamePalette[src[j] * 4]; + dst[1] = _gamePalette[src[j] * 4 + 1]; + dst[2] = _gamePalette[src[j] * 4 + 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.bytesPerPixel, _overlayData.pitch, x, y, w, h); + } + + _overlayNeedsRedraw = false; + _overlayDirtyRect = Common::Rect(); +} + +void OpenGLGraphicsManager::refreshCursor() { + _cursorNeedsRedraw = false; + + if (_cursorFormat.bytesPerPixel == 1) { + // Create a temporary RGBA8888 surface + byte *surface = new byte[_cursorState.w * _cursorState.h * 4]; + memset(surface, 0, _cursorState.w * _cursorState.h * 4); + + // Select palette + byte *palette; + if (_cursorPaletteDisabled) + palette = _gamePalette; + else + palette = _cursorPalette; + + // Convert the paletted cursor to RGBA8888 + const byte *src = (byte *)_cursorData.pixels; + byte *dst = surface; + for (int i = 0; i < _cursorState.w * _cursorState.h; i++) { + // Check for keycolor + if (src[i] != _cursorKeyColor) { + dst[0] = palette[src[i] * 4]; + dst[1] = palette[src[i] * 4 + 1]; + dst[2] = palette[src[i] * 4 + 2]; + dst[3] = 255; + } + dst += 4; + } + + // Allocate a texture big enough for cursor + _cursorTexture->allocBuffer(_cursorState.w, _cursorState.h); + + // 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); + int actualFactor = 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.overlayWidth; + height = _videoMode.overlayHeight; + } 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, 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(_displayX, _displayY, _displayWidth, _displayHeight); + } + + 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) { + // 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 + glOrthox(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; +#endif + + 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 + _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 (_transactionDetails.formatChanged || + _oldVideoMode.screenWidth != _videoMode.screenWidth || + _oldVideoMode.screenHeight != _videoMode.screenHeight) + _screenData.create(_videoMode.screenWidth, _videoMode.screenHeight, + _screenFormat.bytesPerPixel); + + if (_oldVideoMode.overlayWidth != _videoMode.overlayWidth || + _oldVideoMode.overlayHeight != _videoMode.overlayHeight) + _overlayData.create(_videoMode.overlayWidth, _videoMode.overlayHeight, + _overlayFormat.bytesPerPixel); + + _screenNeedsRedraw = true; + _overlayNeedsRedraw = true; + _cursorNeedsRedraw = true; + +#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); +#endif +} + +bool OpenGLGraphicsManager::loadGFXMode() { + // Initialize OpenGL settings + initGL(); + + loadTextures(); + + refreshCursorScale(); + + refreshDisplaySize(); + + internUpdateScreen(); + + return true; +} + +void OpenGLGraphicsManager::unloadGFXMode() { + +} + +void OpenGLGraphicsManager::setScale(int newScale) { + if (newScale == _videoMode.scaleFactor) + return; + + _videoMode.scaleFactor = newScale; + _transactionDetails.sizeChanged = true; +} + +uint OpenGLGraphicsManager::getAspectRatio() { + if (_videoMode.mode == OpenGL::GFX_NORMAL) + return _videoMode.hardwareWidth * 10000 / _videoMode.hardwareHeight; + else if (_videoMode.mode == OpenGL::GFX_4_3) + return 13333; + else + return _videoMode.screenWidth * 10000 / _videoMode.screenHeight; +} + +void OpenGLGraphicsManager::adjustMouseEvent(const Common::Event &event) { + if (!event.synthetic) { + Common::Event newEvent(event); + newEvent.synthetic = true; + + if (_videoMode.mode == OpenGL::GFX_NORMAL) { + if (_videoMode.hardwareWidth != _videoMode.overlayWidth) + newEvent.mouse.x = newEvent.mouse.x * _videoMode.overlayWidth / _videoMode.hardwareWidth; + if (_videoMode.hardwareHeight != _videoMode.overlayHeight) + newEvent.mouse.y = newEvent.mouse.y * _videoMode.overlayHeight / _videoMode.hardwareHeight; + + if (!_overlayVisible) { + newEvent.mouse.x /= _videoMode.scaleFactor; + newEvent.mouse.y /= _videoMode.scaleFactor; + } + + } else { + newEvent.mouse.x -= _displayX; + newEvent.mouse.y -= _displayY; + + if (_overlayVisible) { + if (_displayWidth != _videoMode.overlayWidth) + newEvent.mouse.x = newEvent.mouse.x * _videoMode.overlayWidth / _displayWidth; + if (_displayHeight != _videoMode.overlayHeight) + newEvent.mouse.y = newEvent.mouse.y * _videoMode.overlayHeight / _displayHeight; + } else { + if (_displayWidth != _videoMode.screenWidth) + newEvent.mouse.x = newEvent.mouse.x * _videoMode.screenWidth / _displayWidth; + if (_displayHeight != _videoMode.screenHeight) + newEvent.mouse.y = newEvent.mouse.y * _videoMode.screenHeight / _displayHeight; + } + } + + g_system->getEventManager()->pushEvent(newEvent); + } +} + +bool OpenGLGraphicsManager::notifyEvent(const Common::Event &event) { + switch (event.type) { + case Common::EVENT_MOUSEMOVE: + if (!event.synthetic) + setMousePos(event.mouse.x, event.mouse.y); + case Common::EVENT_LBUTTONDOWN: + case Common::EVENT_RBUTTONDOWN: + case Common::EVENT_WHEELUP: + case Common::EVENT_WHEELDOWN: + case Common::EVENT_MBUTTONDOWN: + case Common::EVENT_LBUTTONUP: + case Common::EVENT_RBUTTONUP: + case Common::EVENT_MBUTTONUP: + adjustMouseEvent(event); + return !event.synthetic; + + default: + break; + } + + return false; +} + +bool OpenGLGraphicsManager::saveScreenshot(const char *filename) { + int width = _videoMode.hardwareWidth; + int height = _videoMode.hardwareHeight; + + // Allocate memory for screenshot + uint8 *pixels = new uint8[width * height * 3]; + + // 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 * width * 3 + 52); + out.writeUint32LE(0); + out.writeUint32LE(52); + 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, width * height * 3); + + // 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; +} + +void OpenGLGraphicsManager::switchDisplayMode(int mode) { + if (_oldVideoMode.setup && _oldVideoMode.mode == mode) + return; + + if (_transactionMode == kTransactionActive) { + if (mode == -1) // If -1, switch to next mode + _videoMode.mode = (_videoMode.mode + 1) % 4; + else if (mode == -2) // If -2, switch to previous mode + _videoMode.mode = (_videoMode.mode + 3) % 4; + else + _videoMode.mode = mode; + + _transactionDetails.needRefresh = true; + _aspectRatioCorrection = false; + } +} + +#endif diff --git a/backends/graphics/opengl/opengl-graphics.h b/backends/graphics/opengl/opengl-graphics.h new file mode 100644 index 0000000000..9b3340aef2 --- /dev/null +++ b/backends/graphics/opengl/opengl-graphics.h @@ -0,0 +1,293 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef BACKENDS_GRAPHICS_OPENGL_H +#define BACKENDS_GRAPHICS_OPENGL_H + +#include "backends/graphics/opengl/gltexture.h" +#include "backends/graphics/graphics.h" +#include "common/events.h" + +// Uncomment this to enable the 'on screen display' code. +#define USE_OSD 1 + +namespace OpenGL { +// The OpenGL GFX modes. They have to be inside the OpenGL namespace so they +// do not clash with the SDL GFX modes. +enum { + GFX_NORMAL = 0, + GFX_CONSERVE = 1, + GFX_4_3 = 2, + GFX_ORIGINAL = 3 +}; + +} + +/** + * Open GL graphics manager. This is an abstract class, it does not do the + * window and OpenGL context initialization. + * Derived classes should at least override internUpdateScreen for doing + * the buffers swap, and implement loadGFXMode for handling the window/context if + * needed. If USE_RGB_COLOR is enabled, getSupportedFormats must be implemented. + */ +class OpenGLGraphicsManager : public GraphicsManager, public Common::EventObserver { +public: + OpenGLGraphicsManager(); + virtual ~OpenGLGraphicsManager(); + + virtual void initEventObserver(); + + virtual bool hasFeature(OSystem::Feature f); + virtual void setFeatureState(OSystem::Feature f, bool enable); + virtual bool getFeatureState(OSystem::Feature f); + + static const OSystem::GraphicsMode *supportedGraphicsModes(); + virtual const OSystem::GraphicsMode *getSupportedGraphicsModes() const; + virtual int getDefaultGraphicsMode() const; + virtual bool setGraphicsMode(int mode); + virtual int getGraphicsMode() const; + virtual void resetGraphicsScale(); +#ifdef USE_RGB_COLOR + virtual Graphics::PixelFormat getScreenFormat() const; + virtual Common::List<Graphics::PixelFormat> getSupportedFormats() const = 0; +#endif + virtual void initSize(uint width, uint height, const Graphics::PixelFormat *format = NULL); + virtual int getScreenChangeID() const; + + virtual void beginGFXTransaction(); + virtual OSystem::TransactionError endGFXTransaction(); + + virtual int16 getHeight(); + virtual int16 getWidth(); + virtual void setPalette(const byte *colors, uint start, uint num); + virtual void grabPalette(byte *colors, uint start, uint num); + virtual void copyRectToScreen(const byte *buf, int pitch, int x, int y, int w, int h); + virtual Graphics::Surface *lockScreen(); + virtual void unlockScreen(); + virtual void fillScreen(uint32 col); + virtual void updateScreen(); + virtual void setShakePos(int shakeOffset); + virtual void setFocusRectangle(const Common::Rect& rect); + virtual void clearFocusRectangle(); + + virtual void showOverlay(); + virtual void hideOverlay(); + virtual Graphics::PixelFormat getOverlayFormat() const; + virtual void clearOverlay(); + virtual void grabOverlay(OverlayColor *buf, int pitch); + virtual void copyRectToOverlay(const OverlayColor *buf, int pitch, int x, int y, int w, int h); + virtual int16 getOverlayHeight(); + virtual int16 getOverlayWidth(); + + virtual bool showMouse(bool visible); + virtual void warpMouse(int x, int y); + virtual void setMouseCursor(const byte *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, int cursorTargetScale = 1, const Graphics::PixelFormat *format = NULL); + virtual void setCursorPalette(const byte *colors, uint start, uint num); + virtual void disableCursorPalette(bool disable); + + virtual void displayMessageOnOSD(const char *msg); + + // Override from Common::EventObserver + bool notifyEvent(const Common::Event &event); + +protected: + /** + * Setup OpenGL settings + */ + virtual void initGL(); + + /** + * Creates and refreshs OpenGL textures. + */ + virtual void loadTextures(); + + // + // GFX and video + // + enum { + kTransactionNone = 0, + kTransactionActive = 1, + kTransactionRollback = 2 + }; + + struct TransactionDetails { + bool sizeChanged; + bool needRefresh; + bool needUpdatescreen; + bool filterChanged; +#ifdef USE_RGB_COLOR + bool formatChanged; +#endif + }; + TransactionDetails _transactionDetails; + int _transactionMode; + + struct VideoState { + bool setup; + + bool fullscreen; + int activeFullscreenMode; + + int mode; + int scaleFactor; + bool antialiasing; + + int screenWidth, screenHeight; + int overlayWidth, overlayHeight; + int hardwareWidth, hardwareHeight; +#ifdef USE_RGB_COLOR + Graphics::PixelFormat format; +#endif + }; + VideoState _videoMode, _oldVideoMode; + + /** + * Sets the OpenGL texture format for the given pixel format. If format is not support will raise an error. + */ + virtual void getGLPixelFormat(Graphics::PixelFormat pixelFormat, byte &bpp, GLenum &intFormat, GLenum &glFormat, GLenum &type); + + virtual void internUpdateScreen(); + virtual bool loadGFXMode(); + virtual void unloadGFXMode(); + + virtual void setScale(int newScale); + + // Drawing coordinates for the current display mode and scale + int _displayX; + int _displayY; + int _displayWidth; + int _displayHeight; + + /** + * Sets the dispaly mode. + * @mode the dispaly mode, if -1 it will switch to next mode. If -2 to previous mode. + */ + virtual void switchDisplayMode(int mode); + + virtual const char *getCurrentModeName(); + + virtual void calculateDisplaySize(int &width, int &height); + virtual void refreshDisplaySize(); + + bool _aspectRatioCorrection; + + /** + * Returns the current target aspect ratio x 10000 + */ + virtual uint getAspectRatio(); + + bool _formatBGR; + + // + // Game screen + // + GLTexture* _gameTexture; + Graphics::Surface _screenData; + int _screenChangeCount; + bool _screenNeedsRedraw; + Common::Rect _screenDirtyRect; + +#ifdef USE_RGB_COLOR + Graphics::PixelFormat _screenFormat; +#endif + byte *_gamePalette; + + virtual void refreshGameScreen(); + + // Shake mode + int _shakePos; + + // + // Overlay + // + GLTexture* _overlayTexture; + Graphics::Surface _overlayData; + Graphics::PixelFormat _overlayFormat; + bool _overlayVisible; + bool _overlayNeedsRedraw; + Common::Rect _overlayDirtyRect; + + virtual void refreshOverlay(); + + // + // Mouse + // + struct MousePos { + // The mouse position, using either virtual (game) or real + // (overlay) coordinates. + int16 x, y; + + // The size and hotspot of the original cursor image. + int16 w, h; + int16 hotX, hotY; + + // The size and hotspot of the scaled cursor, in real coordinates. + int16 rW, rH; + int16 rHotX, rHotY; + + // The size and hotspot of the scaled cursor, in game coordinates. + int16 vW, vH; + int16 vHotX, vHotY; + + MousePos() : x(0), y(0), w(0), h(0), hotX(0), hotY(0), + rW(0), rH(0), rHotX(0), rHotY(0), vW(0), vH(0), + vHotX(0), vHotY(0) {} + }; + + GLTexture* _cursorTexture; + Graphics::Surface _cursorData; + Graphics::PixelFormat _cursorFormat; + byte *_cursorPalette; + bool _cursorPaletteDisabled; + MousePos _cursorState; + bool _cursorVisible; + uint32 _cursorKeyColor; + int _cursorTargetScale; + bool _cursorNeedsRedraw; + + virtual void refreshCursor(); + virtual void refreshCursorScale(); + virtual void adjustMouseEvent(const Common::Event &event); + virtual void setMousePos(int x, int y); + + // + // Misc + // + virtual bool saveScreenshot(const char *filename); + +#ifdef USE_OSD + GLTexture *_osdTexture; + Graphics::Surface _osdSurface; + uint8 _osdAlpha; + uint32 _osdFadeStartTime; + enum { + kOSDFadeOutDelay = 2 * 1000, + kOSDFadeOutDuration = 500, + kOSDInitialAlpha = 80 + }; +#endif +}; + +#endif |