diff options
author | Johannes Schickel | 2013-08-16 05:29:56 +0200 |
---|---|---|
committer | Johannes Schickel | 2013-10-19 22:12:01 +0200 |
commit | 46323074e77622e7d08fe20bfdcc459b8eba08a3 (patch) | |
tree | 4bdb5ccf144faec961ee348199e6068097873441 /backends | |
parent | 8a6e57cba1c89f922e27bb72201cfda55681f6dc (diff) | |
download | scummvm-rg350-46323074e77622e7d08fe20bfdcc459b8eba08a3.tar.gz scummvm-rg350-46323074e77622e7d08fe20bfdcc459b8eba08a3.tar.bz2 scummvm-rg350-46323074e77622e7d08fe20bfdcc459b8eba08a3.zip |
OPENGL: Add new generic OpenGL (ES) backend.
This backend is based on ideas of the old OpenGL backend, of the Android GL
backend and of the iPhone GL backend.
Diffstat (limited to 'backends')
-rw-r--r-- | backends/graphics/opengl/debug.cpp | 65 | ||||
-rw-r--r-- | backends/graphics/opengl/debug.h | 39 | ||||
-rw-r--r-- | backends/graphics/opengl/extensions.cpp | 48 | ||||
-rw-r--r-- | backends/graphics/opengl/extensions.h | 41 | ||||
-rw-r--r-- | backends/graphics/opengl/opengl-graphics.cpp | 922 | ||||
-rw-r--r-- | backends/graphics/opengl/opengl-graphics.h | 418 | ||||
-rw-r--r-- | backends/graphics/opengl/opengl-sys.h | 57 | ||||
-rw-r--r-- | backends/graphics/opengl/texture.cpp | 305 | ||||
-rw-r--r-- | backends/graphics/opengl/texture.h | 159 | ||||
-rw-r--r-- | backends/module.mk | 9 |
10 files changed, 2063 insertions, 0 deletions
diff --git a/backends/graphics/opengl/debug.cpp b/backends/graphics/opengl/debug.cpp new file mode 100644 index 0000000000..69006bb975 --- /dev/null +++ b/backends/graphics/opengl/debug.cpp @@ -0,0 +1,65 @@ +/* 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 "backends/graphics/opengl/debug.h" +#include "backends/graphics/opengl/opengl-sys.h" + +#include "common/str.h" +#include "common/textconsole.h" + +#ifdef OPENGL_DEBUG + +namespace OpenGL { + +namespace { +Common::String getGLErrStr(GLenum error) { + switch (error) { + case GL_INVALID_ENUM: + return "GL_INVALID_ENUM"; + case GL_INVALID_VALUE: + return "GL_INVALID_VALUE"; + 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"; + } + + return Common::String::format("(Unknown GL error code 0x%X)", error); +} +} // End of anonymous namespace + +void checkGLError(const char *expr, const char *file, int line) { + GLenum error = glGetError(); + + if (error != GL_NO_ERROR) { + // We cannot use error here because we do not know whether we have a + // working screen or not. + warning("GL ERROR: %s on %s (%s:%d)", getGLErrStr(error).c_str(), expr, file, line); + } +} +} // End of namespace OpenGL + +#endif diff --git a/backends/graphics/opengl/debug.h b/backends/graphics/opengl/debug.h new file mode 100644 index 0000000000..ff6b678870 --- /dev/null +++ b/backends/graphics/opengl/debug.h @@ -0,0 +1,39 @@ +/* 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. + * + */ + +#ifndef BACKENDS_GRAPHICS_OPENGL_DEBUG_H +#define BACKENDS_GRAPHICS_OPENGL_DEBUG_H + +#define OPENGL_DEBUG + +#ifdef OPENGL_DEBUG + +namespace OpenGL { +void checkGLError(const char *expr, const char *file, int line); +} // End of namespace OpenGL + +#define GLCALL(x) do { (x); OpenGL::checkGLError(#x, __FILE__, __LINE__); } while (false) +#else +#define GLCALL(x) do { (x); } while (false) +#endif + +#endif diff --git a/backends/graphics/opengl/extensions.cpp b/backends/graphics/opengl/extensions.cpp new file mode 100644 index 0000000000..4482ef82b5 --- /dev/null +++ b/backends/graphics/opengl/extensions.cpp @@ -0,0 +1,48 @@ +/* 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 "backends/graphics/opengl/extensions.h" +#include "backends/graphics/opengl/opengl-sys.h" + +#include "common/tokenizer.h" + +namespace OpenGL { + +bool g_extNPOTSupported = false; + +void initializeGLExtensions() { + const char *extString = (const char *)glGetString(GL_EXTENSIONS); + + // Initialize default state. + g_extNPOTSupported = false; + + Common::StringTokenizer tokenizer(extString, " "); + while (!tokenizer.empty()) { + Common::String token = tokenizer.nextToken(); + + if (token == "GL_ARB_texture_non_power_of_two") { + g_extNPOTSupported = true; + } + } +} + +} // End of namespace OpenGL diff --git a/backends/graphics/opengl/extensions.h b/backends/graphics/opengl/extensions.h new file mode 100644 index 0000000000..87452429e2 --- /dev/null +++ b/backends/graphics/opengl/extensions.h @@ -0,0 +1,41 @@ +/* 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. + * + */ + +#ifndef BACKENDS_GRAPHICS_OPENGL_EXTENSIONS_H +#define BACKENDS_GRAPHICS_OPENGL_EXTENSIONS_H + +namespace OpenGL { + +/** + * Checks for availability of extensions we want to use and initializes them + * when available. + */ +void initializeGLExtensions(); + +/** + * Whether non power of two textures are supported + */ +extern bool g_extNPOTSupported; + +} // End of namespace OpenGL + +#endif diff --git a/backends/graphics/opengl/opengl-graphics.cpp b/backends/graphics/opengl/opengl-graphics.cpp new file mode 100644 index 0000000000..7ea880c92e --- /dev/null +++ b/backends/graphics/opengl/opengl-graphics.cpp @@ -0,0 +1,922 @@ +/* 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 "backends/graphics/opengl/opengl-graphics.h" +#include "backends/graphics/opengl/texture.h" +#include "backends/graphics/opengl/debug.h" +#include "backends/graphics/opengl/extensions.h" + +#include "common/textconsole.h" +#include "common/translation.h" +#include "common/algorithm.h" + +#include "graphics/conversion.h" + +namespace OpenGL { + +OpenGLGraphicsManager::OpenGLGraphicsManager() + : _currentState(), _oldState(), _transactionMode(kTransactionNone), _screenChangeID(1 << (sizeof(int) * 8 - 2)), + _outputScreenWidth(0), _outputScreenHeight(0), _displayX(0), _displayY(0), + _displayWidth(0), _displayHeight(0), _defaultFormat(), _defaultFormatAlpha(), + _gameScreen(nullptr), _gameScreenShakeOffset(0), _overlay(nullptr), + _overlayVisible(false), _cursor(nullptr), + _cursorX(0), _cursorY(0), _cursorHotspotX(0), _cursorHotspotY(0), _cursorHotspotXScaled(0), + _cursorHotspotYScaled(0), _cursorWidthScaled(0), _cursorHeightScaled(0), _cursorKeyColor(0), + _cursorVisible(false), _cursorDontScale(false), _cursorPaletteEnabled(false) { + memset(_gamePalette, 0, sizeof(_gamePalette)); +} + +OpenGLGraphicsManager::~OpenGLGraphicsManager() { + delete _gameScreen; + delete _overlay; + delete _cursor; +} + +bool OpenGLGraphicsManager::hasFeature(OSystem::Feature f) { + switch (f) { + case OSystem::kFeatureAspectRatioCorrection: + case OSystem::kFeatureCursorPalette: + return true; + + case OSystem::kFeatureOverlaySupportsAlpha: + return _defaultFormatAlpha.aBits() > 3; + + default: + return false; + } +} + +void OpenGLGraphicsManager::setFeatureState(OSystem::Feature f, bool enable) { + switch (f) { + case OSystem::kFeatureAspectRatioCorrection: + assert(_transactionMode != kTransactionNone); + _currentState.aspectRatioCorrection = enable; + break; + + case OSystem::kFeatureCursorPalette: + _cursorPaletteEnabled = enable; + updateCursorPalette(); + break; + + default: + break; + } +} + +bool OpenGLGraphicsManager::getFeatureState(OSystem::Feature f) { + switch (f) { + case OSystem::kFeatureAspectRatioCorrection: + return _currentState.aspectRatioCorrection; + + case OSystem::kFeatureCursorPalette: + return _cursorPaletteEnabled; + + default: + return false; + } +} + +namespace { + +const OSystem::GraphicsMode glGraphicsModes[] = { + { "opengl_linear", _s("OpenGL"), GFX_LINEAR }, + { "opengl_nearest", _s("OpenGL (No filtering)"), GFX_NEAREST }, + { nullptr, nullptr, 0 } +}; + +} // End of anonymous namespace + +const OSystem::GraphicsMode *OpenGLGraphicsManager::getSupportedGraphicsModes() const { + return glGraphicsModes; +} + +int OpenGLGraphicsManager::getDefaultGraphicsMode() const { + return GFX_LINEAR; +} + +bool OpenGLGraphicsManager::setGraphicsMode(int mode) { + assert(_transactionMode != kTransactionNone); + + switch (mode) { + case GFX_LINEAR: + case GFX_NEAREST: + _currentState.graphicsMode = mode; + + if (_gameScreen) { + _gameScreen->enableLinearFiltering(mode == GFX_LINEAR); + } + + if (_overlay) { + _overlay->enableLinearFiltering(mode == GFX_LINEAR); + } + + if (_cursor) { + _cursor->enableLinearFiltering(mode == GFX_LINEAR); + } + return true; + + default: + warning("OpenGLGraphicsManager::setGraphicsMode(%d): Unknown graphics mode", mode); + return false; + } +} + +int OpenGLGraphicsManager::getGraphicsMode() const { + return _currentState.graphicsMode; +} + +#ifdef USE_RGB_COLOR +Graphics::PixelFormat OpenGLGraphicsManager::getScreenFormat() const { + return _currentState.gameFormat; +} +#endif + +void OpenGLGraphicsManager::beginGFXTransaction() { + assert(_transactionMode == kTransactionNone); + + // Start a transaction. + _oldState = _currentState; + _transactionMode = kTransactionActive; +} + +OSystem::TransactionError OpenGLGraphicsManager::endGFXTransaction() { + assert(_transactionMode == kTransactionActive); + + uint transactionError = OSystem::kTransactionSuccess; + + bool setupNewGameScreen = false; + if ( _oldState.gameWidth != _currentState.gameWidth + || _oldState.gameHeight != _currentState.gameHeight) { + setupNewGameScreen = true; + } + +#ifdef USE_RGB_COLOR + if (_oldState.gameFormat != _currentState.gameFormat) { + setupNewGameScreen = true; + } + + // Check whether the requested format can actually be used. + Common::List<Graphics::PixelFormat> supportedFormats = getSupportedFormats(); + // In case the requested format is not usable we will fall back to CLUT8. + if (Common::find(supportedFormats.begin(), supportedFormats.end(), _currentState.gameFormat) == supportedFormats.end()) { + _currentState.gameFormat = Graphics::PixelFormat::createFormatCLUT8(); + transactionError |= OSystem::kTransactionFormatNotSupported; + } +#endif + + do { + uint requestedWidth = _currentState.gameWidth; + uint requestedHeight = _currentState.gameHeight; + const uint desiredAspect = getDesiredGameScreenAspect(); + requestedHeight = intToFrac(requestedWidth) / desiredAspect; + + if (!loadVideoMode(requestedWidth, requestedHeight, +#ifdef USE_RGB_COLOR + _currentState.gameFormat +#else + Graphics::PixelFormat::createFormatCLUT8() +#endif + )) { + if (_transactionMode == kTransactionActive) { + // Try to setup the old state in case its valid and is + // actually different from the new one. + if (_oldState.valid && _oldState != _currentState) { + // Give some hints on what failed to set up. + if ( _oldState.gameWidth != _currentState.gameWidth + || _oldState.gameHeight != _currentState.gameHeight) { + transactionError |= OSystem::kTransactionSizeChangeFailed; + } + +#ifdef USE_RGB_COLOR + if (_oldState.gameFormat != _currentState.gameFormat) { + transactionError |= OSystem::kTransactionFormatNotSupported; + } +#endif + + if (_oldState.aspectRatioCorrection != _currentState.aspectRatioCorrection) { + transactionError |= OSystem::kTransactionAspectRatioFailed; + } + + if (_oldState.graphicsMode != _currentState.graphicsMode) { + transactionError |= OSystem::kTransactionModeSwitchFailed; + } + + // Roll back to the old state. + _currentState = _oldState; + _transactionMode = kTransactionRollback; + + // Try to set up the old state. + continue; + } + } + + // DON'T use error(), as this tries to bring up the debug + // console, which WON'T WORK now that we might no have a + // proper screen. + warning("OpenGLGraphicsManager::endGFXTransaction: Could not load any graphics mode!"); + g_system->quit(); + } + + // In case we reach this we have a valid state, yay. + _transactionMode = kTransactionNone; + _currentState.valid = true; + } while (_transactionMode == kTransactionRollback); + + if (setupNewGameScreen) { + delete _gameScreen; + _gameScreen = nullptr; + + GLenum glIntFormat, glFormat, glType; +#ifdef USE_RGB_COLOR + if (_currentState.gameFormat.bytesPerPixel == 1) { +#endif + const bool supported = getGLPixelFormat(_defaultFormat, glIntFormat, glFormat, glType); + assert(supported); + _gameScreen = new TextureCLUT8(glIntFormat, glFormat, glType, _defaultFormat); + _gameScreen->setPalette(0, 255, _gamePalette); +#ifdef USE_RGB_COLOR + } else { + const bool supported = getGLPixelFormat(_currentState.gameFormat, glIntFormat, glFormat, glType); + assert(supported); + _gameScreen = new Texture(glIntFormat, glFormat, glType, _currentState.gameFormat); + } +#endif + + _gameScreen->allocate(_currentState.gameWidth, _currentState.gameHeight); + _gameScreen->enableLinearFiltering(_currentState.graphicsMode == GFX_LINEAR); + // We fill the screen to all black or index 0 for CLUT8. + if (_currentState.gameFormat.bytesPerPixel == 1) { + _gameScreen->fill(0); + } else { + _gameScreen->fill(_gameScreen->getSurface()->format.RGBToColor(0, 0, 0)); + } + } + + // Update our display area and cursor scaling. This makes sure we pick up + // aspect ratio correction and game screen changes correctly. + recalculateDisplayArea(); + recalculateCursorScaling(); + + // Something changed, so update the screen change ID. + ++_screenChangeID; + + // Since transactionError is a ORd list of TransactionErrors this is + // clearly wrong. But our API is simply broken. + return (OSystem::TransactionError)transactionError; +} + +int OpenGLGraphicsManager::getScreenChangeID() const { + return _screenChangeID; +} + +void OpenGLGraphicsManager::initSize(uint width, uint height, const Graphics::PixelFormat *format) { + Graphics::PixelFormat requestedFormat; +#ifdef USE_RGB_COLOR + if (!format) { + requestedFormat = Graphics::PixelFormat::createFormatCLUT8(); + } else { + requestedFormat = *format; + } + _currentState.gameFormat = requestedFormat; +#endif + + _currentState.gameWidth = width; + _currentState.gameHeight = height; +} + +int16 OpenGLGraphicsManager::getWidth() { + return _currentState.gameWidth; +} + +int16 OpenGLGraphicsManager::getHeight() { + return _currentState.gameHeight; +} + +void OpenGLGraphicsManager::copyRectToScreen(const void *buf, int pitch, int x, int y, int w, int h) { + _gameScreen->copyRectToTexture(x, y, w, h, buf, pitch); +} + +void OpenGLGraphicsManager::fillScreen(uint32 col) { + // FIXME: This does not conform to the OSystem specs because fillScreen + // is always taking CLUT8 color values and use color indexed mode. This is, + // however, plain odd and probably was a forgotten when we introduced + // RGB support. Thus, we simply do the "sane" thing here and hope OSystem + // gets fixed one day. + _gameScreen->fill(col); +} + +void OpenGLGraphicsManager::setShakePos(int shakeOffset) { + _gameScreenShakeOffset = shakeOffset; +} + +void OpenGLGraphicsManager::updateScreen() { + if (!_gameScreen) { + return; + } + + // Clear the screen buffer + GLCALL(glClear(GL_COLOR_BUFFER_BIT)); + + const GLfloat shakeOffset = _gameScreenShakeOffset * (GLfloat)_displayHeight / _gameScreen->getHeight(); + + // First step: Draw the (virtual) game screen. + glPushMatrix(); + + // Adjust game screen shake position + GLCALL(glTranslatef(0, shakeOffset, 0)); + + // Draw the game screen + _gameScreen->draw(_displayX, _displayY, _displayWidth, _displayHeight); + + glPopMatrix(); + + // Second step: Draw the overlay if visible. + if (_overlayVisible) { + _overlay->draw(0, 0, _outputScreenWidth, _outputScreenHeight); + } + + // Third step: Draw the cursor if visible. + if (_cursorVisible && _cursor) { + glPushMatrix(); + + // Adjust game screen shake position, but only when the overlay is not + // visible. + if (!_overlayVisible) { + GLCALL(glTranslatef(0, shakeOffset, 0)); + } + + _cursor->draw(_cursorX - _cursorHotspotXScaled, _cursorY - _cursorHotspotYScaled, + _cursorWidthScaled, _cursorHeightScaled); + + glPopMatrix(); + } +} + +Graphics::Surface *OpenGLGraphicsManager::lockScreen() { + return _gameScreen->getSurface(); +} + +void OpenGLGraphicsManager::unlockScreen() { + _gameScreen->flagDirty(); +} + +void OpenGLGraphicsManager::setFocusRectangle(const Common::Rect& rect) { +} + +void OpenGLGraphicsManager::clearFocusRectangle() { +} + +int16 OpenGLGraphicsManager::getOverlayWidth() { + if (_overlay) { + return _overlay->getWidth(); + } else { + return 0; + } +} + +int16 OpenGLGraphicsManager::getOverlayHeight() { + if (_overlay) { + return _overlay->getHeight(); + } else { + return 0; + } +} + +void OpenGLGraphicsManager::showOverlay() { + _overlayVisible = true; +} + +void OpenGLGraphicsManager::hideOverlay() { + _overlayVisible = false; +} + +Graphics::PixelFormat OpenGLGraphicsManager::getOverlayFormat() const { + return _overlay->getFormat(); +} + +void OpenGLGraphicsManager::copyRectToOverlay(const void *buf, int pitch, int x, int y, int w, int h) { + _overlay->copyRectToTexture(x, y, w, h, buf, pitch); +} + +void OpenGLGraphicsManager::clearOverlay() { + _overlay->fill(0); +} + +void OpenGLGraphicsManager::grabOverlay(void *buf, int pitch) { + const Graphics::Surface *overlayData = _overlay->getSurface(); + + const byte *src = (const byte *)overlayData->getPixels(); + byte *dst = (byte *)buf; + + for (uint h = overlayData->h; h > 0; --h) { + memcpy(dst, src, overlayData->w * overlayData->format.bytesPerPixel); + dst += pitch; + src += overlayData->pitch; + } +} + +bool OpenGLGraphicsManager::showMouse(bool visible) { + bool last = _cursorVisible; + _cursorVisible = visible; + return last; +} + +void OpenGLGraphicsManager::warpMouse(int x, int y) { + int16 currentX = _cursorX; + int16 currentY = _cursorY; + adjustMousePosition(currentX, currentY); + + // Check whether the (virtual) coordinate actually changed. If not, then + // simply do nothing. This avoids ugly "jittering" due to the actual + // output screen having a bigger resolution than the virtual coordinates. + if (currentX == x && currentY == y) { + return; + } + + // Scale the virtual coordinates into actual physical coordinates. + if (_overlayVisible) { + if (!_overlay) { + return; + } + + // It might be confusing that we actually have to handle something + // here when the overlay is visible. This is because for very small + // resolutions we have a minimal overlay size and have to adjust + // for that. + x = (x * _outputScreenWidth) / _overlay->getWidth(); + y = (y * _outputScreenHeight) / _overlay->getHeight(); + } else { + if (!_gameScreen) { + return; + } + + x = (x * _displayWidth) / _gameScreen->getWidth(); + y = (y * _displayHeight) / _gameScreen->getHeight(); + + x += _displayX; + y += _displayY; + } + + setMousePosition(x, y); + setInternalMousePosition(x, y); +} + +namespace { +template<typename DstPixel, typename SrcPixel> +void applyColorKey(DstPixel *dst, const SrcPixel *src, uint w, uint h, uint dstPitch, uint srcPitch, SrcPixel keyColor, DstPixel alphaMask) { + const uint srcAdd = srcPitch - w * sizeof(SrcPixel); + const uint dstAdd = dstPitch - w * sizeof(DstPixel); + + while (h-- > 0) { + for (uint x = w; x > 0; --x, ++dst, ++src) { + if (*src == keyColor) { + *dst &= ~alphaMask; + } + } + + dst = (DstPixel *)((byte *)dst + dstAdd); + src = (const SrcPixel *)((const byte *)src + srcAdd); + } +} +} // End of anonymous namespace + +void OpenGLGraphicsManager::setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format) { + Graphics::PixelFormat inputFormat; +#ifdef USE_RGB_COLOR + if (format) { + inputFormat = *format; + } else { + inputFormat = Graphics::PixelFormat::createFormatCLUT8(); + } +#else + inputFormat = Graphics::PixelFormat::createFormatCLUT8(); +#endif + + // In case the color format has changed we will need to create the texture. + if (!_cursor || _cursor->getFormat() != inputFormat) { + delete _cursor; + _cursor = nullptr; + + GLenum glIntFormat, glFormat, glType; + + if (inputFormat.bytesPerPixel == 1) { + // In case this is not supported this is a serious programming + // error and the assert a bit below will trigger! + const bool supported = getGLPixelFormat(_defaultFormatAlpha, glIntFormat, glFormat, glType); + assert(supported); + _cursor = new TextureCLUT8(glIntFormat, glFormat, glType, _defaultFormatAlpha); + } else { + // Try to use the format specified as input directly. We can only + // do so when it actually has alpha bits. + if (inputFormat.aBits() != 0 && getGLPixelFormat(inputFormat, glIntFormat, glFormat, glType)) { + _cursor = new Texture(glIntFormat, glFormat, glType, inputFormat); + } + + // Otherwise fall back to the default alpha format. + if (!_cursor) { + const bool supported = getGLPixelFormat(_defaultFormatAlpha, glIntFormat, glFormat, glType); + assert(supported); + _cursor = new Texture(glIntFormat, glFormat, glType, _defaultFormatAlpha); + } + } + + assert(_cursor); + _cursor->enableLinearFiltering(_currentState.graphicsMode == GFX_LINEAR); + } + + _cursorKeyColor = keycolor; + _cursorHotspotX = hotspotX; + _cursorHotspotY = hotspotY; + _cursorDontScale = dontScale; + + _cursor->allocate(w, h); + if (inputFormat.bytesPerPixel == 1) { + // For CLUT8 cursors we can simply copy the input data into the + // texture. + _cursor->copyRectToTexture(0, 0, w, h, buf, w * inputFormat.bytesPerPixel); + } else { + // Otherwise it is a bit more ugly because we have to handle a key + // color properly. + + Graphics::Surface *dst = _cursor->getSurface(); + const uint srcPitch = w * inputFormat.bytesPerPixel; + + // In case the actual cursor format differs from the requested format + // we will need to convert the format. This might be the case because + // the cursor format does not have any alpha bits. + if (dst->format != inputFormat) { + Graphics::crossBlit((byte *)dst->getPixels(), (const byte *)buf, dst->pitch, srcPitch, + w, h, dst->format, inputFormat); + } + + // We apply the color key by setting the alpha bits of the pixels to + // fully transparent. + const uint32 aMask = (0xFF >> dst->format.aLoss) << dst->format.aShift; + if (dst->format.bytesPerPixel == 2) { + if (inputFormat.bytesPerPixel == 2) { + applyColorKey<uint16, uint16>((uint16 *)dst->getPixels(), (const uint16 *)buf, w, h, + dst->pitch, srcPitch, keycolor, aMask); + } else if (inputFormat.bytesPerPixel == 4) { + applyColorKey<uint16, uint32>((uint16 *)dst->getPixels(), (const uint32 *)buf, w, h, + dst->pitch, srcPitch, keycolor, aMask); + } + } else { + if (inputFormat.bytesPerPixel == 2) { + applyColorKey<uint32, uint16>((uint32 *)dst->getPixels(), (const uint16 *)buf, w, h, + dst->pitch, srcPitch, keycolor, aMask); + } else if (inputFormat.bytesPerPixel == 4) { + applyColorKey<uint32, uint32>((uint32 *)dst->getPixels(), (const uint32 *)buf, w, h, + dst->pitch, srcPitch, keycolor, aMask); + } + } + + // Flag the texture as dirty. + _cursor->flagDirty(); + } + + // In case we actually use a palette set that up properly. + if (inputFormat.bytesPerPixel == 1) { + updateCursorPalette(); + } + + // Update the scaling. + recalculateCursorScaling(); +} + +void OpenGLGraphicsManager::setCursorPalette(const byte *colors, uint start, uint num) { + // FIXME: For some reason client code assumes that usage of this function + // automatically enables the cursor palette. + _cursorPaletteEnabled = true; + + memcpy(_cursorPalette + start * 3, colors, num * 3); + updateCursorPalette(); +} + +void OpenGLGraphicsManager::displayMessageOnOSD(const char *msg) { +} + +void OpenGLGraphicsManager::setPalette(const byte *colors, uint start, uint num) { + assert(_gameScreen->hasPalette()); + + memcpy(_gamePalette + start * 3, colors, num * 3); + _gameScreen->setPalette(start, num, colors); + + // We might need to update the cursor palette here. + updateCursorPalette(); +} + +void OpenGLGraphicsManager::grabPalette(byte *colors, uint start, uint num) { + assert(_gameScreen->hasPalette()); + + memcpy(colors, _gamePalette + start * 3, num * 3); +} + +void OpenGLGraphicsManager::setActualScreenSize(uint width, uint height) { + _outputScreenWidth = width; + _outputScreenHeight = height; + + // Setup coordinates system. + GLCALL(glViewport(0, 0, _outputScreenWidth, _outputScreenHeight)); + + GLCALL(glMatrixMode(GL_PROJECTION)); + GLCALL(glLoadIdentity()); +#ifdef USE_GLES + GLCALL(glOrthof(0, _outputScreenWidth, _outputScreenHeight, 0, -1, 1)); +#else + GLCALL(glOrtho(0, _outputScreenWidth, _outputScreenHeight, 0, -1, 1)); +#endif + GLCALL(glMatrixMode(GL_MODELVIEW)); + GLCALL(glLoadIdentity()); + + // HACK: We limit the minimal overlay size to 256x200, which is the + // minimum of the dimensions of the two resolutions 256x240 (NES) and + // 320x200 (many DOS games use this). This hopefully assure that our + // GUI has working layouts. + const uint overlayWidth = MAX<uint>(width, 256); + const uint overlayHeight = MAX<uint>(height, 200); + + if (!_overlay || _overlay->getFormat() != _defaultFormatAlpha) { + delete _overlay; + _overlay = nullptr; + + GLenum glIntFormat, glFormat, glType; + const bool supported = getGLPixelFormat(_defaultFormatAlpha, glIntFormat, glFormat, glType); + assert(supported); + _overlay = new Texture(glIntFormat, glFormat, glType, _defaultFormatAlpha); + _overlay->enableLinearFiltering(_currentState.graphicsMode == GFX_LINEAR); + } + _overlay->allocate(overlayWidth, overlayHeight); + _overlay->fill(0); + + // Re-setup the scaling for the screen and cursor + recalculateDisplayArea(); + recalculateCursorScaling(); + + // Something changed, so update the screen change ID. + ++_screenChangeID; +} + +void OpenGLGraphicsManager::notifyContextChange(const Graphics::PixelFormat &defaultFormat, const Graphics::PixelFormat &defaultFormatAlpha) { + // Initialize all extensions. + initializeGLExtensions(); + + // Disable 3D properties. + GLCALL(glDisable(GL_CULL_FACE)); + GLCALL(glDisable(GL_DEPTH_TEST)); + GLCALL(glDisable(GL_LIGHTING)); + GLCALL(glDisable(GL_FOG)); + GLCALL(glDisable(GL_DITHER)); + GLCALL(glShadeModel(GL_FLAT)); + GLCALL(glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST)); + + // Default to black as clear color. + GLCALL(glClearColor(0.0f, 0.0f, 0.0f, 0.0f)); + GLCALL(glColor4f(1.0f, 1.0f, 1.0f, 1.0f)); + + // Setup alpha blend (for overlay and cursor). + GLCALL(glEnable(GL_BLEND)); + GLCALL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + + // Enable rendering with vertex and coord arrays. + GLCALL(glEnableClientState(GL_VERTEX_ARRAY)); + GLCALL(glEnableClientState(GL_TEXTURE_COORD_ARRAY)); + + GLCALL(glEnable(GL_TEXTURE_2D)); + + // Refresh the output screen dimensions if some are set up. + if (_outputScreenWidth != 0 && _outputScreenHeight != 0) { + setActualScreenSize(_outputScreenWidth, _outputScreenHeight); + } + + // TODO: Should we try to convert textures into one of those formats if + // possible? For example, when _gameScreen is CLUT8 we might want to use + // defaultFormat now. + _defaultFormat = defaultFormat; + _defaultFormatAlpha = defaultFormatAlpha; + + if (_gameScreen) { + _gameScreen->recreateInternalTexture(); + } + + if (_overlay) { + _overlay->recreateInternalTexture(); + } + + if (_cursor) { + _cursor->recreateInternalTexture(); + } +} + +void OpenGLGraphicsManager::adjustMousePosition(int16 &x, int16 &y) { + if (_overlayVisible) { + // It might be confusing that we actually have to handle something + // here when the overlay is visible. This is because for very small + // resolutions we have a minimal overlay size and have to adjust + // for that. + if (_overlay) { + x = (x * _overlay->getWidth()) / _outputScreenWidth; + y = (y * _overlay->getHeight()) / _outputScreenHeight; + } + } else if (_gameScreen) { + x -= _displayX; + y -= _displayY; + + const int16 width = _gameScreen->getWidth(); + const int16 height = _gameScreen->getHeight(); + + x = (x * width) / _displayWidth; + y = (y * height) / _displayHeight; + + // Make sure we only supply valid coordinates. + x = CLIP<int16>(x, 0, width - 1); + y = CLIP<int16>(y, 0, height - 1); + } +} + +bool OpenGLGraphicsManager::getGLPixelFormat(const Graphics::PixelFormat &pixelFormat, GLenum &glIntFormat, GLenum &glFormat, GLenum &glType) const { + if (pixelFormat == Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0)) { // RGBA8888 + glIntFormat = GL_RGBA; + glFormat = GL_RGBA; + glType = GL_UNSIGNED_INT_8_8_8_8; + return true; + } else if (pixelFormat == Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0)) { // RGB565 + glIntFormat = GL_RGB; + glFormat = GL_RGB; + glType = GL_UNSIGNED_SHORT_5_6_5; + return true; + } else if (pixelFormat == Graphics::PixelFormat(2, 5, 5, 5, 1, 11, 6, 1, 0)) { // RGBA5551 + glIntFormat = GL_RGBA; + glFormat = GL_RGBA; + glType = GL_UNSIGNED_SHORT_5_5_5_1; + return true; + } else if (pixelFormat == Graphics::PixelFormat(2, 4, 4, 4, 4, 12, 8, 4, 0)) { // RGBA4444 + glIntFormat = GL_RGBA; + glFormat = GL_RGBA; + glType = GL_UNSIGNED_SHORT_4_4_4_4; + return true; +#ifndef USE_GLES + } else if (pixelFormat == Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0)) { // RGB555 + // GL_BGRA does not exist in every GLES implementation so should not be configured if + // USE_GLES is set. + glIntFormat = GL_RGB; + glFormat = GL_BGRA; + glType = GL_UNSIGNED_SHORT_1_5_5_5_REV; + return true; + } else if (pixelFormat == Graphics::PixelFormat(4, 8, 8, 8, 8, 16, 8, 0, 24)) { // ARGB8888 + glIntFormat = GL_RGBA; + glFormat = GL_BGRA; + glType = GL_UNSIGNED_INT_8_8_8_8_REV; + return true; + } else if (pixelFormat == Graphics::PixelFormat(2, 4, 4, 4, 4, 8, 4, 0, 12)) { // ARGB4444 + glIntFormat = GL_RGBA; + glFormat = GL_BGRA; + glType = GL_UNSIGNED_SHORT_4_4_4_4_REV; + return true; + } else if (pixelFormat == Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24)) { // ABGR8888 + glIntFormat = GL_RGBA; + glFormat = GL_RGBA; + glType = GL_UNSIGNED_INT_8_8_8_8_REV; + return true; + } else if (pixelFormat == Graphics::PixelFormat(4, 8, 8, 8, 8, 8, 16, 24, 0)) { // BGRA8888 + glIntFormat = GL_RGBA; + glFormat = GL_BGRA; + glType = GL_UNSIGNED_INT_8_8_8_8; + return true; + } else if (pixelFormat == Graphics::PixelFormat(2, 5, 6, 5, 0, 0, 5, 11, 0)) { // BGR565 + glIntFormat = GL_RGB; + glFormat = GL_BGR; + glType = GL_UNSIGNED_SHORT_5_6_5; + return true; + } else if (pixelFormat == Graphics::PixelFormat(2, 5, 5, 5, 1, 1, 6, 11, 0)) { // BGRA5551 + glIntFormat = GL_RGBA; + glFormat = GL_BGRA; + glType = GL_UNSIGNED_SHORT_5_5_5_1; + return true; + } else if (pixelFormat == Graphics::PixelFormat(2, 4, 4, 4, 4, 0, 4, 8, 12)) { // ABGR4444 + glIntFormat = GL_RGBA; + glFormat = GL_RGBA; + glType = GL_UNSIGNED_SHORT_4_4_4_4_REV; + return true; + } else if (pixelFormat == Graphics::PixelFormat(2, 4, 4, 4, 4, 4, 8, 12, 0)) { // BGRA4444 + glIntFormat = GL_RGBA; + glFormat = GL_BGRA; + glType = GL_UNSIGNED_SHORT_4_4_4_4; + return true; +#endif + } else { + return false; + } +} + +frac_t OpenGLGraphicsManager::getDesiredGameScreenAspect() const { + const uint width = _currentState.gameWidth; + const uint height = _currentState.gameHeight; + + if (_currentState.aspectRatioCorrection) { + // 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. + if ((width == 320 && height == 200) || (width == 640 && height == 400)) { + return intToFrac(4) / 3; + } + } + + return intToFrac(width) / height; +} + +void OpenGLGraphicsManager::recalculateDisplayArea() { + if (!_gameScreen || _outputScreenHeight == 0) { + return; + } + + const frac_t outputAspect = intToFrac(_outputScreenWidth) / _outputScreenHeight; + const frac_t desiredAspect = getDesiredGameScreenAspect(); + + _displayWidth = _outputScreenWidth; + _displayHeight = _outputScreenHeight; + + // Adjust one dimension for mantaining the aspect ratio. + if (outputAspect < desiredAspect) { + _displayHeight = intToFrac(_displayWidth) / desiredAspect; + } else if (outputAspect > desiredAspect) { + _displayWidth = fracToInt(_displayHeight * desiredAspect); + } + + // We center the screen in the middle for now. + _displayX = (_outputScreenWidth - _displayWidth ) / 2; + _displayY = (_outputScreenHeight - _displayHeight) / 2; +} + +void OpenGLGraphicsManager::updateCursorPalette() { + if (!_cursor || !_cursor->hasPalette()) { + return; + } + + if (_cursorPaletteEnabled) { + _cursor->setPalette(0, 256, _cursorPalette); + } else { + _cursor->setPalette(0, 256, _gamePalette); + } + + // We remove all alpha bits from the palette entry of the color key. + // This makes sure its properly handled as color key. + const Graphics::PixelFormat &hardwareFormat = _cursor->getHardwareFormat(); + const uint32 aMask = (0xFF >> hardwareFormat.aLoss) << hardwareFormat.aShift; + + if (hardwareFormat.bytesPerPixel == 2) { + uint16 *palette = (uint16 *)_cursor->getPalette() + _cursorKeyColor; + *palette &= ~aMask; + } else if (hardwareFormat.bytesPerPixel == 4) { + uint32 *palette = (uint32 *)_cursor->getPalette() + _cursorKeyColor; + *palette &= ~aMask; + } else { + warning("OpenGLGraphicsManager::updateCursorPalette: Unsupported pixel depth %d", hardwareFormat.bytesPerPixel); + } +} + +void OpenGLGraphicsManager::recalculateCursorScaling() { + if (!_cursor || !_gameScreen) { + return; + } + + // By default we use the unscaled versions. + _cursorHotspotXScaled = _cursorHotspotX; + _cursorHotspotYScaled = _cursorHotspotY; + _cursorWidthScaled = _cursor->getWidth(); + _cursorHeightScaled = _cursor->getHeight(); + + // In case scaling is actually enabled we will scale the cursor according + // to the game screen. + if (!_cursorDontScale) { + const uint screenScaleFactorX = _displayWidth * 10000 / _gameScreen->getWidth(); + const uint screenScaleFactorY = _displayHeight * 10000 / _gameScreen->getHeight(); + + _cursorHotspotXScaled = (_cursorHotspotXScaled * screenScaleFactorX) / 10000; + _cursorWidthScaled = (_cursorWidthScaled * screenScaleFactorX) / 10000; + + _cursorHotspotYScaled = (_cursorHotspotYScaled * screenScaleFactorY) / 10000; + _cursorHeightScaled = (_cursorHeightScaled * screenScaleFactorY) / 10000; + } +} + +} // End of namespace OpenGL diff --git a/backends/graphics/opengl/opengl-graphics.h b/backends/graphics/opengl/opengl-graphics.h new file mode 100644 index 0000000000..e21bda4b8a --- /dev/null +++ b/backends/graphics/opengl/opengl-graphics.h @@ -0,0 +1,418 @@ +/* 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. + * + */ + +#ifndef BACKENDS_GRAPHICS_OPENGL_OPENGL_GRAPHICS_H +#define BACKENDS_GRAPHICS_OPENGL_OPENGL_GRAPHICS_H + +#include "backends/graphics/opengl/opengl-sys.h" +#include "backends/graphics/graphics.h" + +#include "common/frac.h" + +namespace OpenGL { + +class Texture; + +enum { + GFX_LINEAR = 0, + GFX_NEAREST = 1 +}; + +class OpenGLGraphicsManager : public GraphicsManager { +public: + OpenGLGraphicsManager(); + virtual ~OpenGLGraphicsManager(); + + // GraphicsManager API + virtual bool hasFeature(OSystem::Feature f); + virtual void setFeatureState(OSystem::Feature f, bool enable); + virtual bool getFeatureState(OSystem::Feature f); + + 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 beginGFXTransaction(); + virtual OSystem::TransactionError endGFXTransaction(); + + virtual int getScreenChangeID() const; + + virtual void initSize(uint width, uint height, const Graphics::PixelFormat *format); + + virtual int16 getWidth(); + virtual int16 getHeight(); + + virtual void copyRectToScreen(const void *buf, int pitch, int x, int y, int w, int h); + virtual void fillScreen(uint32 col); + + virtual void setShakePos(int shakeOffset); + + virtual void updateScreen(); + + virtual Graphics::Surface *lockScreen(); + virtual void unlockScreen(); + + virtual void setFocusRectangle(const Common::Rect& rect); + virtual void clearFocusRectangle(); + + virtual int16 getOverlayWidth(); + virtual int16 getOverlayHeight(); + + virtual void showOverlay(); + virtual void hideOverlay(); + + virtual Graphics::PixelFormat getOverlayFormat() const; + + virtual void copyRectToOverlay(const void *buf, int pitch, int x, int y, int w, int h); + virtual void clearOverlay(); + virtual void grabOverlay(void *buf, int pitch); + + virtual bool showMouse(bool visible); + virtual void warpMouse(int x, int y); + virtual void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format); + virtual void setCursorPalette(const byte *colors, uint start, uint num); + + virtual void displayMessageOnOSD(const char *msg); + + // PaletteManager interface + virtual void setPalette(const byte *colors, uint start, uint num); + virtual void grabPalette(byte *colors, uint start, uint num); + +protected: + /** + * Set up the actual screen size available for the OpenGL code to do any + * drawing. + * + * @param width The width of the screen. + * @param height The height of the screen. + */ + void setActualScreenSize(uint width, uint height); + + /** + * Notify the manager of a OpenGL context change. This should be the first + * thing to call when you create an OpenGL (ES) context! + * + * @param defaultFormat The new default format for the game screen + * (this is used for the CLUT8 game screens). + * @param defaultFromatAlpha The new default format with an alpha channel + * (this is used for the overlay and cursor). + */ + void notifyContextChange(const Graphics::PixelFormat &defaultFormat, const Graphics::PixelFormat &defaultFormatAlpha); + + /** + * Adjust the physical mouse coordinates according to the currently visible screen. + */ + void adjustMousePosition(int16 &x, int16 &y); + + /** + * Set up the mouse position for graphics output. + * + * @param x X coordinate in physical coordinates. + * @param y Y coordinate in physical coordinates. + */ + void setMousePosition(int x, int y) { _cursorX = x; _cursorY = y; } + + /** + * Set up the mouse position for the (event) system. + * + * @param x X coordinate in physical coordinates. + * @param y Y coordinate in physical coordinates. + */ + virtual void setInternalMousePosition(int x, int y) = 0; + +private: + // + // Transaction support + // + struct VideoState { + VideoState() : valid(false), gameWidth(0), gameHeight(0), +#ifdef USE_RGB_COLOR + gameFormat(), +#endif + aspectRatioCorrection(false), graphicsMode(GFX_LINEAR) { + } + + bool valid; + + uint gameWidth, gameHeight; +#ifdef USE_RGB_COLOR + Graphics::PixelFormat gameFormat; +#endif + bool aspectRatioCorrection; + int graphicsMode; + + bool operator==(const VideoState &right) { + return gameWidth == right.gameWidth && gameHeight == right.gameHeight +#ifdef USE_RGB_COLOR + && gameFormat == right.gameFormat +#endif + && aspectRatioCorrection == right.aspectRatioCorrection + && graphicsMode == right.graphicsMode; + } + + bool operator!=(const VideoState &right) { + return !(*this == right); + } + }; + + /** + * The currently setup video state. + */ + VideoState _currentState; + + /** + * The old video state used when doing a transaction rollback. + */ + VideoState _oldState; + +protected: + enum TransactionMode { + kTransactionNone = 0, + kTransactionActive = 1, + kTransactionRollback = 2 + }; + + TransactionMode getTransactionMode() const { return _transactionMode; } + +private: + /** + * The current transaction mode. + */ + TransactionMode _transactionMode; + + /** + * The current screen change ID. + */ + int _screenChangeID; + +protected: + /** + * Set up the requested video mode. This takes parameters which describe + * what resolution the game screen requests (this is possibly aspect ratio + * corrected!). + * + * A sub-class should take these parameters as hints. It might very well + * set up a mode which it thinks suites the situation best. + * + * @parma requestedWidth This is the requested actual game screen width. + * @param requestedHeight This is the requested actual game screen height. + * @param format This is the requested pixel format of the virtual game screen. + * @return true on success, false otherwise + */ + virtual bool loadVideoMode(uint requestedWidth, uint requestedHeight, const Graphics::PixelFormat &format) = 0; + +private: + // + // OpenGL utilities + // + + /** + * Try to determine the internal parameters for a given pixel format. + * + * @return true when the format can be used, false otherwise. + */ + bool getGLPixelFormat(const Graphics::PixelFormat &pixelFormat, GLenum &glIntFormat, GLenum &glFormat, GLenum &glType) const; + + // + // Actual hardware screen + // + + /** + * The width of the physical output. + */ + uint _outputScreenWidth; + + /** + * The height of the physical output. + */ + uint _outputScreenHeight; + + /** + * @return The desired aspect of the game screen. + */ + frac_t getDesiredGameScreenAspect() const; + + /** + * Recalculates the area used to display the game screen. + */ + void recalculateDisplayArea(); + + /** + * The X coordinate of the game screen. + */ + uint _displayX; + + /** + * The Y coordinate of the game screen. + */ + uint _displayY; + + /** + * The width of the game screen in physical coordinates. + */ + uint _displayWidth; + + /** + * The height of the game screen in physical coordinates. + */ + uint _displayHeight; + + /** + * The default pixel format of the backend. + */ + Graphics::PixelFormat _defaultFormat; + + /** + * The default pixel format with an alpha channel. + */ + Graphics::PixelFormat _defaultFormatAlpha; + + // + // Game screen + // + + /** + * The virtual game screen. + */ + Texture *_gameScreen; + + /** + * The game palette if in CLUT8 mode. + */ + byte _gamePalette[3 * 256]; + + /** + * The offset by which the screen is moved vertically. + */ + int _gameScreenShakeOffset; + + // + // Overlay + // + + /** + * The overlay screen. + */ + Texture *_overlay; + + /** + * Whether the overlay is visible or not. + */ + bool _overlayVisible; + + // + // Cursor + // + + /** + * Set up the correct cursor palette. + */ + void updateCursorPalette(); + + /** + * The cursor image. + */ + Texture *_cursor; + + /** + * X coordinate of the cursor in phyiscal coordinates. + */ + uint _cursorX; + + /** + * Y coordinate of the cursor in physical coordinates. + */ + uint _cursorY; + + /** + * The X offset for the cursor hotspot in unscaled coordinates. + */ + uint _cursorHotspotX; + + /** + * The Y offset for the cursor hotspot in unscaled coordinates. + */ + uint _cursorHotspotY; + + /** + * Recalculate the cursor scaling. Scaling is always done according to + * the game screen. + */ + void recalculateCursorScaling(); + + /** + * The X offset for the cursor hotspot in scaled coordinates. + */ + uint _cursorHotspotXScaled; + + /** + * The Y offset for the cursor hotspot in scaled coordinates. + */ + uint _cursorHotspotYScaled; + + /** + * The width of the cursor scaled coordinates. + */ + uint _cursorWidthScaled; + + /** + * The height of the cursor scaled coordinates. + */ + uint _cursorHeightScaled; + + /** + * The key color. + */ + uint32 _cursorKeyColor; + + /** + * Whether the cursor is actually visible. + */ + bool _cursorVisible; + + /** + * Whether no cursor scaling should be applied. + */ + bool _cursorDontScale; + + /** + * Whether the special cursor palette is enabled. + */ + bool _cursorPaletteEnabled; + + /** + * The special cursor palette in case enabled. + */ + byte _cursorPalette[3 * 256]; +}; + +} // End of namespace OpenGL + +#endif diff --git a/backends/graphics/opengl/opengl-sys.h b/backends/graphics/opengl/opengl-sys.h new file mode 100644 index 0000000000..a3524b28d2 --- /dev/null +++ b/backends/graphics/opengl/opengl-sys.h @@ -0,0 +1,57 @@ +/* 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. + * + */ + +#ifndef BACKENDS_GRAPHICS_OPENGL_OPENGL_H +#define BACKENDS_GRAPHICS_OPENGL_OPENGL_H + +// The purpose of this header is to include the OpenGL headers in an uniform +// fashion. A notable example for a non standard port is the Tizen port. + +#include "common/scummsys.h" + +#ifdef WIN32 +#if defined(ARRAYSIZE) && !defined(_WINDOWS_) +#undef ARRAYSIZE +#endif +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#undef ARRAYSIZE +#endif + +// HACK: In case common/util.h has been included already we need to make sure +// to define ARRAYSIZE again in case of Windows. +#if !defined(ARRAYSIZE) && defined(COMMON_UTIL_H) +#define ARRAYSIZE(x) ((int)(sizeof(x) / sizeof(x[0]))) +#endif + +#if defined(TIZEN) +#include <FGraphicsOpengl.h> +using namespace Tizen::Graphics::Opengl; +#elif defined(USE_GLES) +#include <GLES/gl.h> +#elif defined(SDL_BACKEND) +#include <SDL_opengl.h> +#else +#include <GL/gl.h> +#endif + +#endif diff --git a/backends/graphics/opengl/texture.cpp b/backends/graphics/opengl/texture.cpp new file mode 100644 index 0000000000..3067aa7d1c --- /dev/null +++ b/backends/graphics/opengl/texture.cpp @@ -0,0 +1,305 @@ +/* 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 "backends/graphics/opengl/texture.h" +#include "backends/graphics/opengl/extensions.h" +#include "backends/graphics/opengl/debug.h" + +#include "common/rect.h" +#include "common/textconsole.h" + +namespace OpenGL { + +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; +} + +Texture::Texture(GLenum glIntFormat, GLenum glFormat, GLenum glType, const Graphics::PixelFormat &format) + : _glIntFormat(glIntFormat), _glFormat(glFormat), _glType(glType), _format(format), _glFilter(GL_NEAREST), + _glTexture(0), _textureData(), _userPixelData(), _allDirty(false) { + recreateInternalTexture(); +} + +Texture::~Texture() { + releaseInternalTexture(); + _textureData.free(); +} + +void Texture::releaseInternalTexture() { + GLCALL(glDeleteTextures(1, &_glTexture)); + _glTexture = 0; +} + +void Texture::recreateInternalTexture() { + // Get a new texture name. + GLCALL(glGenTextures(1, &_glTexture)); + + // Set up all texture parameters. + GLCALL(glBindTexture(GL_TEXTURE_2D, _glTexture)); + GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); + GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _glFilter)); + GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _glFilter)); + GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); + GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); + + // In case there is an actual texture setup we reinitialize it. + if (_textureData.getPixels()) { + // Mark dirts such that it will be completely refreshed the next time. + flagDirty(); + } +} + +void Texture::enableLinearFiltering(bool enable) { + if (enable) { + _glFilter = GL_LINEAR; + } else { + _glFilter = GL_NEAREST; + } + + GLCALL(glBindTexture(GL_TEXTURE_2D, _glTexture)); + + GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _glFilter)); + GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _glFilter)); +} + +void Texture::allocate(uint width, uint height) { + uint texWidth = width, texHeight = height; + if (!g_extNPOTSupported) { + texWidth = nextHigher2(texWidth); + texHeight = nextHigher2(texHeight); + } + + // In case the needed texture dimension changed we will reinitialize the + // texture. + if (texWidth != _textureData.w || texHeight != _textureData.h) { + // Create a buffer for the texture data. + _textureData.create(texWidth, texHeight, _format); + } + + // Create a sub-buffer for raw access. + _userPixelData = _textureData.getSubArea(Common::Rect(width, height)); +} + +void Texture::copyRectToTexture(uint x, uint y, uint w, uint h, const void *srcPtr, uint srcPitch) { + Graphics::Surface *dstSurf = getSurface(); + assert(x + w <= dstSurf->w); + assert(y + h <= dstSurf->h); + + const byte *src = (const byte *)srcPtr; + byte *dst = (byte *)dstSurf->getBasePtr(x, y); + const uint pitch = dstSurf->pitch; + const uint bytesPerPixel = dstSurf->format.bytesPerPixel; + + if (srcPitch == pitch && x == 0 && w == dstSurf->w) { + memcpy(dst, src, h * pitch); + } else { + while (h-- > 0) { + memcpy(dst, src, w * bytesPerPixel); + dst += pitch; + src += srcPitch; + } + } + + flagDirty(); +} + +void Texture::fill(uint32 color) { + Graphics::Surface *dst = getSurface(); + dst->fillRect(Common::Rect(dst->w, dst->h), color); + + flagDirty(); +} + +void Texture::draw(GLuint x, GLuint y, GLuint w, GLuint h) { + // Only do any processing when the Texture is initialized. + if (!_textureData.getPixels()) { + return; + } + + // First update any potentional changes. + updateTexture(); + + // Set the texture. + GLCALL(glBindTexture(GL_TEXTURE_2D, _glTexture)); + + // Calculate the texture rect that will be drawn. + const GLfloat texWidth = (GLfloat)_userPixelData.w / _textureData.w; + const GLfloat texHeight = (GLfloat)_userPixelData.h / _textureData.h; + const GLfloat texcoords[4*2] = { + 0, 0, + texWidth, 0, + 0, texHeight, + texWidth, texHeight + }; + GLCALL(glTexCoordPointer(2, GL_FLOAT, 0, texcoords)); + + // Calculate the screen rect where the texture will be drawn. + const GLshort vertices[4*2] = { + (GLshort)x, (GLshort)y, + (GLshort)(x + w), (GLshort)y, + (GLshort)x, (GLshort)(y + h), + (GLshort)(x + w), (GLshort)(y + h) + }; + GLCALL(glVertexPointer(2, GL_SHORT, 0, vertices)); + + // Draw the texture to the screen buffer. + GLCALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); +} + +void Texture::updateTexture() { + if (!isDirty()) { + return; + } + + // In case we use linear filtering we might need to duplicate the last + // pixel row/column to avoid glitches with filtering. + if (_glFilter == GL_LINEAR) { + if (_textureData.w != _userPixelData.w) { + uint height = _userPixelData.h; + + const byte *src = (const byte *)_textureData.getBasePtr(_userPixelData.w - 1, 0); + byte *dst = (byte *)_textureData.getBasePtr(_userPixelData.w, 0); + + while (height-- > 0) { + memcpy(dst, src, _textureData.format.bytesPerPixel); + dst += _textureData.pitch; + src += _textureData.pitch; + } + } + + if (_textureData.h != _userPixelData.h) { + const byte *src = (const byte *)_textureData.getBasePtr(0, _userPixelData.h - 1); + byte *dst = (byte *)_textureData.getBasePtr(0, _userPixelData.h); + memcpy(dst, src, _userPixelData.w * _textureData.format.bytesPerPixel); + } + } + + // Set the texture. + GLCALL(glBindTexture(GL_TEXTURE_2D, _glTexture)); + + // Update the actual texture. + GLCALL(glTexImage2D(GL_TEXTURE_2D, 0, _glIntFormat, _textureData.w, _textureData.h, 0, _glFormat, _glType, _textureData.getPixels())); + + // We should have handled everything, thus not dirty anymore. + clearDirty(); +} + +TextureCLUT8::TextureCLUT8(GLenum glIntFormat, GLenum glFormat, GLenum glType, const Graphics::PixelFormat &format) + : Texture(glIntFormat, glFormat, glType, format), _clut8Data(), _palette(new byte[256 * format.bytesPerPixel]) { + memset(_palette, 0, sizeof(byte) * format.bytesPerPixel); +} + +TextureCLUT8::~TextureCLUT8() { + delete[] _palette; + _palette = nullptr; + _clut8Data.free(); +} + +void TextureCLUT8::allocate(uint width, uint height) { + Texture::allocate(width, height); + + // We only need to reinitialize our CLUT8 surface when the output size + // changed. + if (width == _clut8Data.w && width == _clut8Data.h) { + return; + } + + _clut8Data.create(width, height, Graphics::PixelFormat::createFormatCLUT8()); +} + +Graphics::PixelFormat TextureCLUT8::getFormat() const { + return Graphics::PixelFormat::createFormatCLUT8(); +} + +namespace { +template<typename ColorType> +inline void convertPalette(ColorType *dst, const byte *src, uint colors, const Graphics::PixelFormat &format) { + while (colors-- > 0) { + *dst++ = format.RGBToColor(src[0], src[1], src[2]); + src += 3; + } +} +} // End of anonymous namespace + +void TextureCLUT8::setPalette(uint start, uint colors, const byte *palData) { + const Graphics::PixelFormat &hardwareFormat = getHardwareFormat(); + + if (hardwareFormat.bytesPerPixel == 2) { + convertPalette<uint16>((uint16 *)_palette + start, palData, colors, hardwareFormat); + } else if (hardwareFormat.bytesPerPixel == 4) { + convertPalette<uint32>((uint32 *)_palette + start, palData, colors, hardwareFormat); + } else { + warning("TextureCLUT8::setPalette: Unsupported pixel depth: %d", hardwareFormat.bytesPerPixel); + } + + // A palette changes means we need to refresh the whole surface. + flagDirty(); +} + +namespace { +template<typename PixelType> +inline void doPaletteLookUp(PixelType *dst, const byte *src, uint width, uint height, uint dstPitch, uint srcPitch, const PixelType *palette) { + uint srcAdd = srcPitch - width; + uint dstAdd = dstPitch - width * sizeof(PixelType); + + while (height-- > 0) { + for (uint x = width; x > 0; --x) { + *dst++ = palette[*src++]; + } + + dst = (PixelType *)((byte *)dst + dstAdd); + src += srcAdd; + } +} +} // End of anonymous namespace + +void TextureCLUT8::updateTexture() { + if (!isDirty()) { + return; + } + + // Do the palette look up + Graphics::Surface *outSurf = Texture::getSurface(); + + if (outSurf->format.bytesPerPixel == 2) { + doPaletteLookUp<uint16>((uint16 *)outSurf->getPixels(), (const byte *)_clut8Data.getPixels(), _clut8Data.w, _clut8Data.h, + outSurf->pitch, _clut8Data.pitch, (const uint16 *)_palette); + } else if (outSurf->format.bytesPerPixel == 4) { + doPaletteLookUp<uint32>((uint32 *)outSurf->getPixels(), (const byte *)_clut8Data.getPixels(), _clut8Data.w, _clut8Data.h, + outSurf->pitch, _clut8Data.pitch, (const uint32 *)_palette); + } else { + warning("TextureCLUT8::updateTexture: Unsupported pixel depth: %d", outSurf->format.bytesPerPixel); + } + + // Do generic handling of updating the texture. + Texture::updateTexture(); +} + +} // End of namespace OpenGL diff --git a/backends/graphics/opengl/texture.h b/backends/graphics/opengl/texture.h new file mode 100644 index 0000000000..1127aed350 --- /dev/null +++ b/backends/graphics/opengl/texture.h @@ -0,0 +1,159 @@ +/* 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. + * + */ + +#ifndef BACKENDS_GRAPHICS_OPENGL_TEXTURE_H +#define BACKENDS_GRAPHICS_OPENGL_TEXTURE_H + +#include "backends/graphics/opengl/opengl-sys.h" + +#include "graphics/pixelformat.h" +#include "graphics/surface.h" + +namespace OpenGL { + +/** + * An OpenGL texture wrapper. It automatically takes care of all OpenGL + * texture handling issues and also provides access to the texture data. + */ +class Texture { +public: + /** + * Create a new texture with the specific internal format. + * + * @param glIntFormat The internal format to use. + * @param glFormat The input format. + * @param glType The input type. + * @param format The format used for the texture input. + */ + Texture(GLenum glIntFormat, GLenum glFormat, GLenum glType, const Graphics::PixelFormat &format); + virtual ~Texture(); + + /** + * Destroy the OpenGL texture name. + */ + void releaseInternalTexture(); + + /** + * Create the OpenGL texture name and flag the whole texture as dirty. + */ + void recreateInternalTexture(); + + /** + * Enable or disable linear texture filtering. + * + * @param enable true to enable and false to disable. + */ + void enableLinearFiltering(bool enable); + + /** + * Allocate texture space for the desired dimensions. This wraps any + * handling of requirements for POT textures. + * + * @param width The desired logical width. + * @param height The desired logical height. + */ + virtual void allocate(uint width, uint height); + + void copyRectToTexture(uint x, uint y, uint w, uint h, const void *src, uint srcPitch); + + void fill(uint32 color); + + void draw(GLuint x, GLuint y, GLuint w, GLuint h); + + void flagDirty() { _allDirty = true; } + bool isDirty() const { return _allDirty; } + + uint getWidth() const { return _userPixelData.w; } + uint getHeight() const { return _userPixelData.h; } + + /** + * @return The hardware format of the texture data. + */ + const Graphics::PixelFormat &getHardwareFormat() const { return _format; } + + /** + * @return The logical format of the texture data. + */ + virtual Graphics::PixelFormat getFormat() const { return _format; } + + virtual Graphics::Surface *getSurface() { return &_userPixelData; } + virtual const Graphics::Surface *getSurface() const { return &_userPixelData; } + + /** + * @return Whether the texture data is using a palette. + */ + virtual bool hasPalette() const { return false; } + + virtual void setPalette(uint start, uint colors, const byte *palData) {} + + virtual void *getPalette() { return 0; } + virtual const void *getPalette() const { return 0; } + +protected: + virtual void updateTexture(); + +private: + const GLenum _glIntFormat; + const GLenum _glFormat; + const GLenum _glType; + const Graphics::PixelFormat _format; + + GLint _glFilter; + GLuint _glTexture; + + Graphics::Surface _textureData; + Graphics::Surface _userPixelData; + + bool _allDirty; + void clearDirty() { _allDirty = false; } +}; + +class TextureCLUT8 : public Texture { +public: + TextureCLUT8(GLenum glIntFormat, GLenum glFormat, GLenum glType, const Graphics::PixelFormat &format); + virtual ~TextureCLUT8(); + + virtual void allocate(uint width, uint height); + + virtual Graphics::PixelFormat getFormat() const; + + virtual bool hasPalette() const { return true; } + + virtual void setPalette(uint start, uint colors, const byte *palData); + + virtual void *getPalette() { return _palette; } + virtual const void *getPalette() const { return _palette; } + + virtual Graphics::Surface *getSurface() { return &_clut8Data; } + virtual const Graphics::Surface *getSurface() const { return &_clut8Data; } + +protected: + virtual void updateTexture(); + +private: + Graphics::Surface _clut8Data; + byte *_palette; +}; + +} // End of namespace OpenGL + +#endif diff --git a/backends/module.mk b/backends/module.mk index 682ca78e22..b66b0b04ec 100644 --- a/backends/module.mk +++ b/backends/module.mk @@ -49,6 +49,15 @@ MODULE_OBJS += \ vkeybd/virtual-keyboard-parser.o endif +# OpenGL specific source files. +ifdef USE_OPENGL +MODULE_OBJS += \ + graphics/opengl/debug.o \ + graphics/opengl/extensions.o \ + graphics/opengl/opengl-graphics.o \ + graphics/opengl/texture.o +endif + # SDL specific source files. # We cannot just check $BACKEND = sdl, as various other backends # derive from the SDL backend, and they all need the following files. |