Le Philousophe a95f42317c BACKENDS: OPENGL: Clear screen using opaque black instead of transparent
This looks like an overlook since the creation of the backend.
Previous Android backend used this and it shouldn't harm other
platforms.
This should fix a bug on ChromeOS.
2023-06-04 18:22:59 +02:00

1762 lines
53 KiB
C++

/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/graphics/opengl/opengl-graphics.h"
#include "backends/graphics/opengl/texture.h"
#include "backends/graphics/opengl/pipelines/pipeline.h"
#include "backends/graphics/opengl/pipelines/fixed.h"
#include "backends/graphics/opengl/pipelines/shader.h"
#include "backends/graphics/opengl/pipelines/libretro.h"
#include "backends/graphics/opengl/shader.h"
#include "graphics/opengl/debug.h"
#include "common/array.h"
#include "common/textconsole.h"
#include "common/translation.h"
#include "common/algorithm.h"
#include "common/file.h"
#include "common/zip-set.h"
#include "gui/debugger.h"
#include "engines/engine.h"
#ifdef USE_OSD
#include "common/tokenizer.h"
#include "common/rect.h"
#if defined(MACOSX)
#include "backends/platform/sdl/macosx/macosx-touchbar.h"
#endif
#endif
#include "graphics/blit.h"
#ifdef USE_OSD
#include "graphics/fontman.h"
#include "graphics/font.h"
#endif
#ifdef USE_SCALERS
#include "graphics/scalerplugin.h"
#endif
#ifdef USE_PNG
#include "image/png.h"
#else
#include "image/bmp.h"
#endif
#include "common/text-to-speech.h"
#if !USE_FORCED_GLES
#include "backends/graphics/opengl/pipelines/libretro/parser.h"
#endif
namespace OpenGL {
OpenGLGraphicsManager::OpenGLGraphicsManager()
: _currentState(), _oldState(), _transactionMode(kTransactionNone), _screenChangeID(1 << (sizeof(int) * 8 - 2)),
_pipeline(nullptr), _stretchMode(STRETCH_FIT),
_defaultFormat(), _defaultFormatAlpha(),
_gameScreen(nullptr), _overlay(nullptr),
_cursor(nullptr), _cursorMask(nullptr),
_cursorHotspotX(0), _cursorHotspotY(0),
_cursorHotspotXScaled(0), _cursorHotspotYScaled(0), _cursorWidthScaled(0), _cursorHeightScaled(0),
_cursorKeyColor(0), _cursorUseKey(true), _cursorDontScale(false), _cursorPaletteEnabled(false), _shakeOffsetScaled()
#if !USE_FORCED_GLES
, _libretroPipeline(nullptr)
#endif
#ifdef USE_OSD
, _osdMessageChangeRequest(false), _osdMessageAlpha(0), _osdMessageFadeStartTime(0), _osdMessageSurface(nullptr),
_osdIconSurface(nullptr)
#endif
#ifdef USE_SCALERS
, _scalerPlugins(ScalerMan.getPlugins())
#endif
{
memset(_gamePalette, 0, sizeof(_gamePalette));
OpenGLContext.reset();
}
OpenGLGraphicsManager::~OpenGLGraphicsManager() {
delete _gameScreen;
delete _overlay;
delete _cursor;
delete _cursorMask;
#ifdef USE_OSD
delete _osdMessageSurface;
delete _osdIconSurface;
#endif
#if !USE_FORCED_GLES
ShaderManager::destroy();
#endif
delete _pipeline;
}
bool OpenGLGraphicsManager::hasFeature(OSystem::Feature f) const {
switch (f) {
case OSystem::kFeatureAspectRatioCorrection:
case OSystem::kFeatureCursorPalette:
case OSystem::kFeatureFilteringMode:
case OSystem::kFeatureStretchMode:
case OSystem::kFeatureCursorMask:
case OSystem::kFeatureCursorMaskInvert:
#ifdef USE_SCALERS
case OSystem::kFeatureScalers:
#endif
return true;
#if !USE_FORCED_GLES
case OSystem::kFeatureShaders:
return LibRetroPipeline::isSupportedByContext();
#endif
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::kFeatureFilteringMode:
assert(_transactionMode != kTransactionNone);
_currentState.filtering = enable;
break;
case OSystem::kFeatureCursorPalette:
_cursorPaletteEnabled = enable;
updateCursorPalette();
break;
default:
break;
}
}
bool OpenGLGraphicsManager::getFeatureState(OSystem::Feature f) const {
switch (f) {
case OSystem::kFeatureAspectRatioCorrection:
return _currentState.aspectRatioCorrection;
case OSystem::kFeatureFilteringMode:
return _currentState.filtering;
case OSystem::kFeatureCursorPalette:
return _cursorPaletteEnabled;
default:
return false;
}
}
namespace {
const OSystem::GraphicsMode glGraphicsModes[] = {
{ "opengl", _s("OpenGL"), GFX_OPENGL },
{ nullptr, nullptr, 0 }
};
} // End of anonymous namespace
const OSystem::GraphicsMode *OpenGLGraphicsManager::getSupportedGraphicsModes() const {
return glGraphicsModes;
}
int OpenGLGraphicsManager::getDefaultGraphicsMode() const {
return GFX_OPENGL;
}
bool OpenGLGraphicsManager::setGraphicsMode(int mode, uint flags) {
assert(_transactionMode != kTransactionNone);
switch (mode) {
case GFX_OPENGL:
_currentState.graphicsMode = mode;
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;
}
Common::List<Graphics::PixelFormat> OpenGLGraphicsManager::getSupportedFormats() const {
Common::List<Graphics::PixelFormat> formats;
// Our default mode is (memory layout wise) RGBA8888 which is a different
// logical layout depending on the endianness. We chose this mode because
// it is the only 32bit color mode we can safely assume to be present in
// OpenGL and OpenGL ES implementations. Thus, we need to supply different
// logical formats based on endianness.
#ifdef SCUMM_LITTLE_ENDIAN
// ABGR8888
formats.push_back(Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24));
#else
// RGBA8888
formats.push_back(Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0));
#endif
// RGB565
formats.push_back(Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0));
// RGBA5551
formats.push_back(Graphics::PixelFormat(2, 5, 5, 5, 1, 11, 6, 1, 0));
// RGBA4444
formats.push_back(Graphics::PixelFormat(2, 4, 4, 4, 4, 12, 8, 4, 0));
// These formats are not natively supported by OpenGL ES implementations,
// we convert the pixel format internally.
#ifdef SCUMM_LITTLE_ENDIAN
// RGBA8888
formats.push_back(Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0));
#else
// ABGR8888
formats.push_back(Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24));
#endif
// RGB555, this is used by SCUMM HE 16 bit games.
formats.push_back(Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0));
formats.push_back(Graphics::PixelFormat::createFormatCLUT8());
return formats;
}
#endif
namespace {
const OSystem::GraphicsMode glStretchModes[] = {
{"center", _s("Center"), STRETCH_CENTER},
{"pixel-perfect", _s("Pixel-perfect scaling"), STRETCH_INTEGRAL},
{"even-pixels", _s("Even pixels scaling"), STRETCH_INTEGRAL_AR},
{"fit", _s("Fit to window"), STRETCH_FIT},
{"stretch", _s("Stretch to window"), STRETCH_STRETCH},
{"fit_force_aspect", _s("Fit to window (4:3)"), STRETCH_FIT_FORCE_ASPECT},
{nullptr, nullptr, 0}
};
} // End of anonymous namespace
const OSystem::GraphicsMode *OpenGLGraphicsManager::getSupportedStretchModes() const {
return glStretchModes;
}
int OpenGLGraphicsManager::getDefaultStretchMode() const {
return STRETCH_FIT;
}
bool OpenGLGraphicsManager::setStretchMode(int mode) {
assert(getTransactionMode() != kTransactionNone);
if (mode == _stretchMode)
return true;
// Check this is a valid mode
const OSystem::GraphicsMode *sm = getSupportedStretchModes();
bool found = false;
while (sm->name) {
if (sm->id == mode) {
found = true;
break;
}
sm++;
}
if (!found) {
warning("unknown stretch mode %d", mode);
return false;
}
_stretchMode = mode;
return true;
}
int OpenGLGraphicsManager::getStretchMode() const {
return _stretchMode;
}
#ifdef USE_SCALERS
uint OpenGLGraphicsManager::getDefaultScaler() const {
return ScalerMan.findScalerPluginIndex("normal");
}
uint OpenGLGraphicsManager::getDefaultScaleFactor() const {
return 1;
}
bool OpenGLGraphicsManager::setScaler(uint mode, int factor) {
assert(_transactionMode != kTransactionNone);
int newFactor;
if (factor == -1)
newFactor = getDefaultScaleFactor();
else if (_scalerPlugins[mode]->get<ScalerPluginObject>().hasFactor(factor))
newFactor = factor;
else if (_scalerPlugins[mode]->get<ScalerPluginObject>().hasFactor(_oldState.scaleFactor))
newFactor = _oldState.scaleFactor;
else
newFactor = _scalerPlugins[mode]->get<ScalerPluginObject>().getDefaultFactor();
_currentState.scalerIndex = mode;
_currentState.scaleFactor = newFactor;
return true;
}
uint OpenGLGraphicsManager::getScaler() const {
return _currentState.scalerIndex;
}
uint OpenGLGraphicsManager::getScaleFactor() const {
return _currentState.scaleFactor;
}
#endif
#if !USE_FORCED_GLES
bool OpenGLGraphicsManager::setShader(const Common::String &fileName) {
assert(_transactionMode != kTransactionNone);
// Special case for the 'default' shader
if (fileName == "default")
_currentState.shader = "";
else
_currentState.shader = fileName;
return true;
}
#endif
bool OpenGLGraphicsManager::loadShader(const Common::String &fileName) {
#if !USE_FORCED_GLES
if (!_libretroPipeline) {
warning("Libretro is not supported");
return true;
}
Common::SearchSet shaderSet;
Common::generateZipSet(shaderSet, "shaders.dat", "shaders*.dat");
// Load selected shader preset
if (!fileName.empty()) {
if (!_libretroPipeline->open(fileName, shaderSet)) {
warning("Failed to load shader %s", fileName.c_str());
return false;
}
} else {
_libretroPipeline->close();
}
#endif
return true;
}
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
#ifdef USE_SCALERS
if (_oldState.scaleFactor != _currentState.scaleFactor ||
_oldState.scalerIndex != _currentState.scalerIndex) {
setupNewGameScreen = true;
}
#endif
do {
const uint desiredAspect = getDesiredGameAspectRatio();
const uint requestedWidth = _currentState.gameWidth;
const uint requestedHeight = intToFrac(requestedWidth) / desiredAspect;
// Consider that shader is OK by default
// If loadVideoMode fails, we won't consider that shader was the error
bool shaderOK = true;
if (!loadVideoMode(requestedWidth, requestedHeight,
#ifdef USE_RGB_COLOR
_currentState.gameFormat
#else
Graphics::PixelFormat::createFormatCLUT8()
#endif
)
|| !(shaderOK = loadShader(_currentState.shader))
// HACK: This is really nasty but we don't have any guarantees of
// a context existing before, which means we don't know the maximum
// supported texture size before this. Thus, we check whether the
// requested game resolution is supported over here.
|| ( _currentState.gameWidth > (uint)OpenGLContext.maxTextureSize
|| _currentState.gameHeight > (uint)OpenGLContext.maxTextureSize)) {
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;
}
if (_oldState.filtering != _currentState.filtering) {
transactionError |= OSystem::kTransactionFilteringFailed;
}
#ifdef USE_SCALERS
if (_oldState.scalerIndex != _currentState.scalerIndex) {
transactionError |= OSystem::kTransactionModeSwitchFailed;
}
#endif
#if !USE_FORCED_GLES
if (_oldState.shader != _currentState.shader) {
transactionError |= OSystem::kTransactionShaderChangeFailed;
}
#endif
// Roll back to the old state.
_currentState = _oldState;
_transactionMode = kTransactionRollback;
// Try to set up the old state.
continue;
}
// If the shader failed and we had not a valid old state, try to unset the shader and do it again
if (!shaderOK && !_currentState.shader.empty()) {
_currentState.shader = "";
_transactionMode = kTransactionRollback;
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;
bool wantScaler = _currentState.scaleFactor > 1;
#ifdef USE_RGB_COLOR
_gameScreen = createSurface(_currentState.gameFormat, false, wantScaler);
#else
_gameScreen = createSurface(Graphics::PixelFormat::createFormatCLUT8(), false, wantScaler);
#endif
assert(_gameScreen);
if (_gameScreen->hasPalette()) {
_gameScreen->setPalette(0, 256, _gamePalette);
}
#ifdef USE_SCALERS
if (wantScaler) {
_gameScreen->setScaler(_currentState.scalerIndex, _currentState.scaleFactor);
}
#endif
_gameScreen->allocate(_currentState.gameWidth, _currentState.gameHeight);
// We fill the screen to all black or index 0 for CLUT8.
#ifdef USE_RGB_COLOR
if (_currentState.gameFormat.bytesPerPixel == 1) {
_gameScreen->fill(0);
} else {
_gameScreen->fill(_gameScreen->getSurface()->format.RGBToColor(0, 0, 0));
}
#else
_gameScreen->fill(0);
#endif
}
// Update our display area and cursor scaling. This makes sure we pick up
// aspect ratio correction and game screen changes correctly.
recalculateDisplayAreas();
recalculateCursorScaling();
updateLinearFiltering();
// 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;
_gameScreenShakeXOffset = 0;
_gameScreenShakeYOffset = 0;
}
int16 OpenGLGraphicsManager::getWidth() const {
return _currentState.gameWidth;
}
int16 OpenGLGraphicsManager::getHeight() const {
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) {
_gameScreen->fill(col);
}
void OpenGLGraphicsManager::renderCursor() {
/*
Windows and Mac cursor XOR works by drawing the cursor to the screen with the formula (Destination AND Mask XOR Color)
OpenGL does not have an XOR blend mode though. Full inversions can be accomplished by using blend modes with
ONE_MINUS_DST_COLOR but the problem is how to do that in a way that handles linear filtering properly.
To avoid color fringing, we need to produce an output of 3 separately-modulated inputs: The framebuffer modulated by
(1 - inversion)*(1 - alpha), the inverted framebuffer modulated by inversion*(1 - alpha), and the cursor colors modulated by alpha.
The last part is additive and not framebuffer dependent so it can just be a separate draw call. The first two are the problem
because we can't use the unmodified framebuffer value twice if we do it in two separate draw calls, and if we do it in a single
draw call, we can only supply one RGB input even though the inversion mask should be RGB.
If we only allow grayscale inversions though, then we can put inversion*(1 - alpha) in the RGB channel and
(1 - inversion)*(1 - alpha) in the alpha channel and use and use ((1-dstColor)*src+(1-srcAlpha)*dest) blend formula to do
the inversion and opacity mask at once. We use 1-srcAlpha instead of srcAlpha so zero-fill is transparent.
*/
if (_cursorMask) {
_backBuffer.enableBlend(Framebuffer::kBlendModeMaskAlphaAndInvertByColor);
_pipeline->drawTexture(_cursorMask->getGLTexture(),
_cursorX - _cursorHotspotXScaled + _shakeOffsetScaled.x,
_cursorY - _cursorHotspotYScaled + _shakeOffsetScaled.y,
_cursorWidthScaled, _cursorHeightScaled);
_backBuffer.enableBlend(Framebuffer::kBlendModeAdditive);
} else
_backBuffer.enableBlend(Framebuffer::kBlendModePremultipliedTransparency);
_pipeline->drawTexture(_cursor->getGLTexture(),
_cursorX - _cursorHotspotXScaled + _shakeOffsetScaled.x,
_cursorY - _cursorHotspotYScaled + _shakeOffsetScaled.y,
_cursorWidthScaled, _cursorHeightScaled);
}
void OpenGLGraphicsManager::updateScreen() {
if (!_gameScreen) {
return;
}
#ifdef USE_OSD
if (_osdMessageChangeRequest) {
osdMessageUpdateSurface();
}
if (_osdIconSurface) {
_osdIconSurface->updateGLTexture();
}
#endif
// If there's an active debugger, update it
GUI::Debugger *debugger = g_engine ? g_engine->getDebugger() : nullptr;
if (debugger)
debugger->onFrame();
// We only update the screen when there actually have been any changes.
if ( !_forceRedraw
&& !_cursorNeedsRedraw
&& !_gameScreen->isDirty()
#if !USE_FORCED_GLES
&& !(_libretroPipeline && _libretroPipeline->isAnimated())
#endif
&& !(_overlayVisible && _overlay->isDirty())
&& !(_cursorVisible && ((_cursor && _cursor->isDirty()) || (_cursorMask && _cursorMask->isDirty())))
#ifdef USE_OSD
&& !_osdMessageSurface && !_osdIconSurface
#endif
) {
return;
}
// Update changes to textures.
_gameScreen->updateGLTexture();
if (_cursorVisible && _cursor) {
_cursor->updateGLTexture();
}
if (_cursorVisible && _cursorMask) {
_cursorMask->updateGLTexture();
}
_overlay->updateGLTexture();
#if !USE_FORCED_GLES
if (_libretroPipeline) {
_libretroPipeline->beginScaling();
}
#endif
_pipeline->activate();
// Clear the screen buffer.
GL_CALL(glClear(GL_COLOR_BUFFER_BIT));
if (!_overlayVisible) {
// The scissor test is enabled to:
// - Clip the cursor to the game screen
// - Clip the game screen when the shake offset is non-zero
_backBuffer.enableScissorTest(true);
}
// Don't draw cursor if it's not visible or there is none
bool drawCursor = _cursorVisible && _cursor;
// Alpha blending is disabled when drawing the screen
_backBuffer.enableBlend(Framebuffer::kBlendModeDisabled);
// First step: Draw the (virtual) game screen.
_pipeline->drawTexture(_gameScreen->getGLTexture(), _gameDrawRect.left, _gameDrawRect.top, _gameDrawRect.width(), _gameDrawRect.height());
// Second step: Draw the cursor if necessary and we are not in GUI and it
#if !USE_FORCED_GLES
if (_libretroPipeline) {
// If we are in game, draw the cursor through scaler
// This has the disadvantage of having overlay (subtitles) drawn above it
// but the cursor will look nicer
if (!_overlayInGUI && drawCursor) {
renderCursor();
drawCursor = false;
// Everything we need to clip has been clipped
_backBuffer.enableScissorTest(false);
}
// Overlay must not be scaled and its cursor won't be either
_libretroPipeline->finishScaling();
}
#endif
// Third step: Draw the overlay if visible.
if (_overlayVisible) {
int dstX = (_windowWidth - _overlayDrawRect.width()) / 2;
int dstY = (_windowHeight - _overlayDrawRect.height()) / 2;
_backBuffer.enableBlend(Framebuffer::kBlendModeTraditionalTransparency);
_pipeline->drawTexture(_overlay->getGLTexture(), dstX, dstY, _overlayDrawRect.width(), _overlayDrawRect.height());
}
// Fourth step: Draw the cursor if we didn't before.
if (drawCursor)
renderCursor();
if (!_overlayVisible) {
_backBuffer.enableScissorTest(false);
}
#ifdef USE_OSD
// Fourth step: Draw the OSD.
if (_osdMessageSurface || _osdIconSurface) {
_backBuffer.enableBlend(Framebuffer::kBlendModeTraditionalTransparency);
}
if (_osdMessageSurface) {
// Update alpha value.
const int diff = g_system->getMillis(false) - _osdMessageFadeStartTime;
if (diff > 0) {
if (diff >= kOSDMessageFadeOutDuration) {
// Back to full transparency.
_osdMessageAlpha = 0;
} else {
// Do a fade out.
_osdMessageAlpha = kOSDMessageInitialAlpha - diff * kOSDMessageInitialAlpha / kOSDMessageFadeOutDuration;
}
}
// Set the OSD transparency.
_pipeline->setColor(1.0f, 1.0f, 1.0f, _osdMessageAlpha / 100.0f);
int dstX = (_windowWidth - _osdMessageSurface->getWidth()) / 2;
int dstY = (_windowHeight - _osdMessageSurface->getHeight()) / 2;
// Draw the OSD texture.
_pipeline->drawTexture(_osdMessageSurface->getGLTexture(),
dstX, dstY, _osdMessageSurface->getWidth(), _osdMessageSurface->getHeight());
// Reset color.
_pipeline->setColor(1.0f, 1.0f, 1.0f, 1.0f);
if (_osdMessageAlpha <= 0) {
delete _osdMessageSurface;
_osdMessageSurface = nullptr;
#if defined(MACOSX)
macOSTouchbarUpdate(nullptr);
#endif
}
}
if (_osdIconSurface) {
int dstX = _windowWidth - _osdIconSurface->getWidth() - kOSDIconRightMargin;
int dstY = kOSDIconTopMargin;
// Draw the OSD icon texture.
_pipeline->drawTexture(_osdIconSurface->getGLTexture(),
dstX, dstY, _osdIconSurface->getWidth(), _osdIconSurface->getHeight());
}
#endif
_cursorNeedsRedraw = false;
_forceRedraw = false;
refreshScreen();
}
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() const {
if (_overlay) {
return _overlay->getWidth();
} else {
return 0;
}
}
int16 OpenGLGraphicsManager::getOverlayHeight() const {
if (_overlay) {
return _overlay->getHeight();
} else {
return 0;
}
}
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(Graphics::Surface &surface) const {
const Graphics::Surface *overlayData = _overlay->getSurface();
assert(surface.w >= overlayData->w);
assert(surface.h >= overlayData->h);
assert(surface.format.bytesPerPixel == overlayData->format.bytesPerPixel);
const byte *src = (const byte *)overlayData->getPixels();
byte *dst = (byte *)surface.getPixels();
Graphics::copyBlit(dst, src, surface.pitch, overlayData->pitch, overlayData->w, overlayData->h, overlayData->format.bytesPerPixel);
}
namespace {
template<typename SrcColor, typename DstColor>
void multiplyColorWithAlpha(const byte *src, byte *dst, const uint w, const uint h,
const Graphics::PixelFormat &srcFmt, const Graphics::PixelFormat &dstFmt,
const uint srcPitch, const uint dstPitch, const SrcColor keyColor, bool useKeyColor) {
for (uint y = 0; y < h; ++y) {
for (uint x = 0; x < w; ++x) {
const uint32 color = *(const SrcColor *)src;
if (useKeyColor && color == keyColor) {
*(DstColor *)dst = 0;
} else {
byte a, r, g, b;
srcFmt.colorToARGB(color, a, r, g, b);
if (a != 0xFF) {
r = (int) r * a / 255;
g = (int) g * a / 255;
b = (int) b * a / 255;
}
*(DstColor *)dst = dstFmt.ARGBToColor(a, r, g, b);
}
src += sizeof(SrcColor);
dst += sizeof(DstColor);
}
src += srcPitch - w * srcFmt.bytesPerPixel;
dst += dstPitch - w * dstFmt.bytesPerPixel;
}
}
} // 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, const byte *mask) {
_cursorUseKey = (mask == nullptr);
if (_cursorUseKey)
_cursorKeyColor = keycolor;
_cursorHotspotX = hotspotX;
_cursorHotspotY = hotspotY;
_cursorDontScale = dontScale;
if (!w || !h) {
delete _cursor;
_cursor = nullptr;
delete _cursorMask;
_cursorMask = nullptr;
return;
}
Graphics::PixelFormat inputFormat;
Graphics::PixelFormat maskFormat;
#ifdef USE_RGB_COLOR
if (format) {
inputFormat = *format;
} else {
inputFormat = Graphics::PixelFormat::createFormatCLUT8();
}
#else
inputFormat = Graphics::PixelFormat::createFormatCLUT8();
#endif
#ifdef USE_SCALERS
bool wantScaler = (_currentState.scaleFactor > 1) && !dontScale && _scalerPlugins[_currentState.scalerIndex]->get<ScalerPluginObject>().canDrawCursor();
#else
bool wantScaler = false;
#endif
bool wantMask = (mask != nullptr);
bool haveMask = (_cursorMask != nullptr);
// In case the color format has changed we will need to create the texture.
if (!_cursor || _cursor->getFormat() != inputFormat || haveMask != wantMask) {
delete _cursor;
_cursor = nullptr;
GLenum glIntFormat, glFormat, glType;
Graphics::PixelFormat textureFormat;
if (inputFormat.bytesPerPixel == 1 || (inputFormat.aBits() && getGLPixelFormat(inputFormat, glIntFormat, glFormat, glType))) {
// There is two cases when we can use the cursor format directly.
// The first is when it's CLUT8, here color key handling can
// always be applied because we use the alpha channel of
// _defaultFormatAlpha for that.
// The other is when the input format has alpha bits and
// furthermore is directly supported.
textureFormat = inputFormat;
} else {
textureFormat = _defaultFormatAlpha;
}
_cursor = createSurface(textureFormat, true, wantScaler, wantMask);
assert(_cursor);
updateLinearFiltering();
#ifdef USE_SCALERS
if (wantScaler) {
_cursor->setScaler(_currentState.scalerIndex, _currentState.scaleFactor);
}
#endif
}
if (mask) {
if (!_cursorMask) {
maskFormat = _defaultFormatAlpha;
_cursorMask = createSurface(maskFormat, true, wantScaler);
assert(_cursorMask);
updateLinearFiltering();
#ifdef USE_SCALERS
if (wantScaler) {
_cursorMask->setScaler(_currentState.scalerIndex, _currentState.scaleFactor);
}
#endif
}
} else {
delete _cursorMask;
_cursorMask = nullptr;
}
Common::Point topLeftCoord(0, 0);
Common::Point cursorSurfaceSize(w, h);
// If the cursor is scalable, add a 1-texel transparent border.
// This ensures that linear filtering falloff from the edge pixels has room to completely fade out instead of
// being cut off at half-way. Could use border clamp too, but GLES2 doesn't support that.
if (!_cursorDontScale) {
topLeftCoord = Common::Point(1, 1);
cursorSurfaceSize += Common::Point(2, 2);
}
_cursor->allocate(cursorSurfaceSize.x, cursorSurfaceSize.y);
if (_cursorMask)
_cursorMask->allocate(cursorSurfaceSize.x, cursorSurfaceSize.y);
_cursorHotspotX += topLeftCoord.x;
_cursorHotspotY += topLeftCoord.y;
if (inputFormat.bytesPerPixel == 1) {
// For CLUT8 cursors we can simply copy the input data into the
// texture.
if (!_cursorDontScale)
_cursor->fill(keycolor);
_cursor->copyRectToTexture(topLeftCoord.x, topLeftCoord.y, w, h, buf, w * inputFormat.bytesPerPixel);
if (mask) {
// Construct a mask of opaque pixels
Common::Array<byte> maskBytes;
maskBytes.resize(cursorSurfaceSize.x * cursorSurfaceSize.y, 0);
for (uint y = 0; y < h; y++) {
for (uint x = 0; x < w; x++) {
// The cursor pixels must be masked out for anything except opaque
if (mask[y * w + x] == kCursorMaskOpaque)
maskBytes[(y + topLeftCoord.y) * cursorSurfaceSize.x + topLeftCoord.x + x] = 1;
}
}
_cursor->setMask(&maskBytes[0]);
} else {
_cursor->setMask(nullptr);
}
} 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;
// Copy the cursor data to the actual texture surface. This will make
// sure that the data is also converted to the expected format.
// Also multiply the color values with the alpha channel.
// The pre-multiplication allows using a blend mode that prevents
// color fringes due to filtering.
if (!_cursorDontScale)
_cursor->fill(0);
byte *topLeftPixelPtr = static_cast<byte *>(dst->getBasePtr(topLeftCoord.x, topLeftCoord.y));
if (dst->format.bytesPerPixel == 2) {
if (inputFormat.bytesPerPixel == 2) {
multiplyColorWithAlpha<uint16, uint16>((const byte *)buf, topLeftPixelPtr, w, h,
inputFormat, dst->format, srcPitch, dst->pitch, keycolor, _cursorUseKey);
} else if (inputFormat.bytesPerPixel == 4) {
multiplyColorWithAlpha<uint32, uint16>((const byte *)buf, topLeftPixelPtr, w, h,
inputFormat, dst->format, srcPitch, dst->pitch, keycolor, _cursorUseKey);
}
} else if (dst->format.bytesPerPixel == 4) {
if (inputFormat.bytesPerPixel == 2) {
multiplyColorWithAlpha<uint16, uint32>((const byte *)buf, topLeftPixelPtr, w, h,
inputFormat, dst->format, srcPitch, dst->pitch, keycolor, _cursorUseKey);
} else if (inputFormat.bytesPerPixel == 4) {
multiplyColorWithAlpha<uint32, uint32>((const byte *)buf, topLeftPixelPtr, w, h,
inputFormat, dst->format, srcPitch, dst->pitch, keycolor, _cursorUseKey);
}
}
// Replace all non-opaque pixels with black pixels
if (mask) {
Graphics::Surface *cursorSurface = _cursor->getSurface();
for (uint x = 0; x < w; x++) {
for (uint y = 0; y < h; y++) {
uint8 maskByte = mask[y * w + x];
if (maskByte != kCursorMaskOpaque)
cursorSurface->setPixel(x + topLeftCoord.x, y + topLeftCoord.y, 0);
}
}
}
// Flag the texture as dirty.
_cursor->flagDirty();
}
if (_cursorMask && mask) {
// Generate the multiply+invert texture.
// We're generating this for a blend mode where source factor is ONE_MINUS_DST_COLOR and dest factor is ONE_MINUS_SRC_ALPHA
// In other words, positive RGB channel values will add inverted destination pixels, positive alpha values will modulate
// RGB+Alpha = Inverted Alpha Only = Black 0 = No change
Graphics::Surface *cursorSurface = _cursor->getSurface();
Graphics::Surface *maskSurface = _cursorMask->getSurface();
maskFormat = _cursorMask->getFormat();
const Graphics::PixelFormat cursorFormat = cursorSurface->format;
_cursorMask->fill(0);
for (uint x = 0; x < w; x++) {
for (uint y = 0; y < h; y++) {
// See the description of renderCursor for an explanation of why this works the way it does.
uint8 maskOpacity = 0xff;
if (inputFormat.bytesPerPixel != 1) {
uint32 cursorPixel = cursorSurface->getPixel(x + topLeftCoord.x, y + topLeftCoord.y);
uint8 r, g, b;
cursorFormat.colorToARGB(cursorPixel, maskOpacity, r, g, b);
}
uint8 maskInversionAdd = 0;
uint8 maskByte = mask[y * w + x];
if (maskByte == kCursorMaskTransparent)
maskOpacity = 0;
if (maskByte == kCursorMaskInvert) {
maskOpacity = 0xff;
maskInversionAdd = 0xff;
}
uint32 encodedMaskPixel = maskFormat.ARGBToColor(maskOpacity, maskInversionAdd, maskInversionAdd, maskInversionAdd);
maskSurface->setPixel(x + topLeftCoord.x, y + topLeftCoord.y, encodedMaskPixel);
}
}
_cursorMask->flagDirty();
}
// In case we actually use a palette set that up properly.
if (inputFormat.bytesPerPixel == 1) {
updateCursorPalette();
}
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 Common::U32String &msg) {
#ifdef USE_OSD
_osdMessageChangeRequest = true;
_osdMessageNextData = msg;
#endif // USE_OSD
}
#ifdef USE_OSD
void OpenGLGraphicsManager::osdMessageUpdateSurface() {
// Split up the lines.
Common::Array<Common::U32String> osdLines;
Common::U32StringTokenizer tokenizer(_osdMessageNextData, "\n");
while (!tokenizer.empty()) {
osdLines.push_back(tokenizer.nextToken());
}
// Do the actual drawing like the SDL backend.
const Graphics::Font *font = getFontOSD();
// 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;
uint width = 0;
uint height = lineHeight * osdLines.size() + 2 * vOffset;
for (uint i = 0; i < osdLines.size(); i++) {
width = MAX<uint>(width, font->getStringWidth(osdLines[i]) + 14);
}
// Clip the rect
width = MIN<uint>(width, _gameDrawRect.width());
height = MIN<uint>(height, _gameDrawRect.height());
delete _osdMessageSurface;
_osdMessageSurface = nullptr;
_osdMessageSurface = createSurface(_defaultFormatAlpha);
assert(_osdMessageSurface);
// We always filter the osd with GL_LINEAR. This assures it's
// readable in case it needs to be scaled and does not affect it
// otherwise.
_osdMessageSurface->enableLinearFiltering(true);
_osdMessageSurface->allocate(width, height);
Graphics::Surface *dst = _osdMessageSurface->getSurface();
// Draw a dark gray rect.
const uint32 color = dst->format.RGBToColor(40, 40, 40);
dst->fillRect(Common::Rect(0, 0, width, height), color);
// Render the message in white
const uint32 white = dst->format.RGBToColor(255, 255, 255);
for (uint i = 0; i < osdLines.size(); ++i) {
font->drawString(dst, osdLines[i],
0, i * lineHeight + vOffset + lineSpacing, width,
white, Graphics::kTextAlignCenter, 0, true);
}
_osdMessageSurface->updateGLTexture();
#if defined(MACOSX)
macOSTouchbarUpdate(_osdMessageNextData.encode().c_str());
#endif
// Init the OSD display parameters.
_osdMessageAlpha = kOSDMessageInitialAlpha;
_osdMessageFadeStartTime = g_system->getMillis() + kOSDMessageFadeOutDelay;
if (ConfMan.hasKey("tts_enabled", "scummvm") &&
ConfMan.getBool("tts_enabled", "scummvm")) {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
if (ttsMan)
ttsMan->say(_osdMessageNextData);
}
// Clear the text update request
_osdMessageNextData.clear();
_osdMessageChangeRequest = false;
}
#endif
void OpenGLGraphicsManager::displayActivityIconOnOSD(const Graphics::Surface *icon) {
#ifdef USE_OSD
if (_osdIconSurface) {
delete _osdIconSurface;
_osdIconSurface = nullptr;
// Make sure the icon is cleared on the next update
_forceRedraw = true;
}
if (icon) {
Graphics::Surface *converted = icon->convertTo(_defaultFormatAlpha);
_osdIconSurface = createSurface(_defaultFormatAlpha);
assert(_osdIconSurface);
// We always filter the osd with GL_LINEAR. This assures it's
// readable in case it needs to be scaled and does not affect it
// otherwise.
_osdIconSurface->enableLinearFiltering(true);
_osdIconSurface->allocate(converted->w, converted->h);
Graphics::Surface *dst = _osdIconSurface->getSurface();
// Copy the icon to the texture
dst->copyRectToSurface(*converted, 0, 0, Common::Rect(0, 0, converted->w, converted->h));
converted->free();
delete converted;
}
#endif
}
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) const {
assert(_gameScreen->hasPalette());
memcpy(colors, _gamePalette + start * 3, num * 3);
}
void OpenGLGraphicsManager::handleResizeImpl(const int width, const int height) {
// Setup backbuffer size.
_backBuffer.setDimensions(width, height);
uint overlayWidth = width;
uint overlayHeight = height;
// WORKAROUND: We can only support surfaces up to the maximum supported
// texture size. Thus, in case we encounter a physical size bigger than
// this maximum texture size we will simply use an overlay as big as
// possible and then scale it to the physical display size. This sounds
// bad but actually all recent chips should support full HD resolution
// anyway. Thus, it should not be a real issue for modern hardware.
if ( overlayWidth > (uint)OpenGLContext.maxTextureSize
|| overlayHeight > (uint)OpenGLContext.maxTextureSize) {
const frac_t outputAspect = intToFrac(_windowWidth) / _windowHeight;
if (outputAspect > (frac_t)FRAC_ONE) {
overlayWidth = OpenGLContext.maxTextureSize;
overlayHeight = intToFrac(overlayWidth) / outputAspect;
} else {
overlayHeight = OpenGLContext.maxTextureSize;
overlayWidth = fracToInt(overlayHeight * outputAspect);
}
}
// 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.
overlayWidth = MAX<uint>(overlayWidth, 256);
overlayHeight = MAX<uint>(overlayHeight, 200);
if (!_overlay || _overlay->getFormat() != _defaultFormatAlpha) {
delete _overlay;
_overlay = nullptr;
_overlay = createSurface(_defaultFormatAlpha);
assert(_overlay);
}
_overlay->allocate(overlayWidth, overlayHeight);
_overlay->fill(0);
// Re-setup the scaling and filtering for the screen and cursor
recalculateDisplayAreas();
recalculateCursorScaling();
updateLinearFiltering();
// Something changed, so update the screen change ID.
++_screenChangeID;
}
void OpenGLGraphicsManager::notifyContextCreate(ContextType type,
const Graphics::PixelFormat &defaultFormat,
const Graphics::PixelFormat &defaultFormatAlpha) {
// Initialize pipeline.
delete _pipeline;
_pipeline = nullptr;
#if !USE_FORCED_GLES
// _libretroPipeline has just been destroyed as the pipeline
_libretroPipeline = nullptr;
#endif
OpenGLContext.initialize(type);
// Try to setup LibRetro pipeline first if available.
#if !USE_FORCED_GLES
if (LibRetroPipeline::isSupportedByContext()) {
ShaderMan.notifyCreate();
_libretroPipeline = new LibRetroPipeline();
_pipeline = _libretroPipeline;
}
#endif
#if !USE_FORCED_GLES
if (!_pipeline && OpenGLContext.shadersSupported) {
ShaderMan.notifyCreate();
_pipeline = new ShaderPipeline(ShaderMan.query(ShaderManager::kDefault));
}
#endif
#if !USE_FORCED_GLES2
if (!_pipeline) {
_pipeline = new FixedPipeline();
}
#endif
if (!_pipeline) {
error("Can't initialize any pipeline");
}
// Disable 3D properties.
GL_CALL(glDisable(GL_CULL_FACE));
GL_CALL(glDisable(GL_DEPTH_TEST));
GL_CALL(glDisable(GL_DITHER));
_pipeline->setColor(1.0f, 1.0f, 1.0f, 1.0f);
// Setup backbuffer state.
// Default to opaque black as clear color.
_backBuffer.setClearColor(0.0f, 0.0f, 0.0f, 1.0f);
_pipeline->setFramebuffer(&_backBuffer);
// We use a "pack" alignment (when reading from textures) to 4 here,
// since the only place where we really use it is the BMP screenshot
// code and that requires the same alignment too.
GL_CALL(glPixelStorei(GL_PACK_ALIGNMENT, 4));
// Refresh the output screen dimensions if some are set up.
if (_windowWidth != 0 && _windowHeight != 0) {
handleResize(_windowWidth, _windowHeight);
}
// 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->recreate();
}
if (_overlay) {
_overlay->recreate();
}
if (_cursor) {
_cursor->recreate();
}
if (_cursorMask) {
_cursorMask->recreate();
}
#ifdef USE_OSD
if (_osdMessageSurface) {
_osdMessageSurface->recreate();
}
if (_osdIconSurface) {
_osdIconSurface->recreate();
}
#endif
}
void OpenGLGraphicsManager::notifyContextDestroy() {
if (_gameScreen) {
_gameScreen->destroy();
}
if (_overlay) {
_overlay->destroy();
}
if (_cursor) {
_cursor->destroy();
}
if (_cursorMask) {
_cursorMask->destroy();
}
#ifdef USE_OSD
if (_osdMessageSurface) {
_osdMessageSurface->destroy();
}
if (_osdIconSurface) {
_osdIconSurface->destroy();
}
#endif
#if !USE_FORCED_GLES
if (OpenGLContext.shadersSupported) {
ShaderMan.notifyDestroy();
}
#endif
// Destroy rendering pipeline.
delete _pipeline;
_pipeline = nullptr;
#if !USE_FORCED_GLES
// _libretroPipeline has just been destroyed as the pipeline
_libretroPipeline = nullptr;
#endif
// Rest our context description since the context is gone soon.
OpenGLContext.reset();
}
Surface *OpenGLGraphicsManager::createSurface(const Graphics::PixelFormat &format, bool wantAlpha, bool wantScaler, bool wantMask) {
GLenum glIntFormat, glFormat, glType;
#ifdef USE_SCALERS
if (wantScaler) {
// TODO: Ensure that the requested pixel format is supported by the scaler
if (getGLPixelFormat(format, glIntFormat, glFormat, glType)) {
return new ScaledTexture(glIntFormat, glFormat, glType, format, format);
} else {
#ifdef SCUMM_LITTLE_ENDIAN
return new ScaledTexture(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24), format);
#else
return new ScaledTexture(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0), format);
#endif
}
}
#endif
if (format.bytesPerPixel == 1) {
#if !USE_FORCED_GLES
if (TextureCLUT8GPU::isSupportedByContext() && !wantMask) {
return new TextureCLUT8GPU();
}
#endif
const Graphics::PixelFormat &virtFormat = wantAlpha ? _defaultFormatAlpha : _defaultFormat;
const bool supported = getGLPixelFormat(virtFormat, glIntFormat, glFormat, glType);
if (!supported) {
return nullptr;
} else {
return new FakeTexture(glIntFormat, glFormat, glType, virtFormat, format);
}
} else if (getGLPixelFormat(format, glIntFormat, glFormat, glType)) {
return new Texture(glIntFormat, glFormat, glType, format);
} else if (OpenGLContext.packedPixelsSupported && format == Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0)) {
// OpenGL ES does not support a texture format usable for RGB555.
// Since SCUMM uses this pixel format for some games (and there is no
// hope for this to change anytime soon) we use pixel format
// conversion to a supported texture format.
return new TextureRGB555();
#ifdef SCUMM_LITTLE_ENDIAN
} else if (format == Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0)) { // RGBA8888
#else
} else if (format == Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24)) { // ABGR8888
#endif
return new TextureRGBA8888Swap();
} else {
#ifdef SCUMM_LITTLE_ENDIAN
return new FakeTexture(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24), format);
#else
return new FakeTexture(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0), format);
#endif
}
}
bool OpenGLGraphicsManager::getGLPixelFormat(const Graphics::PixelFormat &pixelFormat, GLenum &glIntFormat, GLenum &glFormat, GLenum &glType) const {
#ifdef SCUMM_LITTLE_ENDIAN
if (pixelFormat == Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24)) { // ABGR8888
#else
if (pixelFormat == Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0)) { // RGBA8888
#endif
glIntFormat = GL_RGBA;
glFormat = GL_RGBA;
glType = GL_UNSIGNED_BYTE;
return true;
} else if (!OpenGLContext.packedPixelsSupported) {
return false;
} 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;
#if !USE_FORCED_GLES && !USE_FORCED_GLES2
// The formats below are not supported by every GLES implementation.
// Thus, we do not mark them as supported when a GLES context is setup.
} else if (isGLESContext()) {
return false;
#ifdef SCUMM_LITTLE_ENDIAN
} else 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;
#endif
} else if (pixelFormat == Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0)) { // RGB555
glIntFormat = GL_RGB;
glFormat = GL_BGRA;
glType = GL_UNSIGNED_SHORT_1_5_5_5_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;
#ifdef SCUMM_BIG_ENDIAN
} 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;
#endif
} 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_RGB;
glType = GL_UNSIGNED_SHORT_5_6_5_REV;
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 // !USE_FORCED_GLES && !USE_FORCED_GLES2
} else {
return false;
}
}
bool OpenGLGraphicsManager::gameNeedsAspectRatioCorrection() const {
if (_currentState.aspectRatioCorrection) {
const uint width = getWidth();
const uint height = getHeight();
// 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.
return (width == 320 && height == 200) || (width == 640 && height == 400);
}
return false;
}
int OpenGLGraphicsManager::getGameRenderScale() const {
return _currentState.scaleFactor;
}
void OpenGLGraphicsManager::recalculateDisplayAreas() {
if (!_gameScreen) {
return;
}
WindowedGraphicsManager::recalculateDisplayAreas();
#if !USE_FORCED_GLES
if (_libretroPipeline) {
const GLTexture &gameScreenTexture = _gameScreen->getGLTexture();
_libretroPipeline->setDisplaySizes(gameScreenTexture.getLogicalWidth(), gameScreenTexture.getLogicalHeight(),
_gameDrawRect);
}
#endif
// Setup drawing limitation for game graphics.
// This involves some trickery because OpenGL's viewport coordinate system
// is upside down compared to ours.
_backBuffer.setScissorBox(_gameDrawRect.left,
_windowHeight - _gameDrawRect.height() - _gameDrawRect.top,
_gameDrawRect.width(),
_gameDrawRect.height());
_shakeOffsetScaled = Common::Point(_gameScreenShakeXOffset * _gameDrawRect.width() / (int)_currentState.gameWidth,
_gameScreenShakeYOffset * _gameDrawRect.height() / (int)_currentState.gameHeight);
// Update the cursor position to adjust for new display area.
setMousePosition(_cursorX, _cursorY);
// Force a redraw to assure screen is properly redrawn.
_forceRedraw = true;
}
void OpenGLGraphicsManager::updateCursorPalette() {
if (!_cursor || !_cursor->hasPalette()) {
return;
}
if (_cursorPaletteEnabled) {
_cursor->setPalette(0, 256, _cursorPalette);
} else {
_cursor->setPalette(0, 256, _gamePalette);
}
if (_cursorUseKey)
_cursor->setColorKey(_cursorKeyColor);
}
void OpenGLGraphicsManager::recalculateCursorScaling() {
if (!_cursor || !_gameScreen) {
return;
}
uint cursorWidth = _cursor->getWidth();
uint cursorHeight = _cursor->getHeight();
// By default we use the unscaled versions.
_cursorHotspotXScaled = _cursorHotspotX;
_cursorHotspotYScaled = _cursorHotspotY;
_cursorWidthScaled = cursorWidth;
_cursorHeightScaled = cursorHeight;
// In case scaling is actually enabled we will scale the cursor according
// to the game screen.
if (!_cursorDontScale) {
const frac_t screenScaleFactorX = intToFrac(_gameDrawRect.width()) / _gameScreen->getWidth();
const frac_t screenScaleFactorY = intToFrac(_gameDrawRect.height()) / _gameScreen->getHeight();
_cursorHotspotXScaled = fracToInt(_cursorHotspotXScaled * screenScaleFactorX);
_cursorWidthScaled = fracToDouble(cursorWidth * screenScaleFactorX);
_cursorHotspotYScaled = fracToInt(_cursorHotspotYScaled * screenScaleFactorY);
_cursorHeightScaled = fracToDouble(cursorHeight * screenScaleFactorY);
}
}
void OpenGLGraphicsManager::updateLinearFiltering() {
#if !USE_FORCED_GLES
if (_libretroPipeline) {
_libretroPipeline->enableLinearFiltering(_currentState.filtering);
}
#endif
if (_gameScreen) {
_gameScreen->enableLinearFiltering(_currentState.filtering);
}
if (_cursor) {
_cursor->enableLinearFiltering(_currentState.filtering);
}
if (_cursorMask) {
_cursorMask->enableLinearFiltering(_currentState.filtering);
}
// The overlay UI should also obey the filtering choice (managed via the Filter Graphics checkbox in Graphics Tab).
// Thus, when overlay filtering is disabled, scaling in OPENGL is done with GL_NEAREST (nearest neighbor scaling).
// It may look crude, but it should be crispier and it's left to user choice to enable filtering.
if (_overlay) {
_overlay->enableLinearFiltering(_currentState.filtering);
}
}
#ifdef USE_OSD
const Graphics::Font *OpenGLGraphicsManager::getFontOSD() const {
return FontMan.getFontByUsage(Graphics::FontManager::kLocalizedFont);
}
#endif
bool OpenGLGraphicsManager::saveScreenshot(const Common::String &filename) const {
const uint width = _windowWidth;
const uint height = _windowHeight;
// GL_PACK_ALIGNMENT is 4 so each row must be aligned to 4 bytes boundary
// A line of a BMP image must also have a size divisible by 4.
// Calculate lineSize as the next multiple of 4 after the real line size
const uint lineSize = (width * 3 + 3) & ~3;
Common::DumpFile out;
if (!out.open(filename)) {
return false;
}
Common::Array<uint8> pixels;
pixels.resize(lineSize * height);
GL_CALL(glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, &pixels.front()));
#ifdef SCUMM_LITTLE_ENDIAN
const Graphics::PixelFormat format(3, 8, 8, 8, 0, 0, 8, 16, 0);
#else
const Graphics::PixelFormat format(3, 8, 8, 8, 0, 16, 8, 0, 0);
#endif
Graphics::Surface data;
data.init(width, height, lineSize, &pixels.front(), format);
data.flipVertical(Common::Rect(width, height));
#ifdef USE_PNG
return Image::writePNG(out, data);
#else
return Image::writeBMP(out, data);
#endif
}
} // End of namespace OpenGL