diff --git a/backends/events/sdl/sdl-events.h b/backends/events/sdl/sdl-events.h index 91c0e6fa45e..e3137210b3e 100644 --- a/backends/events/sdl/sdl-events.h +++ b/backends/events/sdl/sdl-events.h @@ -24,6 +24,7 @@ #define BACKEND_EVENTS_SDL_H #include "backends/platform/sdl/sdl-sys.h" +#include "backends/graphics/sdl/sdl-graphics.h" #include "backends/graphics3d/sdl/sdl-graphics3d.h" #include "common/events.h" diff --git a/backends/graphics/opengl/context.cpp b/backends/graphics/opengl/context.cpp new file mode 100644 index 00000000000..564007bd2c2 --- /dev/null +++ b/backends/graphics/opengl/context.cpp @@ -0,0 +1,186 @@ +/* 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-sys.h" +#include "backends/graphics/opengl/opengl-graphics.h" +#include "backends/graphics/opengl/shader.h" +#include "backends/graphics/opengl/pipelines/pipeline.h" +#include "backends/graphics/opengl/framebuffer.h" + +#include "common/tokenizer.h" +#include "common/debug.h" + +namespace OpenGL { + +void Context::reset() { + maxTextureSize = 0; + + NPOTSupported = false; + shadersSupported = false; + multitextureSupported = false; + framebufferObjectSupported = false; + +#define GL_FUNC_DEF(ret, name, param) name = nullptr; +#include "backends/graphics/opengl/opengl-func.h" +#undef GL_FUNC_DEF + + activePipeline = nullptr; +} + +Pipeline *Context::setPipeline(Pipeline *pipeline) { + Pipeline *oldPipeline = activePipeline; + if (oldPipeline) { + oldPipeline->deactivate(); + } + + activePipeline = pipeline; + if (activePipeline) { + activePipeline->activate(); + } + + return oldPipeline; +} + +Context g_context; + +void OpenGLGraphicsManager::setContextType(ContextType type) { +#if USE_FORCED_GL + type = kContextGL; +#elif USE_FORCED_GLES + type = kContextGLES; +#elif USE_FORCED_GLES2 + type = kContextGLES2; +#endif + + g_context.type = type; +} + +void OpenGLGraphicsManager::initializeGLContext() { + // Initialize default state. + g_context.reset(); + + // Load all functions. + // We use horrible trickery to silence C++ compilers. + // See backends/plugins/sdl/sdl-provider.cpp for more information. + assert(sizeof(void (*)()) == sizeof(void *)); + +#define LOAD_FUNC(name, loadName) { \ + void *fn = getProcAddress(#loadName); \ + memcpy(&g_context.name, &fn, sizeof(fn)); \ +} + +#define GL_EXT_FUNC_DEF(ret, name, param) LOAD_FUNC(name, name) + +#ifdef USE_BUILTIN_OPENGL +#define GL_FUNC_DEF(ret, name, param) g_context.name = &name +#define GL_FUNC_2_DEF GL_FUNC_DEF +#else +#define GL_FUNC_DEF GL_EXT_FUNC_DEF +#define GL_FUNC_2_DEF(ret, name, extName, param) \ + if (g_context.type == kContextGL) { \ + LOAD_FUNC(name, extName); \ + } else { \ + LOAD_FUNC(name, name); \ + } +#endif +#include "backends/graphics/opengl/opengl-func.h" +#undef GL_FUNC_2_DEF +#undef GL_FUNC_DEF +#undef GL_EXT_FUNC_DEF +#undef LOAD_FUNC + + // Obtain maximum texture size. + GL_CALL(glGetIntegerv(GL_MAX_TEXTURE_SIZE, &g_context.maxTextureSize)); + debug(5, "OpenGL maximum texture size: %d", g_context.maxTextureSize); + + const char *extString = (const char *)g_context.glGetString(GL_EXTENSIONS); + debug(5, "OpenGL extensions: %s", extString); + + bool ARBShaderObjects = false; + bool ARBShadingLanguage100 = false; + bool ARBVertexShader = false; + bool ARBFragmentShader = false; + + Common::StringTokenizer tokenizer(extString, " "); + while (!tokenizer.empty()) { + Common::String token = tokenizer.nextToken(); + + if (token == "GL_ARB_texture_non_power_of_two" || token == "GL_OES_texture_npot") { + g_context.NPOTSupported = true; + } else if (token == "GL_ARB_shader_objects") { + ARBShaderObjects = true; + } else if (token == "GL_ARB_shading_language_100") { + ARBShadingLanguage100 = true; + } else if (token == "GL_ARB_vertex_shader") { + ARBVertexShader = true; + } else if (token == "GL_ARB_fragment_shader") { + ARBFragmentShader = true; + } else if (token == "GL_ARB_multitexture") { + g_context.multitextureSupported = true; + } else if (token == "GL_EXT_framebuffer_object") { + g_context.framebufferObjectSupported = true; + } + } + + if (g_context.type == kContextGLES2) { + // GLES2 always has (limited) NPOT support. + g_context.NPOTSupported = true; + + // GLES2 always has shader support. + g_context.shadersSupported = true; + + // GLES2 always has multi texture support. + g_context.multitextureSupported = true; + + // GLES2 always has FBO support. + g_context.framebufferObjectSupported = true; + } else { + g_context.shadersSupported = ARBShaderObjects & ARBShadingLanguage100 & ARBVertexShader & ARBFragmentShader; + } + + // Log context type. + switch (g_context.type) { + case kContextGL: + debug(5, "OpenGL: GL context initialized"); + break; + + case kContextGLES: + debug(5, "OpenGL: GLES context initialized"); + break; + + case kContextGLES2: + debug(5, "OpenGL: GLES2 context initialized"); + break; + + default: + warning("OpenGL: Unknown context initialized"); + break; + } + + // Log features supported by GL context. + debug(5, "OpenGL: NPOT texture support: %d", g_context.NPOTSupported); + debug(5, "OpenGL: Shader support: %d", g_context.shadersSupported); + debug(5, "OpenGL: Multitexture support: %d", g_context.multitextureSupported); + debug(5, "OpenGL: FBO support: %d", g_context.framebufferObjectSupported); +} + +} // End of namespace OpenGL diff --git a/backends/graphics/opengl/debug.cpp b/backends/graphics/opengl/debug.cpp new file mode 100644 index 00000000000..05228cec34e --- /dev/null +++ b/backends/graphics/opengl/debug.cpp @@ -0,0 +1,67 @@ +/* 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"; + default: + break; + } + + 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; + + while ((error = g_context.glGetError()) != 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 00000000000..abaa6544dc1 --- /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 GL_WRAP_DEBUG(call, name) do { (call); OpenGL::checkGLError(#name, __FILE__, __LINE__); } while (false) +#else +#define GL_WRAP_DEBUG(call, name) do { (call); } while (false) +#endif + +#endif diff --git a/backends/graphics/opengl/framebuffer.cpp b/backends/graphics/opengl/framebuffer.cpp new file mode 100644 index 00000000000..02c913c830d --- /dev/null +++ b/backends/graphics/opengl/framebuffer.cpp @@ -0,0 +1,269 @@ +/* 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/framebuffer.h" +#include "backends/graphics/opengl/texture.h" +#include "backends/graphics/opengl/pipelines/pipeline.h" + +namespace OpenGL { + +Framebuffer::Framebuffer() + : _viewport(), _projectionMatrix(), _isActive(false), _clearColor(), + _blendState(kBlendModeDisabled), _scissorTestState(false), _scissorBox() { +} + +void Framebuffer::activate() { + _isActive = true; + + applyViewport(); + applyProjectionMatrix(); + applyClearColor(); + applyBlendState(); + applyScissorTestState(); + applyScissorBox(); + + activateInternal(); +} + +void Framebuffer::deactivate() { + _isActive = false; + + deactivateInternal(); +} + +void Framebuffer::setClearColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a) { + _clearColor[0] = r; + _clearColor[1] = g; + _clearColor[2] = b; + _clearColor[3] = a; + + // Directly apply changes when we are active. + if (isActive()) { + applyClearColor(); + } +} + +void Framebuffer::enableBlend(BlendMode mode) { + _blendState = mode; + + // Directly apply changes when we are active. + if (isActive()) { + applyBlendState(); + } +} + +void Framebuffer::enableScissorTest(bool enable) { + _scissorTestState = enable; + + // Directly apply changes when we are active. + if (isActive()) { + applyScissorTestState(); + } +} + +void Framebuffer::setScissorBox(GLint x, GLint y, GLsizei w, GLsizei h) { + _scissorBox[0] = x; + _scissorBox[1] = y; + _scissorBox[2] = w; + _scissorBox[3] = h; + + // Directly apply changes when we are active. + if (isActive()) { + applyScissorBox(); + } +} + +void Framebuffer::applyViewport() { + GL_CALL(glViewport(_viewport[0], _viewport[1], _viewport[2], _viewport[3])); +} + +void Framebuffer::applyProjectionMatrix() { + g_context.getActivePipeline()->setProjectionMatrix(_projectionMatrix); +} + +void Framebuffer::applyClearColor() { + GL_CALL(glClearColor(_clearColor[0], _clearColor[1], _clearColor[2], _clearColor[3])); +} + +void Framebuffer::applyBlendState() { + switch (_blendState) { + case kBlendModeDisabled: + GL_CALL(glDisable(GL_BLEND)); + break; + case kBlendModeTraditionalTransparency: + GL_CALL(glEnable(GL_BLEND)); + GL_CALL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + break; + case kBlendModePremultipliedTransparency: + GL_CALL(glEnable(GL_BLEND)); + GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); + break; + default: + break; + } +} + +void Framebuffer::applyScissorTestState() { + if (_scissorTestState) { + GL_CALL(glEnable(GL_SCISSOR_TEST)); + } else { + GL_CALL(glDisable(GL_SCISSOR_TEST)); + } +} + +void Framebuffer::applyScissorBox() { + GL_CALL(glScissor(_scissorBox[0], _scissorBox[1], _scissorBox[2], _scissorBox[3])); +} + +// +// Backbuffer implementation +// + +void Backbuffer::activateInternal() { +#if !USE_FORCED_GLES + if (g_context.framebufferObjectSupported) { + GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, 0)); + } +#endif +} + +void Backbuffer::setDimensions(uint width, uint height) { + // Set viewport dimensions. + _viewport[0] = 0; + _viewport[1] = 0; + _viewport[2] = width; + _viewport[3] = height; + + // Setup orthogonal projection matrix. + _projectionMatrix[ 0] = 2.0f / width; + _projectionMatrix[ 1] = 0.0f; + _projectionMatrix[ 2] = 0.0f; + _projectionMatrix[ 3] = 0.0f; + + _projectionMatrix[ 4] = 0.0f; + _projectionMatrix[ 5] = -2.0f / height; + _projectionMatrix[ 6] = 0.0f; + _projectionMatrix[ 7] = 0.0f; + + _projectionMatrix[ 8] = 0.0f; + _projectionMatrix[ 9] = 0.0f; + _projectionMatrix[10] = 0.0f; + _projectionMatrix[11] = 0.0f; + + _projectionMatrix[12] = -1.0f; + _projectionMatrix[13] = 1.0f; + _projectionMatrix[14] = 0.0f; + _projectionMatrix[15] = 1.0f; + + // Directly apply changes when we are active. + if (isActive()) { + applyViewport(); + applyProjectionMatrix(); + } +} + +// +// Render to texture target implementation +// + +#if !USE_FORCED_GLES +TextureTarget::TextureTarget() + : _texture(new GLTexture(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE)), _glFBO(0), _needUpdate(true) { +} + +TextureTarget::~TextureTarget() { + delete _texture; + GL_CALL_SAFE(glDeleteFramebuffers, (1, &_glFBO)); +} + +void TextureTarget::activateInternal() { + // Allocate framebuffer object if necessary. + if (!_glFBO) { + GL_CALL(glGenFramebuffers(1, &_glFBO)); + _needUpdate = true; + } + + // Attach destination texture to FBO. + GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, _glFBO)); + + // If required attach texture to FBO. + if (_needUpdate) { + GL_CALL(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture->getGLTexture(), 0)); + _needUpdate = false; + } +} + +void TextureTarget::destroy() { + GL_CALL(glDeleteFramebuffers(1, &_glFBO)); + _glFBO = 0; + + _texture->destroy(); +} + +void TextureTarget::create() { + _texture->create(); + + _needUpdate = true; +} + +void TextureTarget::setSize(uint width, uint height) { + _texture->setSize(width, height); + + const uint texWidth = _texture->getWidth(); + const uint texHeight = _texture->getHeight(); + + // Set viewport dimensions. + _viewport[0] = 0; + _viewport[1] = 0; + _viewport[2] = texWidth; + _viewport[3] = texHeight; + + // Setup orthogonal projection matrix. + _projectionMatrix[ 0] = 2.0f / texWidth; + _projectionMatrix[ 1] = 0.0f; + _projectionMatrix[ 2] = 0.0f; + _projectionMatrix[ 3] = 0.0f; + + _projectionMatrix[ 4] = 0.0f; + _projectionMatrix[ 5] = 2.0f / texHeight; + _projectionMatrix[ 6] = 0.0f; + _projectionMatrix[ 7] = 0.0f; + + _projectionMatrix[ 8] = 0.0f; + _projectionMatrix[ 9] = 0.0f; + _projectionMatrix[10] = 0.0f; + _projectionMatrix[11] = 0.0f; + + _projectionMatrix[12] = -1.0f; + _projectionMatrix[13] = -1.0f; + _projectionMatrix[14] = 0.0f; + _projectionMatrix[15] = 1.0f; + + // Directly apply changes when we are active. + if (isActive()) { + applyViewport(); + applyProjectionMatrix(); + } +} +#endif // !USE_FORCED_GLES + +} // End of namespace OpenGL diff --git a/backends/graphics/opengl/framebuffer.h b/backends/graphics/opengl/framebuffer.h new file mode 100644 index 00000000000..2f0245faa7c --- /dev/null +++ b/backends/graphics/opengl/framebuffer.h @@ -0,0 +1,198 @@ +/* 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_FRAMEBUFFER_H +#define BACKENDS_GRAPHICS_OPENGL_FRAMEBUFFER_H + +#include "backends/graphics/opengl/opengl-sys.h" + +namespace OpenGL { + +/** + * Object describing a framebuffer OpenGL can render to. + */ +class Framebuffer { + friend class Pipeline; +public: + Framebuffer(); + virtual ~Framebuffer() {}; + +public: + enum BlendMode { + /** + * Newly drawn pixels overwrite the existing contents of the framebuffer + * without mixing with them + */ + kBlendModeDisabled, + + /** + * Newly drawn pixels mix with the framebuffer based on their alpha value + * for transparency. + */ + kBlendModeTraditionalTransparency, + + /** + * Newly drawn pixels mix with the framebuffer based on their alpha value + * for transparency. + * + * Requires the image data being drawn to have its color values pre-multipled + * with the alpha value. + */ + kBlendModePremultipliedTransparency + }; + + /** + * Set the clear color of the framebuffer. + */ + void setClearColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a); + + /** + * Enable/disable GL_BLEND. + */ + void enableBlend(BlendMode mode); + + /** + * Enable/disable GL_SCISSOR_TEST. + */ + void enableScissorTest(bool enable); + + /** + * Set scissor box dimensions. + */ + void setScissorBox(GLint x, GLint y, GLsizei w, GLsizei h); + + /** + * Obtain projection matrix of the framebuffer. + */ + const GLfloat *getProjectionMatrix() const { return _projectionMatrix; } +protected: + bool isActive() const { return _isActive; } + + GLint _viewport[4]; + void applyViewport(); + + GLfloat _projectionMatrix[4*4]; + void applyProjectionMatrix(); + + /** + * Activate framebuffer. + * + * This is supposed to set all state associated with the framebuffer. + */ + virtual void activateInternal() = 0; + + /** + * Deactivate framebuffer. + * + * This is supposed to make any cleanup required when unbinding the + * framebuffer. + */ + virtual void deactivateInternal() {} + +private: + /** + * Accessor to activate framebuffer for pipeline. + */ + void activate(); + + /** + * Accessor to deactivate framebuffer from pipeline. + */ + void deactivate(); + +private: + bool _isActive; + + GLfloat _clearColor[4]; + void applyClearColor(); + + BlendMode _blendState; + void applyBlendState(); + + bool _scissorTestState; + void applyScissorTestState(); + + GLint _scissorBox[4]; + void applyScissorBox(); +}; + +/** + * Default back buffer implementation. + */ +class Backbuffer : public Framebuffer { +public: + /** + * Set the dimensions (a.k.a. size) of the back buffer. + */ + void setDimensions(uint width, uint height); + +protected: + virtual void activateInternal(); +}; + +#if !USE_FORCED_GLES +class GLTexture; + +/** + * Render to texture framebuffer implementation. + * + * This target allows to render to a texture, which can then be used for + * further rendering. + */ +class TextureTarget : public Framebuffer { +public: + TextureTarget(); + virtual ~TextureTarget(); + + /** + * Notify that the GL context is about to be destroyed. + */ + void destroy(); + + /** + * Notify that the GL context has been created. + */ + void create(); + + /** + * Set size of the texture target. + */ + void setSize(uint width, uint height); + + /** + * Query pointer to underlying GL texture. + */ + GLTexture *getTexture() const { return _texture; } + +protected: + virtual void activateInternal(); + +private: + GLTexture *_texture; + GLuint _glFBO; + bool _needUpdate; +}; +#endif + +} // End of namespace OpenGL + +#endif diff --git a/backends/graphics/opengl/opengl-defs.h b/backends/graphics/opengl/opengl-defs.h new file mode 100644 index 00000000000..733fc2933c4 --- /dev/null +++ b/backends/graphics/opengl/opengl-defs.h @@ -0,0 +1,262 @@ +/* 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. + * + */ + +/* This file is based on Mesa 3-D's gl.h and GLES/gl.h from Khronos Registry. + * + * Mesa 3-D's gl.h file is distributed under the following license: + * Mesa 3-D graphics library + * + * Copyright (C) 1999-2006 Brian Paul All Rights Reserved. + * Copyright (C) 2009 VMware, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * + * GLES/gl.h from Khronos Registry is distributed under the following license: + * This document is licensed under the SGI Free Software B License Version + * 2.0. For details, see http://oss.sgi.com/projects/FreeB/ . + */ + +#ifndef BACKENDS_GRAPHICS_OPENGL_OPENGL_DEFS_H +#define BACKENDS_GRAPHICS_OPENGL_OPENGL_DEFS_H + +#include "common/scummsys.h" + +/* + * Datatypes + */ +typedef uint GLenum; +typedef uint8 GLboolean; +typedef uint GLbitfield; +typedef void GLvoid; +typedef int8 GLbyte; /* 1-byte signed */ +typedef int16 GLshort; /* 2-byte signed */ +typedef int32 GLint; /* 4-byte signed */ +typedef uint8 GLubyte; /* 1-byte unsigned */ +typedef uint16 GLushort; /* 2-byte unsigned */ +typedef uint32 GLuint; /* 4-byte unsigned */ +typedef int32 GLsizei; /* 4-byte signed */ +typedef float GLfloat; /* single precision float */ +typedef float GLclampf; /* single precision float in [0,1] */ +typedef double GLdouble; /* double precision float */ +typedef double GLclampd; /* double precision float in [0,1] */ +typedef char GLchar; +#if defined(MACOSX) +typedef void *GLhandleARB; +#else +typedef uint GLhandleARB; +#endif + +// This is an addition from us to alias ARB shader object extensions to +// OpenGL (ES) 2.0 style functions. It only works when GLhandleARB and GLuint +// are type compatible. +typedef GLhandleARB GLprogram; +typedef GLhandleARB GLshader; + +/* + * Constants + */ + +/* Boolean constants */ +#define GL_FALSE 0 +#define GL_TRUE 1 + +/* StringName */ +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 + +/* ErrorCode */ +#define GL_NO_ERROR 0 +#define GL_INVALID_ENUM 0x0500 +#define GL_INVALID_VALUE 0x0501 +#define GL_INVALID_OPERATION 0x0502 +#define GL_STACK_OVERFLOW 0x0503 +#define GL_STACK_UNDERFLOW 0x0504 +#define GL_OUT_OF_MEMORY 0x0505 + +/* ClearBufferMask */ +#define GL_DEPTH_BUFFER_BIT 0x00000100 +#define GL_STENCIL_BUFFER_BIT 0x00000400 +#define GL_COLOR_BUFFER_BIT 0x00004000 + +/* Scissor box */ +#define GL_SCISSOR_BOX 0x0C10 +#define GL_SCISSOR_TEST 0x0C11 + +/* MatrixMode */ +#define GL_MATRIX_MODE 0x0BA0 +#define GL_MODELVIEW 0x1700 +#define GL_PROJECTION 0x1701 +#define GL_TEXTURE 0x1702 + +/* EnableCap */ +#define GL_FOG 0x0B60 +#define GL_LIGHTING 0x0B50 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_CULL_FACE 0x0B44 +#define GL_ALPHA_TEST 0x0BC0 +#define GL_BLEND 0x0BE2 +#define GL_DITHER 0x0BD0 +#define GL_DEPTH_TEST 0x0B71 +#define GL_VERTEX_ARRAY 0x8074 +#define GL_COLOR_ARRAY 0x8076 +#define GL_TEXTURE_COORD_ARRAY 0x8078 + +/* ShadingModel */ +#define GL_FLAT 0x1D00 +#define GL_SMOOTH 0x1D01 + +/* HintMode */ +#define GL_DONT_CARE 0x1100 +#define GL_FASTEST 0x1101 +#define GL_NICEST 0x1102 + +/* HintTarget */ +#define GL_PERSPECTIVE_CORRECTION_HINT 0x0C50 +#define GL_POINT_SMOOTH_HINT 0x0C51 +#define GL_LINE_SMOOTH_HINT 0x0C52 +#define GL_FOG_HINT 0x0C54 +#define GL_GENERATE_MIPMAP_HINT 0x8192 + +/* BlendingFactorDest */ +#define GL_ZERO 0 +#define GL_ONE 1 +#define GL_SRC_COLOR 0x0300 +#define GL_ONE_MINUS_SRC_COLOR 0x0301 +#define GL_SRC_ALPHA 0x0302 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_DST_ALPHA 0x0304 +#define GL_ONE_MINUS_DST_ALPHA 0x0305 + +/* BlendingFactorSrc */ +/* GL_ZERO */ +/* GL_ONE */ +#define GL_DST_COLOR 0x0306 +#define GL_ONE_MINUS_DST_COLOR 0x0307 +#define GL_SRC_ALPHA_SATURATE 0x0308 +/* GL_SRC_ALPHA */ +/* GL_ONE_MINUS_SRC_ALPHA */ +/* GL_DST_ALPHA */ +/* GL_ONE_MINUS_DST_ALPHA */ + +/* PixelFormat */ +#define GL_ALPHA 0x1906 +#define GL_RGB 0x1907 +#define GL_RGBA 0x1908 +#define GL_BGR 0x80E0 +#define GL_BGRA 0x80E1 + +#define GL_RED 0x1903 +#define GL_R8 0x8229 + +/* PixelStoreParameter */ +#define GL_UNPACK_ALIGNMENT 0x0CF5 +#define GL_PACK_ALIGNMENT 0x0D05 + +/* DataType */ +#define GL_BYTE 0x1400 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_SHORT 0x1402 +#define GL_UNSIGNED_SHORT 0x1403 +#define GL_FLOAT 0x1406 +#define GL_FIXED 0x140C + +/* PixelType */ +/* GL_UNSIGNED_BYTE */ +#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 + +#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 +#define GL_UNSIGNED_INT_8_8_8_8 0x8035 +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 + +/* Implementation limits */ +#define GL_MAX_TEXTURE_SIZE 0x0D33 + +/* TextureMagFilter */ +#define GL_NEAREST 0x2600 +#define GL_LINEAR 0x2601 + +/* TextureParameterName */ +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_WRAP_T 0x2803 + +/* TextureWrapMode */ +#define GL_REPEAT 0x2901 +#define GL_CLAMP_TO_EDGE 0x812F + +/* BeginMode */ +#define GL_POINTS 0x0000 +#define GL_LINES 0x0001 +#define GL_LINE_LOOP 0x0002 +#define GL_LINE_STRIP 0x0003 +#define GL_TRIANGLES 0x0004 +#define GL_TRIANGLE_STRIP 0x0005 +#define GL_TRIANGLE_FAN 0x0006 + +/* Shaders */ +#define GL_FRAGMENT_SHADER 0x8B30 + +#define GL_VERTEX_SHADER 0x8B31 + +/* Programs */ +#define GL_COMPILE_STATUS 0x8B81 +#define GL_LINK_STATUS 0x8B82 +#define GL_INFO_LOG_LENGTH 0x8B84 +#define GL_CURRENT_PROGRAM 0x8B8D + +/* Textures */ +#define GL_TEXTURE0 0x84C0 +#define GL_TEXTURE1 0x84C1 + +/* GetPName */ +#define GL_VIEWPORT 0x0BA2 +#define GL_FRAMEBUFFER_BINDING 0x8CA6 + +/* Framebuffer objects */ +#define GL_COLOR_ATTACHMENT0 0x8CE0 +#define GL_FRAMEBUFFER 0x8D40 + +#endif diff --git a/backends/graphics/opengl/opengl-func.h b/backends/graphics/opengl/opengl-func.h new file mode 100644 index 00000000000..4e44c13d0fb --- /dev/null +++ b/backends/graphics/opengl/opengl-func.h @@ -0,0 +1,153 @@ +/* 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. + * + */ + +/* This file is based on Mesa 3-D's gl.h and GLES/gl.h from Khronos Registry. + * + * Mesa 3-D's gl.h file is distributed under the following license: + * Mesa 3-D graphics library + * + * Copyright (C) 1999-2006 Brian Paul All Rights Reserved. + * Copyright (C) 2009 VMware, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * + * GLES/gl.h from Khronos Registry is distributed under the following license: + * This document is licensed under the SGI Free Software B License Version + * 2.0. For details, see http://oss.sgi.com/projects/FreeB/ . + */ + +/* + * This file is a template file to be used inside specific locations in the + * OpenGL graphics code. It is not to be included otherwise. It intentionally + * does not contain include guards because it can be required to include it + * multiple times in a source file. + * + * Functions are defined by three different user supplied macros: + * GL_FUNC_DEF: Define a (builtin) OpenGL (ES) function. + * GL_FUNC_2_DEF: Define a OpenGL (ES) 2.0 function which can be provided by + * extensions in OpenGL 1.x contexts. + * GL_EXT_FUNC_DEF: Define an OpenGL (ES) extension function. + */ + +#if !defined(GL_FUNC_2_DEF) +#define GL_FUNC_2_DEF(ret, name, extName, param) GL_FUNC_DEF(ret, name, param) +#define DEFINED_GL_FUNC_2_DEF +#endif + +#if !defined(GL_EXT_FUNC_DEF) +#define GL_EXT_FUNC_DEF(ret, name, param) GL_FUNC_DEF(ret, name, param) +#define DEFINED_GL_EXT_FUNC_DEF +#endif + +GL_FUNC_DEF(void, glEnable, (GLenum cap)); +GL_FUNC_DEF(void, glDisable, (GLenum cap)); +GL_FUNC_DEF(GLboolean, glIsEnabled, (GLenum cap)); +GL_FUNC_DEF(void, glClear, (GLbitfield mask)); +GL_FUNC_DEF(void, glColor4f, (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)); +GL_FUNC_DEF(void, glViewport, (GLint x, GLint y, GLsizei width, GLsizei height)); +GL_FUNC_DEF(void, glMatrixMode, (GLenum mode)); +GL_FUNC_DEF(void, glLoadIdentity, ()); +GL_FUNC_DEF(void, glLoadMatrixf, (const GLfloat *m)); +GL_FUNC_DEF(void, glShadeModel, (GLenum mode)); +GL_FUNC_DEF(void, glHint, (GLenum target, GLenum mode)); +GL_FUNC_DEF(void, glClearColor, (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha)); +GL_FUNC_DEF(void, glBlendFunc, (GLenum sfactor, GLenum dfactor)); +GL_FUNC_DEF(void, glEnableClientState, (GLenum array)); +GL_FUNC_DEF(void, glPixelStorei, (GLenum pname, GLint param)); +GL_FUNC_DEF(void, glScissor, (GLint x, GLint y, GLsizei width, GLsizei height)); +GL_FUNC_DEF(void, glReadPixels, (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels)); +GL_FUNC_DEF(void, glGetIntegerv, (GLenum pname, GLint *params)); +GL_FUNC_DEF(void, glDeleteTextures, (GLsizei n, const GLuint *textures)); +GL_FUNC_DEF(void, glGenTextures, (GLsizei n, GLuint *textures)); +GL_FUNC_DEF(void, glBindTexture, (GLenum target, GLuint texture)); +GL_FUNC_DEF(void, glTexParameteri, (GLenum target, GLenum pname, GLint param)); +GL_FUNC_DEF(void, glTexImage2D, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels)); +GL_FUNC_DEF(void, glTexCoordPointer, (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer)); +GL_FUNC_DEF(void, glVertexPointer, (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer)); +GL_FUNC_DEF(void, glDrawArrays, (GLenum mode, GLint first, GLsizei count)); +GL_FUNC_DEF(void, glTexSubImage2D, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels)); +GL_FUNC_DEF(const GLubyte *, glGetString, (GLenum name)); +GL_FUNC_DEF(GLenum, glGetError, ()); + +#if !USE_FORCED_GLES +GL_FUNC_2_DEF(void, glEnableVertexAttribArray, glEnableVertexAttribArrayARB, (GLuint index)); +GL_FUNC_2_DEF(void, glDisableVertexAttribArray, glDisableVertexAttribArrayARB, (GLuint index)); +GL_FUNC_2_DEF(void, glUniform1i, glUniform1iARB, (GLint location, GLint v0)); +GL_FUNC_2_DEF(void, glUniform1f, glUniform1fARB, (GLint location, GLfloat v0)); +GL_FUNC_2_DEF(void, glUniformMatrix4fv, glUniformMatrix4fvARB, (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value)); +GL_FUNC_2_DEF(void, glVertexAttrib4f, glVertexAttrib4fARB, (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w)); +GL_FUNC_2_DEF(void, glVertexAttribPointer, glVertexAttribPointerARB, (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer)); + +GL_FUNC_2_DEF(GLprogram, glCreateProgram, glCreateProgramObjectARB, ()); +GL_FUNC_2_DEF(void, glDeleteProgram, glDeleteObjectARB, (GLprogram program)); +GL_FUNC_2_DEF(void, glAttachShader, glAttachObjectARB, (GLprogram program, GLshader shader)); +GL_FUNC_2_DEF(void, glDetachShader, glDetachObjectARB, (GLprogram program, GLshader shader)); +GL_FUNC_2_DEF(void, glLinkProgram, glLinkProgramARB, (GLprogram program)); +GL_FUNC_2_DEF(void, glUseProgram, glUseProgramObjectARB, (GLprogram program)); +GL_FUNC_2_DEF(void, glGetProgramiv, glGetObjectParameterivARB, (GLprogram program, GLenum pname, GLint *params)); +GL_FUNC_2_DEF(void, glGetProgramInfoLog, glGetInfoLogARB, (GLprogram program, GLsizei bufSize, GLsizei *length, GLchar *infoLog)); +GL_FUNC_2_DEF(void, glBindAttribLocation, glBindAttribLocationARB, (GLprogram program, GLuint index, const GLchar *name)); +GL_FUNC_2_DEF(GLint, glGetAttribLocation, glGetAttribLocationARB, (GLprogram program, const GLchar *name)); +GL_FUNC_2_DEF(GLint, glGetUniformLocation, glGetUniformLocationARB, (GLprogram program, const GLchar *name)); + +GL_FUNC_2_DEF(GLshader, glCreateShader, glCreateShaderObjectARB, (GLenum type)); +GL_FUNC_2_DEF(void, glDeleteShader, glDeleteObjectARB, (GLshader shader)); +GL_FUNC_2_DEF(void, glGetShaderiv, glGetObjectParameterivARB, (GLshader shader, GLenum pname, GLint *params)); +GL_FUNC_2_DEF(void, glGetShaderInfoLog, glGetInfoLogARB, (GLshader shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog)); +GL_FUNC_2_DEF(void, glShaderSource, glShaderSourceARB, (GLshader shader, GLsizei count, const GLchar *const *string, const GLint *length)); +GL_FUNC_2_DEF(void, glCompileShader, glCompileShaderARB, (GLshader shader)); + +GL_FUNC_2_DEF(void, glBindFramebuffer, glBindFramebufferEXT, (GLenum target, GLuint renderbuffer)); +GL_FUNC_2_DEF(void, glDeleteFramebuffers, glDeleteFramebuffersEXT, (GLsizei n, const GLuint *framebuffers)); +GL_FUNC_2_DEF(void, glGenFramebuffers, glGenFramebuffersEXT, (GLsizei n, GLuint *renderbuffers)); +GL_FUNC_2_DEF(void, glFramebufferTexture2D, glFramebufferTexture2DEXT, (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level)); +GL_FUNC_2_DEF(GLenum, glCheckFramebufferStatus, glCheckFramebufferStatusEXT, (GLenum target)); + +GL_FUNC_2_DEF(void, glActiveTexture, glActiveTextureARB, (GLenum texture)); +#endif + +#ifdef DEFINED_GL_EXT_FUNC_DEF +#undef DEFINED_GL_EXT_FUNC_DEF +#undef GL_EXT_FUNC_DEF +#endif + +#ifdef DEFINED_GL_FUNC_2_DEF +#undef DEFINED_GL_FUNC_2_DEF +#undef GL_FUNC_2_DEF +#endif diff --git a/backends/graphics/opengl/opengl-graphics.cpp b/backends/graphics/opengl/opengl-graphics.cpp new file mode 100644 index 00000000000..8af87abcc26 --- /dev/null +++ b/backends/graphics/opengl/opengl-graphics.cpp @@ -0,0 +1,1345 @@ +/* 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/pipelines/pipeline.h" +#include "backends/graphics/opengl/pipelines/fixed.h" +#include "backends/graphics/opengl/pipelines/shader.h" +#include "backends/graphics/opengl/shader.h" + +#include "common/array.h" +#include "common/textconsole.h" +#include "common/translation.h" +#include "common/algorithm.h" +#include "common/file.h" +#include "gui/debugger.h" +#include "engines/engine.h" +#ifdef USE_OSD +#include "common/tokenizer.h" +#include "common/rect.h" +#endif + +#include "graphics/conversion.h" +#ifdef USE_OSD +#include "graphics/fontman.h" +#include "graphics/font.h" +#endif + +#ifdef USE_PNG +#include "image/png.h" +#else +#include "image/bmp.h" +#endif + +#ifdef USE_TTS +#include "common/text-to-speech.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), + _cursorHotspotX(0), _cursorHotspotY(0), + _cursorHotspotXScaled(0), _cursorHotspotYScaled(0), _cursorWidthScaled(0), _cursorHeightScaled(0), + _cursorKeyColor(0), _cursorDontScale(false), _cursorPaletteEnabled(false) +#ifdef USE_OSD + , _osdMessageChangeRequest(false), _osdMessageAlpha(0), _osdMessageFadeStartTime(0), _osdMessageSurface(nullptr), + _osdIconSurface(nullptr) +#endif + { + memset(_gamePalette, 0, sizeof(_gamePalette)); + g_context.reset(); +} + +OpenGLGraphicsManager::~OpenGLGraphicsManager() { + delete _gameScreen; + delete _overlay; + delete _cursor; +#ifdef USE_OSD + delete _osdMessageSurface; + delete _osdIconSurface; +#endif +#if !USE_FORCED_GLES + ShaderManager::destroy(); +#endif +} + +bool OpenGLGraphicsManager::hasFeature(OSystem::Feature f) const { + switch (f) { + case OSystem::kFeatureAspectRatioCorrection: + case OSystem::kFeatureCursorPalette: + case OSystem::kFeatureFilteringMode: + case OSystem::kFeatureStretchMode: + 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::kFeatureFilteringMode: + assert(_transactionMode != kTransactionNone); + _currentState.filtering = enable; + + if (_gameScreen) { + _gameScreen->enableLinearFiltering(enable); + } + + if (_cursor) { + _cursor->enableLinearFiltering(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) { + 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 OpenGLGraphicsManager::getSupportedFormats() const { + Common::List 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}, + {"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; +} + +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 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 { + const uint desiredAspect = getDesiredGameAspectRatio(); + const uint requestedWidth = _currentState.gameWidth; + const uint requestedHeight = intToFrac(requestedWidth) / desiredAspect; + + if (!loadVideoMode(requestedWidth, requestedHeight, +#ifdef USE_RGB_COLOR + _currentState.gameFormat +#else + Graphics::PixelFormat::createFormatCLUT8() +#endif + ) + // 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)g_context.maxTextureSize + || _currentState.gameHeight > (uint)g_context.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; + } + + // 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; + +#ifdef USE_RGB_COLOR + _gameScreen = createSurface(_currentState.gameFormat); +#else + _gameScreen = createSurface(Graphics::PixelFormat::createFormatCLUT8()); +#endif + assert(_gameScreen); + if (_gameScreen->hasPalette()) { + _gameScreen->setPalette(0, 256, _gamePalette); + } + + _gameScreen->allocate(_currentState.gameWidth, _currentState.gameHeight); + _gameScreen->enableLinearFiltering(_currentState.filtering); + // 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(); + + // 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::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() + && !(_overlayVisible && _overlay->isDirty()) + && !(_cursorVisible && _cursor && _cursor->isDirty()) +#ifdef USE_OSD + && !_osdMessageSurface && !_osdIconSurface +#endif + ) { + return; + } + + // Update changes to textures. + _gameScreen->updateGLTexture(); + if (_cursorVisible && _cursor) { + _cursor->updateGLTexture(); + } + _overlay->updateGLTexture(); + + // 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); + } + + // Alpha blending is disabled when drawing the screen + _backBuffer.enableBlend(Framebuffer::kBlendModeDisabled); + + // First step: Draw the (virtual) game screen. + g_context.getActivePipeline()->drawTexture(_gameScreen->getGLTexture(), _gameDrawRect.left, _gameDrawRect.top, _gameDrawRect.width(), _gameDrawRect.height()); + + // Second step: Draw the overlay if visible. + if (_overlayVisible) { + int dstX = (_windowWidth - _overlayDrawRect.width()) / 2; + int dstY = (_windowHeight - _overlayDrawRect.height()) / 2; + _backBuffer.enableBlend(Framebuffer::kBlendModeTraditionalTransparency); + g_context.getActivePipeline()->drawTexture(_overlay->getGLTexture(), dstX, dstY, _overlayDrawRect.width(), _overlayDrawRect.height()); + } + + // Third step: Draw the cursor if visible. + if (_cursorVisible && _cursor) { + _backBuffer.enableBlend(Framebuffer::kBlendModePremultipliedTransparency); + + g_context.getActivePipeline()->drawTexture(_cursor->getGLTexture(), + _cursorX - _cursorHotspotXScaled, + _cursorY - _cursorHotspotYScaled, + _cursorWidthScaled, _cursorHeightScaled); + } + + 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. + g_context.getActivePipeline()->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. + g_context.getActivePipeline()->drawTexture(_osdMessageSurface->getGLTexture(), + dstX, dstY, _osdMessageSurface->getWidth(), _osdMessageSurface->getHeight()); + + // Reset color. + g_context.getActivePipeline()->setColor(1.0f, 1.0f, 1.0f, 1.0f); + + if (_osdMessageAlpha <= 0) { + delete _osdMessageSurface; + _osdMessageSurface = nullptr; + } + } + + if (_osdIconSurface) { + int dstX = _windowWidth - _osdIconSurface->getWidth() - kOSDIconRightMargin; + int dstY = kOSDIconTopMargin; + + // Draw the OSD icon texture. + g_context.getActivePipeline()->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(void *buf, int pitch) const { + 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; + } +} + +namespace { +template +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) { + for (uint y = 0; y < h; ++y) { + for (uint x = 0; x < w; ++x) { + const uint32 color = *(const SrcColor *)src; + + if (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) { + + _cursorKeyColor = keycolor; + _cursorHotspotX = hotspotX; + _cursorHotspotY = hotspotY; + _cursorDontScale = dontScale; + + if (!w || !h) { + delete _cursor; + _cursor = nullptr; + return; + } + + 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; + + 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); + assert(_cursor); + _cursor->enableLinearFiltering(_currentState.filtering); + } + + _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; + + // 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 (dst->format.bytesPerPixel == 2) { + if (inputFormat.bytesPerPixel == 2) { + multiplyColorWithAlpha((const byte *) buf, (byte *) dst->getPixels(), w, h, + inputFormat, dst->format, srcPitch, dst->pitch, keycolor); + } else if (inputFormat.bytesPerPixel == 4) { + multiplyColorWithAlpha((const byte *) buf, (byte *) dst->getPixels(), w, h, + inputFormat, dst->format, srcPitch, dst->pitch, keycolor); + } + } else { + if (inputFormat.bytesPerPixel == 2) { + multiplyColorWithAlpha((const byte *) buf, (byte *) dst->getPixels(), w, h, + inputFormat, dst->format, srcPitch, dst->pitch, keycolor); + } else if (inputFormat.bytesPerPixel == 4) { + multiplyColorWithAlpha((const byte *) buf, (byte *) dst->getPixels(), w, h, + inputFormat, dst->format, srcPitch, dst->pitch, keycolor); + } + } + + // Flag the texture as dirty. + _cursor->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 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(width, font->getStringWidth(osdLines[i]) + 14); + } + + // Clip the rect + width = MIN(width, _gameDrawRect.width()); + height = MIN(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); + } + + _osdMessageSurface->updateGLTexture(); + + // Init the OSD display parameters. + _osdMessageAlpha = kOSDMessageInitialAlpha; + _osdMessageFadeStartTime = g_system->getMillis() + kOSDMessageFadeOutDelay; + +#ifdef USE_TTS + if (ConfMan.hasKey("tts_enabled", "scummvm") && + ConfMan.getBool("tts_enabled", "scummvm")) { + Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager(); + if (ttsMan) + ttsMan->say(_osdMessageNextData); + } +#endif // USE_TTS + // 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, const int xdpi, const int ydpi) { + // 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)g_context.maxTextureSize + || overlayHeight > (uint)g_context.maxTextureSize) { + const frac_t outputAspect = intToFrac(_windowWidth) / _windowHeight; + + if (outputAspect > (frac_t)FRAC_ONE) { + overlayWidth = g_context.maxTextureSize; + overlayHeight = intToFrac(overlayWidth) / outputAspect; + } else { + overlayHeight = g_context.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(overlayWidth, 256); + overlayHeight = MAX(overlayHeight, 200); + + // HACK: Reduce the size of the overlay on high DPI screens. + overlayWidth = fracToInt(overlayWidth * (intToFrac(90) / xdpi)); + overlayHeight = fracToInt(overlayHeight * (intToFrac(90) / ydpi)); + + if (!_overlay || _overlay->getFormat() != _defaultFormatAlpha) { + delete _overlay; + _overlay = nullptr; + + _overlay = createSurface(_defaultFormatAlpha); + assert(_overlay); + // We always filter the overlay with GL_LINEAR. This assures it's + // readable in case it needs to be scaled and does not affect it + // otherwise. + _overlay->enableLinearFiltering(true); + } + _overlay->allocate(overlayWidth, overlayHeight); + _overlay->fill(0); + + // Re-setup the scaling for the screen and cursor + recalculateDisplayAreas(); + recalculateCursorScaling(); + + // Something changed, so update the screen change ID. + ++_screenChangeID; +} + +void OpenGLGraphicsManager::notifyContextCreate(const Graphics::PixelFormat &defaultFormat, const Graphics::PixelFormat &defaultFormatAlpha) { + // Initialize context for use. + initializeGLContext(); + + // Initialize pipeline. + delete _pipeline; + _pipeline = nullptr; + +#if !USE_FORCED_GLES + if (g_context.shadersSupported) { + ShaderMan.notifyCreate(); + _pipeline = new ShaderPipeline(ShaderMan.query(ShaderManager::kDefault)); + } +#endif + +#if !USE_FORCED_GLES2 + if (_pipeline == nullptr) { + _pipeline = new FixedPipeline(); + } +#endif + + g_context.setPipeline(_pipeline); + + // Disable 3D properties. + GL_CALL(glDisable(GL_CULL_FACE)); + GL_CALL(glDisable(GL_DEPTH_TEST)); + GL_CALL(glDisable(GL_DITHER)); + + g_context.getActivePipeline()->setColor(1.0f, 1.0f, 1.0f, 1.0f); + + // Setup backbuffer state. + + // Default to black as clear color. + _backBuffer.setClearColor(0.0f, 0.0f, 0.0f, 0.0f); + + g_context.getActivePipeline()->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, _xdpi, _ydpi); + } + + // 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(); + } + +#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(); + } + +#ifdef USE_OSD + if (_osdMessageSurface) { + _osdMessageSurface->destroy(); + } + + if (_osdIconSurface) { + _osdIconSurface->destroy(); + } +#endif + +#if !USE_FORCED_GLES + if (g_context.shadersSupported) { + ShaderMan.notifyDestroy(); + } +#endif + + // Destroy rendering pipeline. + g_context.setPipeline(nullptr); + delete _pipeline; + _pipeline = nullptr; + + // Rest our context description since the context is gone soon. + g_context.reset(); +} + +Surface *OpenGLGraphicsManager::createSurface(const Graphics::PixelFormat &format, bool wantAlpha) { + GLenum glIntFormat, glFormat, glType; + if (format.bytesPerPixel == 1) { +#if !USE_FORCED_GLES + if (TextureCLUT8GPU::isSupportedByContext()) { + 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 TextureCLUT8(glIntFormat, glFormat, glType, virtFormat); + } +#if !USE_FORCED_GL + } else if (isGLESContext() && 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 (isGLESContext() && format == Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0)) { // RGBA8888 +#else + } else if (isGLESContext() && format == Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24)) { // ABGR8888 +#endif + return new TextureRGBA8888Swap(); +#endif // !USE_FORCED_GL + } else { + const bool supported = getGLPixelFormat(format, glIntFormat, glFormat, glType); + if (!supported) { + return nullptr; + } else { + return new Texture(glIntFormat, glFormat, glType, format); + } + } +} + +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 (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; +} + +void OpenGLGraphicsManager::recalculateDisplayAreas() { + if (!_gameScreen) { + return; + } + + WindowedGraphicsManager::recalculateDisplayAreas(); + + // 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()); + + // 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); + } + + _cursor->setColorKey(_cursorKeyColor); +} + +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 frac_t screenScaleFactorX = intToFrac(_gameDrawRect.width()) / _gameScreen->getWidth(); + const frac_t screenScaleFactorY = intToFrac(_gameDrawRect.height()) / _gameScreen->getHeight(); + + _cursorHotspotXScaled = fracToInt(_cursorHotspotXScaled * screenScaleFactorX); + _cursorWidthScaled = fracToInt(_cursorWidthScaled * screenScaleFactorX); + + _cursorHotspotYScaled = fracToInt(_cursorHotspotYScaled * screenScaleFactorY); + _cursorHeightScaled = fracToInt(_cursorHeightScaled * screenScaleFactorY); + } else { + const frac_t screenScaleFactorX = intToFrac(90) / _xdpi; + const frac_t screenScaleFactorY = intToFrac(90) / _ydpi; + + // FIXME: Replace this with integer maths + _cursorHotspotXScaled /= fracToDouble(screenScaleFactorX); + _cursorWidthScaled /= fracToDouble(screenScaleFactorX); + + _cursorHotspotYScaled /= fracToDouble(screenScaleFactorY); + _cursorHeightScaled /= fracToDouble(screenScaleFactorY); + } +} + +#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; + + // A line of a BMP image must have a size divisible by 4. + // We calculate the padding bytes needed here. + // Since we use a 4 byte per pixel mode, we can use 0 here, since it is + // equal to (4 - (width * 4)) % 4. (4 - (width * Bpp)) % 4, is the usual + // way of computing the padding bytes required). + // GL_PACK_ALIGNMENT is 4, so this line padding is required for PNG too + const uint linePaddingSize = 0; + const uint lineSize = width * 4 + linePaddingSize; + + Common::DumpFile out; + if (!out.open(filename)) { + return false; + } + + Common::Array pixels; + pixels.resize(lineSize * height); + GL_CALL(glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, &pixels.front())); + +#ifdef SCUMM_LITTLE_ENDIAN + const Graphics::PixelFormat format(4, 8, 8, 8, 8, 0, 8, 16, 24); +#else + const Graphics::PixelFormat format(4, 8, 8, 8, 8, 24, 16, 8, 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 diff --git a/backends/graphics/opengl/opengl-graphics.h b/backends/graphics/opengl/opengl-graphics.h new file mode 100644 index 00000000000..0204db257ea --- /dev/null +++ b/backends/graphics/opengl/opengl-graphics.h @@ -0,0 +1,478 @@ +/* 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/opengl/framebuffer.h" +#include "backends/graphics/windowed.h" + +#include "common/frac.h" +#include "common/mutex.h" +#include "common/ustr.h" + +#include "graphics/surface.h" + +namespace Graphics { +class Font; +} // End of namespace Graphics + +namespace OpenGL { + +// HACK: We use glColor in the OSD code. This might not be working on GL ES but +// we still enable it because Tizen already shipped with it. Also, the +// SurfaceSDL backend enables it and disabling it can cause issues in sdl.cpp. +#define USE_OSD 1 + +class Surface; +class Pipeline; +#if !USE_FORCED_GLES +class Shader; +#endif + +enum { + GFX_OPENGL = 0 +}; + +class OpenGLGraphicsManager : virtual public WindowedGraphicsManager { +public: + OpenGLGraphicsManager(); + virtual ~OpenGLGraphicsManager(); + + // GraphicsManager API + virtual bool hasFeature(OSystem::Feature f) const override; + virtual void setFeatureState(OSystem::Feature f, bool enable) override; + virtual bool getFeatureState(OSystem::Feature f) const override; + + virtual const OSystem::GraphicsMode *getSupportedGraphicsModes() const override; + virtual int getDefaultGraphicsMode() const override; + virtual bool setGraphicsMode(int mode) override; + virtual int getGraphicsMode() const override; + +#ifdef USE_RGB_COLOR + virtual Graphics::PixelFormat getScreenFormat() const override; + virtual Common::List getSupportedFormats() const override; +#endif + + virtual const OSystem::GraphicsMode *getSupportedStretchModes() const override; + virtual int getDefaultStretchMode() const override; + virtual bool setStretchMode(int mode) override; + virtual int getStretchMode() const override; + + virtual void beginGFXTransaction() override; + virtual OSystem::TransactionError endGFXTransaction() override; + + virtual int getScreenChangeID() const override; + + virtual void initSize(uint width, uint height, const Graphics::PixelFormat *format) override; + + virtual int16 getWidth() const override; + virtual int16 getHeight() const override; + + virtual void copyRectToScreen(const void *buf, int pitch, int x, int y, int w, int h) override; + virtual void fillScreen(uint32 col) override; + + virtual void updateScreen() override; + + virtual Graphics::Surface *lockScreen() override; + virtual void unlockScreen() override; + + virtual void setFocusRectangle(const Common::Rect& rect) override; + virtual void clearFocusRectangle() override; + + virtual int16 getOverlayWidth() const override; + virtual int16 getOverlayHeight() const override; + + virtual Graphics::PixelFormat getOverlayFormat() const override; + + virtual void copyRectToOverlay(const void *buf, int pitch, int x, int y, int w, int h) override; + virtual void clearOverlay() override; + virtual void grabOverlay(void *buf, int pitch) const override; + + virtual void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format) override; + virtual void setCursorPalette(const byte *colors, uint start, uint num) override; + + virtual void displayMessageOnOSD(const Common::U32String &msg) override; + virtual void displayActivityIconOnOSD(const Graphics::Surface *icon) override; + + // PaletteManager interface + virtual void setPalette(const byte *colors, uint start, uint num) override; + virtual void grabPalette(byte *colors, uint start, uint num) const override; + +protected: + /** + * Whether an GLES or GLES2 context is active. + */ + bool isGLESContext() const { return g_context.type == kContextGLES || g_context.type == kContextGLES2; } + + /** + * Sets the OpenGL (ES) type the graphics manager shall work with. + * + * This needs to be called at least once (and before ever calling + * notifyContextCreate). + * + * @param type Type of the OpenGL (ES) contexts to be created. + */ + void setContextType(ContextType type); + + /** + * Notify the manager of a OpenGL context change. This should be the first + * thing to call after you created an OpenGL (ES) context! + * + * @param defaultFormat The new default format for the game screen + * (this is used for the CLUT8 game screens). + * @param defaultFormatAlpha The new default format with an alpha channel + * (this is used for the overlay and cursor). + */ + void notifyContextCreate(const Graphics::PixelFormat &defaultFormat, const Graphics::PixelFormat &defaultFormatAlpha); + + /** + * Notify the manager that the OpenGL context is about to be destroyed. + * This will free up/reset internal OpenGL related state and *must* be + * called whenever a context might be created again after destroying a + * context. + */ + void notifyContextDestroy(); + + /** + * Create a surface with the specified pixel format. + * + * @param format The pixel format the Surface object should accept as + * input. + * @param wantAlpha For CLUT8 surfaces this marks whether an alpha + * channel should be used. + * @return A pointer to the surface or nullptr on failure. + */ + Surface *createSurface(const Graphics::PixelFormat &format, bool wantAlpha = false); + + // + // Transaction support + // + struct VideoState { + VideoState() : valid(false), gameWidth(0), gameHeight(0), +#ifdef USE_RGB_COLOR + gameFormat(), +#endif + aspectRatioCorrection(false), graphicsMode(GFX_OPENGL), filtering(true) { + } + + bool valid; + + uint gameWidth, gameHeight; +#ifdef USE_RGB_COLOR + Graphics::PixelFormat gameFormat; +#endif + bool aspectRatioCorrection; + int graphicsMode; + bool filtering; + + 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 + && filtering == right.filtering; + } + + bool operator!=(const VideoState &right) { + return !(*this == right); + } + }; + + /** + * The currently set up 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; + + /** + * The current stretch mode. + */ + int _stretchMode; + +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; + + /** + * Refresh the screen contents. + */ + virtual void refreshScreen() = 0; + + /** + * Saves a screenshot of the entire window, excluding window decorations. + * + * @param filename The output filename. + * @return true on success, false otherwise + */ + bool saveScreenshot(const Common::String &filename) const; + +private: + // + // OpenGL utilities + // + + /** + * Initialize the active context for use. + */ + void initializeGLContext(); + + /** + * Render back buffer. + */ + Backbuffer _backBuffer; + + /** + * OpenGL pipeline used for rendering. + */ + Pipeline *_pipeline; + +protected: + /** + * Query the address of an OpenGL function by name. + * + * This can only be used after a context has been created. + * Please note that this function can return valid addresses even if the + * OpenGL context does not support the function. + * + * @param name The name of the OpenGL function. + * @return An function pointer for the requested OpenGL function or + * nullptr in case of failure. + */ + virtual void *getProcAddress(const char *name) const = 0; + + /** + * 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; + + virtual bool gameNeedsAspectRatioCorrection() const override; + virtual void recalculateDisplayAreas() override; + virtual void handleResizeImpl(const int width, const int height, const int xdpi, const int ydpi) override; + + /** + * The default pixel format of the backend. + */ + Graphics::PixelFormat _defaultFormat; + + /** + * The default pixel format with an alpha channel. + */ + Graphics::PixelFormat _defaultFormatAlpha; + + /** + * The rendering surface for the virtual game screen. + */ + Surface *_gameScreen; + + /** + * The game palette if in CLUT8 mode. + */ + byte _gamePalette[3 * 256]; + + // + // Overlay + // + + /** + * The rendering surface for the overlay. + */ + Surface *_overlay; + + // + // Cursor + // + + /** + * Set up the correct cursor palette. + */ + void updateCursorPalette(); + + /** + * The rendering surface for the mouse cursor. + */ + Surface *_cursor; + + /** + * The X offset for the cursor hotspot in unscaled game coordinates. + */ + int _cursorHotspotX; + + /** + * The Y offset for the cursor hotspot in unscaled game coordinates. + */ + int _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 game display area + * coordinates. + */ + int _cursorHotspotXScaled; + + /** + * The Y offset for the cursor hotspot in scaled game display area + * coordinates. + */ + int _cursorHotspotYScaled; + + /** + * The width of the cursor in scaled game display area coordinates. + */ + uint _cursorWidthScaled; + + /** + * The height of the cursor in scaled game display area coordinates. + */ + uint _cursorHeightScaled; + + /** + * The key color. + */ + uint32 _cursorKeyColor; + + /** + * 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]; + +#ifdef USE_OSD + // + // OSD + // +protected: + /** + * Returns the font used for on screen display + */ + virtual const Graphics::Font *getFontOSD() const; + +private: + /** + * Request for the OSD icon surface to be updated. + */ + bool _osdMessageChangeRequest; + + /** + * The next OSD message. + * + * If this value is not empty, the OSD message will be set + * to it on the next frame. + */ + Common::U32String _osdMessageNextData; + + /** + * Set the OSD message surface with the value of the next OSD message. + */ + void osdMessageUpdateSurface(); + + /** + * The OSD message's contents. + */ + Surface *_osdMessageSurface; + + /** + * Current opacity level of the OSD message. + */ + uint8 _osdMessageAlpha; + + /** + * When fading the OSD message has started. + */ + uint32 _osdMessageFadeStartTime; + + enum { + kOSDMessageFadeOutDelay = 2 * 1000, + kOSDMessageFadeOutDuration = 500, + kOSDMessageInitialAlpha = 80 + }; + + /** + * The OSD background activity icon's contents. + */ + Surface *_osdIconSurface; + + enum { + kOSDIconTopMargin = 10, + kOSDIconRightMargin = 10 + }; +#endif +}; + +} // 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 00000000000..5e754bb328a --- /dev/null +++ b/backends/graphics/opengl/opengl-sys.h @@ -0,0 +1,167 @@ +/* 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_SYS_H +#define BACKENDS_GRAPHICS_OPENGL_OPENGL_SYS_H + +#include "common/scummsys.h" + +#include "backends/graphics/opengl/debug.h" +#ifdef SDL_BACKEND +#include "backends/platform/sdl/sdl-sys.h" +#endif + +// On OS X we only support GL contexts. The reason is that Apple's GL interface +// uses "void *" for GLhandleARB which is not type compatible with GLint. This +// kills our aliasing trick for extension functions and thus would force us to +// supply two different Shader class implementations or introduce other +// wrappers. OS X only supports GL contexts right now anyway (at least +// according to SDL2 sources), thus it is not much of an issue. +#if defined(MACOSX) && (!defined(USE_GLES_MODE) || USE_GLES_MODE != 0) +//#warning "Only forced OpenGL mode is supported on Mac OS X. Overriding settings." +#undef USE_GLES_MODE +#define USE_GLES_MODE 0 +#endif + +// We allow to force GL or GLES modes on compile time. +// For this the USE_GLES_MODE define is used. The following values represent +// the given selection choices: +// 0 - Force OpenGL context +// 1 - Force OpenGL ES context +// 2 - Force OpenGL ES 2.0 context +#ifdef USE_GLES_MODE + #define USE_FORCED_GL (USE_GLES_MODE == 0) + #define USE_FORCED_GLES (USE_GLES_MODE == 1) + #define USE_FORCED_GLES2 (USE_GLES_MODE == 2) +#else + #define USE_FORCED_GL 0 + #define USE_FORCED_GLES 0 + #define USE_FORCED_GLES2 0 +#endif + +#ifdef __ANDROID__ + #include + #define USE_BUILTIN_OPENGL +#else + #include "backends/graphics/opengl/opengl-defs.h" +#endif + +#ifdef SDL_BACKEND + // Win32 needs OpenGL functions declared with APIENTRY. + // However, SDL does not define APIENTRY in it's SDL.h file on non-Windows + // targets, thus if it is not available, we just dummy define it. + #ifndef APIENTRY + #define APIENTRY + #endif + #define GL_CALL_CONV APIENTRY +#else + #define GL_CALL_CONV +#endif + +namespace OpenGL { + +enum ContextType { + kContextGL, + kContextGLES, + kContextGLES2 +}; + +class Pipeline; +class Framebuffer; + +/** + * Description structure of the OpenGL (ES) context. + */ +struct Context { + /** The type of the active context. */ + ContextType type; + + /** + * Reset context. + * + * This marks all extensions as unavailable and clears all function + * pointers. + */ + void reset(); + + /** The maximum texture size supported by the context. */ + GLint maxTextureSize; + + /** Whether GL_ARB_texture_non_power_of_two is available or not. */ + bool NPOTSupported; + + /** Whether shader support is available or not. */ + bool shadersSupported; + + /** Whether multi texture support is available or not. */ + bool multitextureSupported; + + /** Whether FBO support is available or not. */ + bool framebufferObjectSupported; + +#define GL_FUNC_DEF(ret, name, param) ret (GL_CALL_CONV *name)param +#include "backends/graphics/opengl/opengl-func.h" +#undef GL_FUNC_DEF + + // + // Wrapper functionality to handle fixed-function pipelines and + // programmable pipelines in the same fashion. + // + +private: + /** Currently active rendering pipeline. */ + Pipeline *activePipeline; + +public: + /** + * Set new pipeline. + * + * Client is responsible for any memory management related to pipelines. + * + * @param pipeline Pipeline to activate. + * @return Formerly active pipeline. + */ + Pipeline *setPipeline(Pipeline *pipeline); + + /** + * Query the currently active rendering pipeline. + */ + Pipeline *getActivePipeline() const { return activePipeline; } +}; + +/** + * The (active) OpenGL context. + */ +extern Context g_context; + +} // End of namespace OpenGL + +#define GL_CALL(x) GL_WRAP_DEBUG(g_context.x, x) +#define GL_CALL_SAFE(func, params) \ + do { \ + if (g_context.func) { \ + GL_CALL(func params); \ + } \ + } while (0) +#define GL_ASSIGN(var, x) GL_WRAP_DEBUG(var = g_context.x, x) + +#endif diff --git a/backends/graphics/opengl/pipelines/clut8.cpp b/backends/graphics/opengl/pipelines/clut8.cpp new file mode 100644 index 00000000000..fca40074f04 --- /dev/null +++ b/backends/graphics/opengl/pipelines/clut8.cpp @@ -0,0 +1,46 @@ +/* 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/pipelines/clut8.h" +#include "backends/graphics/opengl/shader.h" +#include "backends/graphics/opengl/framebuffer.h" + +namespace OpenGL { + +#if !USE_FORCED_GLES +CLUT8LookUpPipeline::CLUT8LookUpPipeline() + : ShaderPipeline(ShaderMan.query(ShaderManager::kCLUT8LookUp)), _paletteTexture(nullptr) { +} + +void CLUT8LookUpPipeline::drawTexture(const GLTexture &texture, const GLfloat *coordinates) { + // Set the palette texture. + GL_CALL(glActiveTexture(GL_TEXTURE1)); + if (_paletteTexture) { + _paletteTexture->bind(); + } + + GL_CALL(glActiveTexture(GL_TEXTURE0)); + ShaderPipeline::drawTexture(texture, coordinates); +} +#endif // !USE_FORCED_GLES + +} // End of namespace OpenGL diff --git a/backends/graphics/opengl/pipelines/clut8.h b/backends/graphics/opengl/pipelines/clut8.h new file mode 100644 index 00000000000..16724e46525 --- /dev/null +++ b/backends/graphics/opengl/pipelines/clut8.h @@ -0,0 +1,46 @@ +/* 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_PIPELINES_CLUT8_H +#define BACKENDS_GRAPHICS_OPENGL_PIPELINES_CLUT8_H + +#include "backends/graphics/opengl/pipelines/shader.h" + +namespace OpenGL { + +#if !USE_FORCED_GLES +class CLUT8LookUpPipeline : public ShaderPipeline { +public: + CLUT8LookUpPipeline(); + + void setPaletteTexture(const GLTexture *paletteTexture) { _paletteTexture = paletteTexture; } + + virtual void drawTexture(const GLTexture &texture, const GLfloat *coordinates); + +private: + const GLTexture *_paletteTexture; +}; +#endif // !USE_FORCED_GLES + +} // End of namespace OpenGL + +#endif diff --git a/backends/graphics/opengl/pipelines/fixed.cpp b/backends/graphics/opengl/pipelines/fixed.cpp new file mode 100644 index 00000000000..8e3bd7eaee1 --- /dev/null +++ b/backends/graphics/opengl/pipelines/fixed.cpp @@ -0,0 +1,70 @@ +/* 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/pipelines/fixed.h" + +namespace OpenGL { + +#if !USE_FORCED_GLES2 +void FixedPipeline::activateInternal() { + GL_CALL(glDisable(GL_LIGHTING)); + GL_CALL(glDisable(GL_FOG)); + GL_CALL(glShadeModel(GL_FLAT)); + GL_CALL(glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST)); + + GL_CALL(glEnableClientState(GL_VERTEX_ARRAY)); + GL_CALL(glEnableClientState(GL_TEXTURE_COORD_ARRAY)); + +#if !USE_FORCED_GLES + if (g_context.multitextureSupported) { + GL_CALL(glActiveTexture(GL_TEXTURE0)); + } +#endif + GL_CALL(glEnable(GL_TEXTURE_2D)); +} + +void FixedPipeline::setColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a) { + GL_CALL(glColor4f(r, g, b, a)); +} + +void FixedPipeline::drawTexture(const GLTexture &texture, const GLfloat *coordinates) { + texture.bind(); + + GL_CALL(glTexCoordPointer(2, GL_FLOAT, 0, texture.getTexCoords())); + GL_CALL(glVertexPointer(2, GL_FLOAT, 0, coordinates)); + GL_CALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); +} + +void FixedPipeline::setProjectionMatrix(const GLfloat *projectionMatrix) { + if (!isActive()) { + return; + } + + GL_CALL(glMatrixMode(GL_PROJECTION)); + GL_CALL(glLoadMatrixf(projectionMatrix)); + + GL_CALL(glMatrixMode(GL_MODELVIEW)); + GL_CALL(glLoadIdentity()); +} +#endif // !USE_FORCED_GLES2 + +} // End of namespace OpenGL diff --git a/backends/graphics/opengl/pipelines/fixed.h b/backends/graphics/opengl/pipelines/fixed.h new file mode 100644 index 00000000000..6bfe140c197 --- /dev/null +++ b/backends/graphics/opengl/pipelines/fixed.h @@ -0,0 +1,46 @@ +/* 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_PIPELINES_FIXED_H +#define BACKENDS_GRAPHICS_OPENGL_PIPELINES_FIXED_H + +#include "backends/graphics/opengl/pipelines/pipeline.h" + +namespace OpenGL { + +#if !USE_FORCED_GLES2 +class FixedPipeline : public Pipeline { +public: + virtual void setColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a); + + virtual void drawTexture(const GLTexture &texture, const GLfloat *coordinates); + + virtual void setProjectionMatrix(const GLfloat *projectionMatrix); + +protected: + virtual void activateInternal(); +}; +#endif // !USE_FORCED_GLES2 + +} // End of namespace OpenGL + +#endif diff --git a/backends/graphics/opengl/pipelines/pipeline.cpp b/backends/graphics/opengl/pipelines/pipeline.cpp new file mode 100644 index 00000000000..6a59cd28e78 --- /dev/null +++ b/backends/graphics/opengl/pipelines/pipeline.cpp @@ -0,0 +1,66 @@ +/* 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/pipelines/pipeline.h" +#include "backends/graphics/opengl/framebuffer.h" + +namespace OpenGL { + +Pipeline::Pipeline() + : _activeFramebuffer(nullptr), _isActive(false) { +} + +void Pipeline::activate() { + _isActive = true; + + if (_activeFramebuffer) { + _activeFramebuffer->activate(); + } + + activateInternal(); +} + +void Pipeline::deactivate() { + deactivateInternal(); + + if (_activeFramebuffer) { + _activeFramebuffer->deactivate(); + } + + _isActive = false; +} + +Framebuffer *Pipeline::setFramebuffer(Framebuffer *framebuffer) { + Framebuffer *oldFramebuffer = _activeFramebuffer; + if (_isActive && oldFramebuffer) { + oldFramebuffer->deactivate(); + } + + _activeFramebuffer = framebuffer; + if (_isActive && _activeFramebuffer) { + _activeFramebuffer->activate(); + } + + return oldFramebuffer; +} + +} // End of namespace OpenGL diff --git a/backends/graphics/opengl/pipelines/pipeline.h b/backends/graphics/opengl/pipelines/pipeline.h new file mode 100644 index 00000000000..9f32d33b95d --- /dev/null +++ b/backends/graphics/opengl/pipelines/pipeline.h @@ -0,0 +1,126 @@ +/* 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_PIPELINES_PIPELINE_H +#define BACKENDS_GRAPHICS_OPENGL_PIPELINES_PIPELINE_H + +#include "backends/graphics/opengl/opengl-sys.h" +#include "backends/graphics/opengl/texture.h" + +namespace OpenGL { + +class Framebuffer; + +/** + * Interface for OpenGL pipeline functionality. + * + * This encapsulates differences in various rendering pipelines used for + * OpenGL, OpenGL ES 1, and OpenGL ES 2. + */ +class Pipeline { +public: + Pipeline(); + virtual ~Pipeline() {} + + /** + * Activate the pipeline. + * + * This sets the OpenGL state to make use of drawing with the given + * OpenGL pipeline. + */ + void activate(); + + /** + * Deactivate the pipeline. + */ + void deactivate(); + + /** + * Set framebuffer to render to. + * + * Client is responsible for any memory management related to framebuffer. + * + * @param framebuffer Framebuffer to activate. + * @return Formerly active framebuffer. + */ + Framebuffer *setFramebuffer(Framebuffer *framebuffer); + + /** + * Set modulation color. + * + * @param r Red component in [0,1]. + * @param g Green component in [0,1]. + * @param b Blue component in [0,1]. + * @param a Alpha component in [0,1]. + */ + virtual void setColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a) = 0; + + /** + * Draw a texture rectangle to the currently active framebuffer. + * + * @param texture Texture to use for drawing. + * @param coordinates x1, y1, x2, y2 coordinates where to draw the texture. + */ + virtual void drawTexture(const GLTexture &texture, const GLfloat *coordinates) = 0; + + void drawTexture(const GLTexture &texture, GLfloat x, GLfloat y, GLfloat w, GLfloat h) { + const GLfloat coordinates[4*2] = { + x, y, + x + w, y, + x, y + h, + x + w, y + h + }; + drawTexture(texture, coordinates); + } + + /** + * Set the projection matrix. + * + * This is intended to be only ever be used by Framebuffer subclasses. + */ + virtual void setProjectionMatrix(const GLfloat *projectionMatrix) = 0; + +protected: + /** + * Activate the pipeline. + * + * This sets the OpenGL state to make use of drawing with the given + * OpenGL pipeline. + */ + virtual void activateInternal() = 0; + + /** + * Deactivate the pipeline. + */ + virtual void deactivateInternal() {} + + bool isActive() const { return _isActive; } + + Framebuffer *_activeFramebuffer; + +private: + bool _isActive; +}; + +} // End of namespace OpenGL + +#endif diff --git a/backends/graphics/opengl/pipelines/shader.cpp b/backends/graphics/opengl/pipelines/shader.cpp new file mode 100644 index 00000000000..a2dabb7c222 --- /dev/null +++ b/backends/graphics/opengl/pipelines/shader.cpp @@ -0,0 +1,94 @@ +/* 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/pipelines/shader.h" +#include "backends/graphics/opengl/shader.h" +#include "backends/graphics/opengl/framebuffer.h" + +namespace OpenGL { + +#if !USE_FORCED_GLES +ShaderPipeline::ShaderPipeline(Shader *shader) + : _activeShader(shader), _colorAttributes() { + _vertexAttribLocation = shader->getAttributeLocation("position"); + _texCoordAttribLocation = shader->getAttributeLocation("texCoordIn"); + _colorAttribLocation = shader->getAttributeLocation("blendColorIn"); + + assert(_vertexAttribLocation != -1); + assert(_texCoordAttribLocation != -1); + assert(_colorAttribLocation != -1); + + // One of the attributes needs to be passed through location 0, otherwise + // we get no output for GL contexts due to GL compatibility reasons. Let's + // check whether this ever happens. If this ever gets hit, we need to + // enable location 0 and pass some dummy values through it to fix output. + assert( _vertexAttribLocation == 0 + || _texCoordAttribLocation == 0 + || _colorAttribLocation == 0); +} + +void ShaderPipeline::activateInternal() { + GL_CALL(glEnableVertexAttribArray(_vertexAttribLocation)); + GL_CALL(glEnableVertexAttribArray(_texCoordAttribLocation)); + GL_CALL(glEnableVertexAttribArray(_colorAttribLocation)); + + if (g_context.multitextureSupported) { + GL_CALL(glActiveTexture(GL_TEXTURE0)); + } + + _activeShader->activate(); + + GL_CALL(glVertexAttribPointer(_colorAttribLocation, 4, GL_FLOAT, GL_FALSE, 0, _colorAttributes)); +} + +void ShaderPipeline::deactivateInternal() { + GL_CALL(glDisableVertexAttribArray(_vertexAttribLocation)); + GL_CALL(glDisableVertexAttribArray(_texCoordAttribLocation)); + GL_CALL(glDisableVertexAttribArray(_colorAttribLocation)); + + _activeShader->deactivate(); +} + +void ShaderPipeline::setColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a) { + GLfloat *dst = _colorAttributes; + for (uint i = 0; i < 4; ++i) { + *dst++ = r; + *dst++ = g; + *dst++ = b; + *dst++ = a; + } +} + +void ShaderPipeline::drawTexture(const GLTexture &texture, const GLfloat *coordinates) { + texture.bind(); + + GL_CALL(glVertexAttribPointer(_texCoordAttribLocation, 2, GL_FLOAT, GL_FALSE, 0, texture.getTexCoords())); + GL_CALL(glVertexAttribPointer(_vertexAttribLocation, 2, GL_FLOAT, GL_FALSE, 0, coordinates)); + GL_CALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); +} + +void ShaderPipeline::setProjectionMatrix(const GLfloat *projectionMatrix) { + _activeShader->setUniform("projection", new ShaderUniformMatrix44(projectionMatrix)); +} +#endif // !USE_FORCED_GLES + +} // End of namespace OpenGL diff --git a/backends/graphics/opengl/pipelines/shader.h b/backends/graphics/opengl/pipelines/shader.h new file mode 100644 index 00000000000..6159607099f --- /dev/null +++ b/backends/graphics/opengl/pipelines/shader.h @@ -0,0 +1,59 @@ +/* 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_PIPELINES_SHADER_H +#define BACKENDS_GRAPHICS_OPENGL_PIPELINES_SHADER_H + +#include "backends/graphics/opengl/pipelines/pipeline.h" + +namespace OpenGL { + +#if !USE_FORCED_GLES +class Shader; + +class ShaderPipeline : public Pipeline { +public: + ShaderPipeline(Shader *shader); + + virtual void setColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a); + + virtual void drawTexture(const GLTexture &texture, const GLfloat *coordinates); + + virtual void setProjectionMatrix(const GLfloat *projectionMatrix); + +protected: + virtual void activateInternal(); + virtual void deactivateInternal(); + + GLint _vertexAttribLocation; + GLint _texCoordAttribLocation; + GLint _colorAttribLocation; + + GLfloat _colorAttributes[4*4]; + + Shader *const _activeShader; +}; +#endif // !USE_FORCED_GLES + +} // End of namespace OpenGL + +#endif diff --git a/backends/graphics/opengl/shader.cpp b/backends/graphics/opengl/shader.cpp new file mode 100644 index 00000000000..80b746b7b3f --- /dev/null +++ b/backends/graphics/opengl/shader.cpp @@ -0,0 +1,337 @@ +/* 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/shader.h" + +#if !USE_FORCED_GLES + +#include "common/textconsole.h" +#include "common/util.h" + +namespace Common { +DECLARE_SINGLETON(OpenGL::ShaderManager); +} + +namespace OpenGL { + +namespace { + +#pragma mark - Builtin Shader Sources - + +const char *const g_defaultVertexShader = + "attribute vec4 position;\n" + "attribute vec2 texCoordIn;\n" + "attribute vec4 blendColorIn;\n" + "\n" + "uniform mat4 projection;\n" + "\n" + "varying vec2 texCoord;\n" + "varying vec4 blendColor;\n" + "\n" + "void main(void) {\n" + "\ttexCoord = texCoordIn;\n" + "\tblendColor = blendColorIn;\n" + "\tgl_Position = projection * position;\n" + "}\n"; + +const char *const g_defaultFragmentShader = + "varying vec2 texCoord;\n" + "varying vec4 blendColor;\n" + "\n" + "uniform sampler2D shaderTexture;\n" + "\n" + "void main(void) {\n" + "\tgl_FragColor = blendColor * texture2D(shaderTexture, texCoord);\n" + "}\n"; + +const char *const g_lookUpFragmentShader = + "varying vec2 texCoord;\n" + "varying vec4 blendColor;\n" + "\n" + "uniform sampler2D shaderTexture;\n" + "uniform sampler2D palette;\n" + "\n" + "const float adjustFactor = 255.0 / 256.0 + 1.0 / (2.0 * 256.0);" + "\n" + "void main(void) {\n" + "\tvec4 index = texture2D(shaderTexture, texCoord);\n" + "\tgl_FragColor = blendColor * texture2D(palette, vec2(index.a * adjustFactor, 0.0));\n" + "}\n"; + + +// Taken from: https://en.wikibooks.org/wiki/OpenGL_Programming/Modern_OpenGL_Tutorial_03#OpenGL_ES_2_portability +const char *const g_precisionDefines = + "#ifdef GL_ES\n" + "\t#if defined(GL_FRAGMENT_PRECISION_HIGH) && GL_FRAGMENT_PRECISION_HIGH == 1\n" + "\t\tprecision highp float;\n" + "\t#else\n" + "\t\tprecision mediump float;\n" + "\t#endif\n" + "#else\n" + "\t#define highp\n" + "\t#define mediump\n" + "\t#define lowp\n" + "#endif\n"; + +} // End of anonymous namespace + +#pragma mark - Uniform Values - + +void ShaderUniformInteger::set(GLint location) const { + GL_CALL(glUniform1i(location, _value)); +} + +void ShaderUniformFloat::set(GLint location) const { + GL_CALL(glUniform1f(location, _value)); +} + +void ShaderUniformMatrix44::set(GLint location) const { + GL_CALL(glUniformMatrix4fv(location, 1, GL_FALSE, _matrix)); +} + +#pragma mark - Shader Implementation - + +Shader::Shader(const Common::String &vertex, const Common::String &fragment) + : _vertex(vertex), _fragment(fragment), _isActive(false), _program(0), _uniforms() { + recreate(); +} + +Shader::~Shader() { + // According to extension specification glDeleteObjectARB silently ignores + // 0. However, with nVidia drivers this can cause GL_INVALID_VALUE, thus + // we do not call it with 0 as parameter to avoid warnings. + if (_program) { + GL_CALL_SAFE(glDeleteProgram, (_program)); + } +} + +void Shader::destroy() { + // According to extension specification glDeleteObjectARB silently ignores + // 0. However, with nVidia drivers this can cause GL_INVALID_VALUE, thus + // we do not call it with 0 as parameter to avoid warnings. + if (_program) { + GL_CALL(glDeleteProgram(_program)); + _program = 0; + } +} + +bool Shader::recreate() { + // Make sure any old programs are destroyed properly. + destroy(); + + GLshader vertexShader = compileShader(_vertex.c_str(), GL_VERTEX_SHADER); + if (!vertexShader) { + return false; + } + + GLshader fragmentShader = compileShader(_fragment.c_str(), GL_FRAGMENT_SHADER); + if (!fragmentShader) { + GL_CALL(glDeleteShader(vertexShader)); + return false; + } + + GL_ASSIGN(_program, glCreateProgram()); + if (!_program) { + GL_CALL(glDeleteShader(vertexShader)); + GL_CALL(glDeleteShader(fragmentShader)); + return false; + } + + GL_CALL(glAttachShader(_program, vertexShader)); + GL_CALL(glAttachShader(_program, fragmentShader)); + + GL_CALL(glLinkProgram(_program)); + + GL_CALL(glDetachShader(_program, fragmentShader)); + GL_CALL(glDeleteShader(fragmentShader)); + + GL_CALL(glDetachShader(_program, vertexShader)); + GL_CALL(glDeleteShader(vertexShader)); + + GLint result; + GL_CALL(glGetProgramiv(_program, GL_LINK_STATUS, &result)); + if (result == GL_FALSE) { + GLint logSize; + GL_CALL(glGetProgramiv(_program, GL_INFO_LOG_LENGTH, &logSize)); + + GLchar *log = new GLchar[logSize]; + GL_CALL(glGetProgramInfoLog(_program, logSize, nullptr, log)); + warning("Could not link shader: \"%s\"", log); + delete[] log; + + destroy(); + return false; + } + + // Set program object in case shader is active during recreation. + if (_isActive) { + GL_CALL(glUseProgram(_program)); + } + + for (UniformMap::iterator i = _uniforms.begin(), end = _uniforms.end(); i != end; ++i) { + i->_value.location = getUniformLocation(i->_key.c_str()); + i->_value.altered = true; + if (_isActive) { + i->_value.set(); + } + } + + return true; +} + +void Shader::activate() { + // Activate program. + GL_CALL(glUseProgram(_program)); + + // Reset changed uniform values. + for (UniformMap::iterator i = _uniforms.begin(), end = _uniforms.end(); i != end; ++i) { + i->_value.set(); + } + + _isActive = true; +} + +void Shader::deactivate() { + _isActive = false; +} + +GLint Shader::getAttributeLocation(const char *name) const { + GLint result = -1; + GL_ASSIGN(result, glGetAttribLocation(_program, name)); + return result; +} + +GLint Shader::getUniformLocation(const char *name) const { + GLint result = -1; + GL_ASSIGN(result, glGetUniformLocation(_program, name)); + return result; +} + +bool Shader::setUniform(const Common::String &name, ShaderUniformValue *value) { + UniformMap::iterator uniformIter = _uniforms.find(name); + Uniform *uniform; + + if (uniformIter == _uniforms.end()) { + const GLint location = getUniformLocation(name.c_str()); + if (location == -1) { + delete value; + return false; + } + + uniform = &_uniforms[name]; + uniform->location = location; + } else { + uniform = &uniformIter->_value; + } + + uniform->value = Common::SharedPtr(value); + uniform->altered = true; + if (_isActive) { + uniform->set(); + } + + return true; +} + +GLshader Shader::compileShader(const char *source, GLenum shaderType) { + const GLchar *versionSource = g_context.type == kContextGLES2 ? "#version 100\n" : "#version 120\n"; + GLshader handle; + GL_ASSIGN(handle, glCreateShader(shaderType)); + if (!handle) { + return 0; + } + + const char *const shaderSources[] = { + versionSource, + g_precisionDefines, + source + }; + + GL_CALL(glShaderSource(handle, ARRAYSIZE(shaderSources), shaderSources, nullptr)); + GL_CALL(glCompileShader(handle)); + + GLint result; + GL_CALL(glGetShaderiv(handle, GL_COMPILE_STATUS, &result)); + if (result == GL_FALSE) { + GLint logSize; + GL_CALL(glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &logSize)); + + GLchar *log = new GLchar[logSize]; + GL_CALL(glGetShaderInfoLog(handle, logSize, nullptr, log)); + warning("Could not compile shader \"%s\": \"%s\"", source, log); + delete[] log; + + GL_CALL(glDeleteShader(handle)); + return 0; + } + + return handle; +} + +ShaderManager::ShaderManager() : _initializeShaders(true) { + for (int i = 0; i < ARRAYSIZE(_builtIn); ++i) { + _builtIn[i] = nullptr; + } +} + +ShaderManager::~ShaderManager() { + for (int i = 0; i < ARRAYSIZE(_builtIn); ++i) { + delete _builtIn[i]; + } +} + +void ShaderManager::notifyDestroy() { + for (int i = 0; i < ARRAYSIZE(_builtIn); ++i) { + _builtIn[i]->destroy(); + } +} + +void ShaderManager::notifyCreate() { + if (_initializeShaders) { + _initializeShaders = false; + + _builtIn[kDefault] = new Shader(g_defaultVertexShader, g_defaultFragmentShader); + _builtIn[kCLUT8LookUp] = new Shader(g_defaultVertexShader, g_lookUpFragmentShader); + _builtIn[kCLUT8LookUp]->setUniform1I("palette", 1); + + for (uint i = 0; i < kMaxUsages; ++i) { + _builtIn[i]->setUniform1I("shaderTexture", 0); + } + } else { + for (int i = 0; i < ARRAYSIZE(_builtIn); ++i) { + _builtIn[i]->recreate(); + } + } +} + +Shader *ShaderManager::query(ShaderUsage shader) const { + if (shader == kMaxUsages) { + warning("OpenGL: ShaderManager::query used with kMaxUsages"); + return nullptr; + } + + return _builtIn[shader]; +} + +} // End of namespace OpenGL + +#endif // !USE_FORCED_GLES diff --git a/backends/graphics/opengl/shader.h b/backends/graphics/opengl/shader.h new file mode 100644 index 00000000000..ec1e516d149 --- /dev/null +++ b/backends/graphics/opengl/shader.h @@ -0,0 +1,288 @@ +/* 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_SHADER_H +#define BACKENDS_GRAPHICS_OPENGL_SHADER_H + +#include "backends/graphics/opengl/opengl-sys.h" + +#if !USE_FORCED_GLES + +#include "common/singleton.h" +#include "common/hash-str.h" +#include "common/ptr.h" + +namespace OpenGL { + +/** + * A generic uniform value interface for a shader program. + */ +class ShaderUniformValue { +public: + virtual ~ShaderUniformValue() {} + + /** + * Setup the the value to the given location. + * + * @param location Location of the uniform. + */ + virtual void set(GLint location) const = 0; +}; + +/** + * Integer value for a shader uniform. + */ +class ShaderUniformInteger : public ShaderUniformValue { +public: + ShaderUniformInteger(GLint value) : _value(value) {} + + virtual void set(GLint location) const override; + +private: + const GLint _value; +}; + +/** + * Float value for a shader uniform. + */ +class ShaderUniformFloat : public ShaderUniformValue { +public: + ShaderUniformFloat(GLfloat value) : _value(value) {} + + virtual void set(GLint location) const override; + +private: + const GLfloat _value; +}; + +/** + * 4x4 Matrix value for a shader uniform. + */ +class ShaderUniformMatrix44 : public ShaderUniformValue { +public: + ShaderUniformMatrix44(const GLfloat *mat44) { + memcpy(_matrix, mat44, sizeof(_matrix)); + } + + virtual void set(GLint location) const override; + +private: + GLfloat _matrix[4*4]; +}; + +class Shader { +public: + Shader(const Common::String &vertex, const Common::String &fragment); + ~Shader(); + + /** + * Destroy the shader program. + * + * This keeps the vertex and fragment shader sources around and thus + * allows for recreating the shader on context recreation. It also keeps + * the uniform state around. + */ + void destroy(); + + /** + * Recreate shader program. + * + * @return true on success, false on failure. + */ + bool recreate(); + + /** + * Make shader active. + */ + void activate(); + + /** + * Make shader inactive. + */ + void deactivate(); + + /** + * Return location for attribute with given name. + * + * @param name Name of the attribute to look up in the shader. + * @return The loctaion of -1 if attribute was not found. + */ + GLint getAttributeLocation(const char *name) const; + GLint getAttributeLocation(const Common::String &name) const { + return getAttributeLocation(name.c_str()); + } + + /** + * Return location for uniform with given name. + * + * @param name Name of the uniform to look up in the shader. + * @return The location or -1 if uniform was not found. + */ + GLint getUniformLocation(const char *name) const; + GLint getUniformLocation(const Common::String &name) const { + return getUniformLocation(name.c_str()); + } + + /** + * Bind value to uniform. + * + * @param name The name of the uniform to be set. + * @param value The value to be set. + * @return 'false' on error (i.e. uniform unknown or otherwise), + * 'true' otherwise. + */ + bool setUniform(const Common::String &name, ShaderUniformValue *value); + + /** + * Bind integer value to uniform. + * + * @param name The name of the uniform to be set. + * @param value The value to be set. + * @return 'false' on error (i.e. uniform unknown or otherwise), + * 'true' otherwise. + */ + bool setUniform1I(const Common::String &name, GLint value) { + return setUniform(name, new ShaderUniformInteger(value)); + } +protected: + /** + * Vertex shader sources. + */ + const Common::String _vertex; + + /** + * Fragment shader sources. + */ + const Common::String _fragment; + + /** + * Whether the shader is active or not. + */ + bool _isActive; + + /** + * Shader program handle. + */ + GLprogram _program; + + /** + * A uniform descriptor. + * + * This stores the state of a shader uniform. The state is made up of the + * uniform location, whether the state was altered since last set, and the + * value of the uniform. + */ + struct Uniform { + Uniform() : location(-1), altered(false), value() {} + Uniform(GLint loc, ShaderUniformValue *val) + : location(loc), altered(true), value(val) {} + + /** + * Write uniform value into currently active shader. + */ + void set() { + if (altered && value) { + value->set(location); + altered = false; + } + } + + /** + * The location of the uniform or -1 in case it does not exist. + */ + GLint location; + + /** + * Whether the uniform state was aletered since last 'set'. + */ + bool altered; + + /** + * The value of the uniform. + */ + Common::SharedPtr value; + }; + + typedef Common::HashMap UniformMap; + + /** + * Map from uniform name to associated uniform description. + */ + UniformMap _uniforms; + + /** + * Compile a vertex or fragment shader. + * + * @param source Sources to the shader. + * @param shaderType Type of shader to compile (GL_FRAGMENT_SHADER_ARB or + * GL_VERTEX_SHADER_ARB) + * @return The shader object or 0 on failure. + */ + static GLshader compileShader(const char *source, GLenum shaderType); +}; + +class ShaderManager : public Common::Singleton { +public: + enum ShaderUsage { + /** Default shader implementing the GL fixed-function pipeline. */ + kDefault = 0, + + /** CLUT8 look up shader. */ + kCLUT8LookUp, + + /** Number of built-in shaders. Should not be used for query. */ + kMaxUsages + }; + + /** + * Notify shader manager about context destruction. + */ + void notifyDestroy(); + + /** + * Notify shader manager about context creation. + */ + void notifyCreate(); + + /** + * Query a built-in shader. + */ + Shader *query(ShaderUsage shader) const; + +private: + friend class Common::Singleton; + ShaderManager(); + ~ShaderManager(); + + bool _initializeShaders; + + Shader *_builtIn[kMaxUsages]; +}; + +} // End of namespace OpenGL + +/** Shortcut for accessing the font manager. */ +#define ShaderMan (OpenGL::ShaderManager::instance()) + +#endif // !USE_FORCED_GLES + +#endif diff --git a/backends/graphics/opengl/texture.cpp b/backends/graphics/opengl/texture.cpp new file mode 100644 index 00000000000..2b6f9ce0a1b --- /dev/null +++ b/backends/graphics/opengl/texture.cpp @@ -0,0 +1,698 @@ +/* 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/shader.h" +#include "backends/graphics/opengl/pipelines/pipeline.h" +#include "backends/graphics/opengl/pipelines/clut8.h" +#include "backends/graphics/opengl/framebuffer.h" + +#include "common/algorithm.h" +#include "common/endian.h" +#include "common/rect.h" +#include "common/textconsole.h" + +namespace OpenGL { + +GLTexture::GLTexture(GLenum glIntFormat, GLenum glFormat, GLenum glType) + : _glIntFormat(glIntFormat), _glFormat(glFormat), _glType(glType), + _width(0), _height(0), _logicalWidth(0), _logicalHeight(0), + _texCoords(), _glFilter(GL_NEAREST), + _glTexture(0) { + create(); +} + +GLTexture::~GLTexture() { + GL_CALL_SAFE(glDeleteTextures, (1, &_glTexture)); +} + +void GLTexture::enableLinearFiltering(bool enable) { + if (enable) { + _glFilter = GL_LINEAR; + } else { + _glFilter = GL_NEAREST; + } + + bind(); + + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _glFilter)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _glFilter)); +} + +void GLTexture::destroy() { + GL_CALL(glDeleteTextures(1, &_glTexture)); + _glTexture = 0; +} + +void GLTexture::create() { + // Release old texture name in case it exists. + destroy(); + + // Get a new texture name. + GL_CALL(glGenTextures(1, &_glTexture)); + + // Set up all texture parameters. + bind(); + GL_CALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _glFilter)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _glFilter)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); + + // If a size is specified, allocate memory for it. + if (_width != 0 && _height != 0) { + // Allocate storage for OpenGL texture. + GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, _glIntFormat, _width, _height, + 0, _glFormat, _glType, NULL)); + } +} + +void GLTexture::bind() const { + GL_CALL(glBindTexture(GL_TEXTURE_2D, _glTexture)); +} + +void GLTexture::setSize(uint width, uint height) { + const uint oldWidth = _width; + const uint oldHeight = _height; + + if (!g_context.NPOTSupported) { + _width = Common::nextHigher2(width); + _height = Common::nextHigher2(height); + } else { + _width = width; + _height = height; + } + + _logicalWidth = width; + _logicalHeight = height; + + // If a size is specified, allocate memory for it. + if (width != 0 && height != 0) { + const GLfloat texWidth = (GLfloat)width / _width; + const GLfloat texHeight = (GLfloat)height / _height; + + _texCoords[0] = 0; + _texCoords[1] = 0; + + _texCoords[2] = texWidth; + _texCoords[3] = 0; + + _texCoords[4] = 0; + _texCoords[5] = texHeight; + + _texCoords[6] = texWidth; + _texCoords[7] = texHeight; + + // Allocate storage for OpenGL texture if necessary. + if (oldWidth != _width || oldHeight != _height) { + bind(); + GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, _glIntFormat, _width, + _height, 0, _glFormat, _glType, NULL)); + } + } +} + +void GLTexture::updateArea(const Common::Rect &area, const Graphics::Surface &src) { + // Set the texture on the active texture unit. + bind(); + + // Update the actual texture. + // Although we have the area of the texture buffer we want to update we + // cannot take advantage of the left/right boundries here because it is + // not possible to specify a pitch to glTexSubImage2D. To be precise, with + // plain OpenGL we could set GL_UNPACK_ROW_LENGTH to achieve this. However, + // OpenGL ES 1.0 does not support GL_UNPACK_ROW_LENGTH. Thus, we are left + // with the following options: + // + // 1) (As we do right now) Simply always update the whole texture lines of + // rect changed. This is simplest to implement. In case performance is + // really an issue we can think of switching to another method. + // + // 2) Copy the dirty rect to a temporary buffer and upload that by using + // glTexSubImage2D. This is what the Android backend does. It is more + // complicated though. + // + // 3) Use glTexSubImage2D per line changed. This is what the old OpenGL + // graphics manager did but it is much slower! Thus, we do not use it. + GL_CALL(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, area.top, src.w, area.height(), + _glFormat, _glType, src.getBasePtr(0, area.top))); +} + +// +// Surface +// + +Surface::Surface() + : _allDirty(false), _dirtyArea() { +} + +void Surface::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); + + // *sigh* Common::Rect::extend behaves unexpected whenever one of the two + // parameters is an empty rect. Thus, we check whether the current dirty + // area is valid. In case it is not we simply use the parameters as new + // dirty area. Otherwise, we simply call extend. + if (_dirtyArea.isEmpty()) { + _dirtyArea = Common::Rect(x, y, x + w, y + h); + } else { + _dirtyArea.extend(Common::Rect(x, y, x + w, y + 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; + } + } +} + +void Surface::fill(uint32 color) { + Graphics::Surface *dst = getSurface(); + dst->fillRect(Common::Rect(dst->w, dst->h), color); + + flagDirty(); +} + +Common::Rect Surface::getDirtyArea() const { + if (_allDirty) { + return Common::Rect(getWidth(), getHeight()); + } else { + return _dirtyArea; + } +} + +// +// Surface implementations +// + +Texture::Texture(GLenum glIntFormat, GLenum glFormat, GLenum glType, const Graphics::PixelFormat &format) + : Surface(), _format(format), _glTexture(glIntFormat, glFormat, glType), + _textureData(), _userPixelData() { +} + +Texture::~Texture() { + _textureData.free(); +} + +void Texture::destroy() { + _glTexture.destroy(); +} + +void Texture::recreate() { + _glTexture.create(); + + // In case image date exists assure it will be completely refreshed next + // time. + if (_textureData.getPixels()) { + flagDirty(); + } +} + +void Texture::enableLinearFiltering(bool enable) { + _glTexture.enableLinearFiltering(enable); +} + +void Texture::allocate(uint width, uint height) { + // Assure the texture can contain our user data. + _glTexture.setSize(width, height); + + // In case the needed texture dimension changed we will reinitialize the + // texture data buffer. + if (_glTexture.getWidth() != _textureData.w || _glTexture.getHeight() != _textureData.h) { + // Create a buffer for the texture data. + _textureData.create(_glTexture.getWidth(), _glTexture.getHeight(), _format); + } + + // Create a sub-buffer for raw access. + _userPixelData = _textureData.getSubArea(Common::Rect(width, height)); + + // The whole texture is dirty after we changed the size. This fixes + // multiple texture size changes without any actual update in between. + // Without this we might try to write a too big texture into the GL + // texture. + flagDirty(); +} + +void Texture::updateGLTexture() { + if (!isDirty()) { + return; + } + + Common::Rect dirtyArea = getDirtyArea(); + + // In case we use linear filtering we might need to duplicate the last + // pixel row/column to avoid glitches with filtering. + if (_glTexture.isLinearFilteringEnabled()) { + if (dirtyArea.right == _userPixelData.w && _userPixelData.w != _textureData.w) { + uint height = dirtyArea.height(); + + const byte *src = (const byte *)_textureData.getBasePtr(_userPixelData.w - 1, dirtyArea.top); + byte *dst = (byte *)_textureData.getBasePtr(_userPixelData.w, dirtyArea.top); + + while (height-- > 0) { + memcpy(dst, src, _textureData.format.bytesPerPixel); + dst += _textureData.pitch; + src += _textureData.pitch; + } + + // Extend the dirty area. + ++dirtyArea.right; + } + + if (dirtyArea.bottom == _userPixelData.h && _userPixelData.h != _textureData.h) { + const byte *src = (const byte *)_textureData.getBasePtr(dirtyArea.left, _userPixelData.h - 1); + byte *dst = (byte *)_textureData.getBasePtr(dirtyArea.left, _userPixelData.h); + memcpy(dst, src, dirtyArea.width() * _textureData.format.bytesPerPixel); + + // Extend the dirty area. + ++dirtyArea.bottom; + } + } + + _glTexture.updateArea(dirtyArea, _textureData); + + // 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 && height == _clut8Data.h) { + return; + } + + _clut8Data.create(width, height, Graphics::PixelFormat::createFormatCLUT8()); +} + +Graphics::PixelFormat TextureCLUT8::getFormat() const { + return Graphics::PixelFormat::createFormatCLUT8(); +} + +void TextureCLUT8::setColorKey(uint colorKey) { + // The key color is set to black so the color value is pre-multiplied with the alpha value + // to avoid color fringes due to filtering. + // Erasing the color data is not a problem as the palette is always fully re-initialized + // before setting the key color. + if (_format.bytesPerPixel == 2) { + uint16 *palette = (uint16 *)_palette + colorKey; + *palette = 0; + } else if (_format.bytesPerPixel == 4) { + uint32 *palette = (uint32 *)_palette + colorKey; + *palette = 0; + } else { + warning("TextureCLUT8::setColorKey: Unsupported pixel depth %d", _format.bytesPerPixel); + } + + // A palette changes means we need to refresh the whole surface. + flagDirty(); +} + +namespace { +template +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) { + if (_format.bytesPerPixel == 2) { + convertPalette((uint16 *)_palette + start, palData, colors, _format); + } else if (_format.bytesPerPixel == 4) { + convertPalette((uint32 *)_palette + start, palData, colors, _format); + } else { + warning("TextureCLUT8::setPalette: Unsupported pixel depth: %d", _format.bytesPerPixel); + } + + // A palette changes means we need to refresh the whole surface. + flagDirty(); +} + +namespace { +template +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::updateGLTexture() { + if (!isDirty()) { + return; + } + + // Do the palette look up + Graphics::Surface *outSurf = Texture::getSurface(); + + Common::Rect dirtyArea = getDirtyArea(); + + if (outSurf->format.bytesPerPixel == 2) { + doPaletteLookUp((uint16 *)outSurf->getBasePtr(dirtyArea.left, dirtyArea.top), + (const byte *)_clut8Data.getBasePtr(dirtyArea.left, dirtyArea.top), + dirtyArea.width(), dirtyArea.height(), + outSurf->pitch, _clut8Data.pitch, (const uint16 *)_palette); + } else if (outSurf->format.bytesPerPixel == 4) { + doPaletteLookUp((uint32 *)outSurf->getBasePtr(dirtyArea.left, dirtyArea.top), + (const byte *)_clut8Data.getBasePtr(dirtyArea.left, dirtyArea.top), + dirtyArea.width(), dirtyArea.height(), + outSurf->pitch, _clut8Data.pitch, (const uint32 *)_palette); + } else { + warning("TextureCLUT8::updateGLTexture: Unsupported pixel depth: %d", outSurf->format.bytesPerPixel); + } + + // Do generic handling of updating the texture. + Texture::updateGLTexture(); +} + +#if !USE_FORCED_GL +FakeTexture::FakeTexture(GLenum glIntFormat, GLenum glFormat, GLenum glType, const Graphics::PixelFormat &format) + : Texture(glIntFormat, glFormat, glType, format), + _rgbData() { +} + +FakeTexture::~FakeTexture() { + _rgbData.free(); +} + +void FakeTexture::allocate(uint width, uint height) { + Texture::allocate(width, height); + + // We only need to reinitialize our surface when the output size + // changed. + if (width == _rgbData.w && height == _rgbData.h) { + return; + } + + warning("%s pixel format not supported by OpenGL ES, using %s instead", getFormat().toString().c_str(), _format.toString().c_str()); + _rgbData.create(width, height, getFormat()); +} + +TextureRGB555::TextureRGB555() + : FakeTexture(GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0)) { +} + +Graphics::PixelFormat TextureRGB555::getFormat() const { + return Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0); +} + +void TextureRGB555::updateGLTexture() { + if (!isDirty()) { + return; + } + + // Convert color space. + Graphics::Surface *outSurf = Texture::getSurface(); + + const Common::Rect dirtyArea = getDirtyArea(); + + uint16 *dst = (uint16 *)outSurf->getBasePtr(dirtyArea.left, dirtyArea.top); + const uint dstAdd = outSurf->pitch - 2 * dirtyArea.width(); + + const uint16 *src = (const uint16 *)_rgbData.getBasePtr(dirtyArea.left, dirtyArea.top); + const uint srcAdd = _rgbData.pitch - 2 * dirtyArea.width(); + + for (int height = dirtyArea.height(); height > 0; --height) { + for (int width = dirtyArea.width(); width > 0; --width) { + const uint16 color = *src++; + + *dst++ = ((color & 0x7C00) << 1) // R + | (((color & 0x03E0) << 1) | ((color & 0x0200) >> 4)) // G + | (color & 0x001F); // B + } + + src = (const uint16 *)((const byte *)src + srcAdd); + dst = (uint16 *)((byte *)dst + dstAdd); + } + + // Do generic handling of updating the texture. + Texture::updateGLTexture(); +} + +TextureRGBA8888Swap::TextureRGBA8888Swap() +#ifdef SCUMM_LITTLE_ENDIAN + : FakeTexture(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24)) // ABGR8888 +#else + : FakeTexture(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0)) // RGBA8888 +#endif + { +} + +Graphics::PixelFormat TextureRGBA8888Swap::getFormat() const { +#ifdef SCUMM_LITTLE_ENDIAN + return Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0); // RGBA8888 +#else + return Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24); // ABGR8888 +#endif +} + +void TextureRGBA8888Swap::updateGLTexture() { + if (!isDirty()) { + return; + } + + // Convert color space. + Graphics::Surface *outSurf = Texture::getSurface(); + + const Common::Rect dirtyArea = getDirtyArea(); + + uint32 *dst = (uint32 *)outSurf->getBasePtr(dirtyArea.left, dirtyArea.top); + const uint dstAdd = outSurf->pitch - 4 * dirtyArea.width(); + + const uint32 *src = (const uint32 *)_rgbData.getBasePtr(dirtyArea.left, dirtyArea.top); + const uint srcAdd = _rgbData.pitch - 4 * dirtyArea.width(); + + for (int height = dirtyArea.height(); height > 0; --height) { + for (int width = dirtyArea.width(); width > 0; --width) { + const uint32 color = *src++; + + *dst++ = SWAP_BYTES_32(color); + } + + src = (const uint32 *)((const byte *)src + srcAdd); + dst = (uint32 *)((byte *)dst + dstAdd); + } + + // Do generic handling of updating the texture. + Texture::updateGLTexture(); +} +#endif // !USE_FORCED_GL + +#if !USE_FORCED_GLES + +// _clut8Texture needs 8 bits internal precision, otherwise graphics glitches +// can occur. GL_ALPHA does not have any internal precision requirements. +// However, in practice (according to fuzzie) it's 8bit. If we run into +// problems, we need to switch to GL_R8 and GL_RED, but that is only supported +// for ARB_texture_rg and GLES3+ (EXT_rexture_rg does not support GL_R8). +TextureCLUT8GPU::TextureCLUT8GPU() + : _clut8Texture(GL_ALPHA, GL_ALPHA, GL_UNSIGNED_BYTE), + _paletteTexture(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE), + _target(new TextureTarget()), _clut8Pipeline(new CLUT8LookUpPipeline()), + _clut8Vertices(), _clut8Data(), _userPixelData(), _palette(), + _paletteDirty(false) { + // Allocate space for 256 colors. + _paletteTexture.setSize(256, 1); + + // Setup pipeline. + _clut8Pipeline->setFramebuffer(_target); + _clut8Pipeline->setPaletteTexture(&_paletteTexture); + _clut8Pipeline->setColor(1.0f, 1.0f, 1.0f, 1.0f); +} + +TextureCLUT8GPU::~TextureCLUT8GPU() { + delete _clut8Pipeline; + delete _target; + _clut8Data.free(); +} + +void TextureCLUT8GPU::destroy() { + _clut8Texture.destroy(); + _paletteTexture.destroy(); + _target->destroy(); +} + +void TextureCLUT8GPU::recreate() { + _clut8Texture.create(); + _paletteTexture.create(); + _target->create(); + + // In case image date exists assure it will be completely refreshed next + // time. + if (_clut8Data.getPixels()) { + flagDirty(); + _paletteDirty = true; + } +} + +void TextureCLUT8GPU::enableLinearFiltering(bool enable) { + _target->getTexture()->enableLinearFiltering(enable); +} + +void TextureCLUT8GPU::allocate(uint width, uint height) { + // Assure the texture can contain our user data. + _clut8Texture.setSize(width, height); + _target->setSize(width, height); + + // In case the needed texture dimension changed we will reinitialize the + // texture data buffer. + if (_clut8Texture.getWidth() != _clut8Data.w || _clut8Texture.getHeight() != _clut8Data.h) { + // Create a buffer for the texture data. + _clut8Data.create(_clut8Texture.getWidth(), _clut8Texture.getHeight(), Graphics::PixelFormat::createFormatCLUT8()); + } + + // Create a sub-buffer for raw access. + _userPixelData = _clut8Data.getSubArea(Common::Rect(width, height)); + + // Setup structures for internal rendering to _glTexture. + _clut8Vertices[0] = 0; + _clut8Vertices[1] = 0; + + _clut8Vertices[2] = width; + _clut8Vertices[3] = 0; + + _clut8Vertices[4] = 0; + _clut8Vertices[5] = height; + + _clut8Vertices[6] = width; + _clut8Vertices[7] = height; + + // The whole texture is dirty after we changed the size. This fixes + // multiple texture size changes without any actual update in between. + // Without this we might try to write a too big texture into the GL + // texture. + flagDirty(); +} + +Graphics::PixelFormat TextureCLUT8GPU::getFormat() const { + return Graphics::PixelFormat::createFormatCLUT8(); +} + +void TextureCLUT8GPU::setColorKey(uint colorKey) { + // The key color is set to black so the color value is pre-multiplied with the alpha value + // to avoid color fringes due to filtering. + // Erasing the color data is not a problem as the palette is always fully re-initialized + // before setting the key color. + _palette[colorKey * 4 ] = 0x00; + _palette[colorKey * 4 + 1] = 0x00; + _palette[colorKey * 4 + 2] = 0x00; + _palette[colorKey * 4 + 3] = 0x00; + + _paletteDirty = true; +} + +void TextureCLUT8GPU::setPalette(uint start, uint colors, const byte *palData) { + byte *dst = _palette + start * 4; + + while (colors-- > 0) { + memcpy(dst, palData, 3); + dst[3] = 0xFF; + + dst += 4; + palData += 3; + } + + _paletteDirty = true; +} + +const GLTexture &TextureCLUT8GPU::getGLTexture() const { + return *_target->getTexture(); +} + +void TextureCLUT8GPU::updateGLTexture() { + const bool needLookUp = Surface::isDirty() || _paletteDirty; + + // Update CLUT8 texture if necessary. + if (Surface::isDirty()) { + _clut8Texture.updateArea(getDirtyArea(), _clut8Data); + clearDirty(); + } + + // Update palette if necessary. + if (_paletteDirty) { + Graphics::Surface palSurface; + palSurface.init(256, 1, 256, _palette, +#ifdef SCUMM_LITTLE_ENDIAN + Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24) // ABGR8888 +#else + Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0) // RGBA8888 +#endif + ); + + _paletteTexture.updateArea(Common::Rect(256, 1), palSurface); + _paletteDirty = false; + } + + // In case any data changed, do color look up and store result in _target. + if (needLookUp) { + lookUpColors(); + } +} + +void TextureCLUT8GPU::lookUpColors() { + // Setup pipeline to do color look up. + Pipeline *oldPipeline = g_context.setPipeline(_clut8Pipeline); + + // Do color look up. + g_context.getActivePipeline()->drawTexture(_clut8Texture, _clut8Vertices); + + // Restore old state. + g_context.setPipeline(oldPipeline); +} +#endif // !USE_FORCED_GLES + +} // End of namespace OpenGL diff --git a/backends/graphics/opengl/texture.h b/backends/graphics/opengl/texture.h new file mode 100644 index 00000000000..81adc384b9c --- /dev/null +++ b/backends/graphics/opengl/texture.h @@ -0,0 +1,419 @@ +/* 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" + +#include "common/rect.h" + +namespace OpenGL { + +class Shader; + +/** + * A simple GL texture object abstraction. + * + * This is used for low-level GL texture handling. + */ +class GLTexture { +public: + /** + * Constrcut a new GL texture object. + * + * @param glIntFormat The internal format to use. + * @param glFormat The input format. + * @param glType The input type. + */ + GLTexture(GLenum glIntFormat, GLenum glFormat, GLenum glType); + ~GLTexture(); + + /** + * Enable or disable linear texture filtering. + * + * @param enable true to enable and false to disable. + */ + void enableLinearFiltering(bool enable); + + /** + * Test whether linear filtering is enabled. + */ + bool isLinearFilteringEnabled() const { return (_glFilter == GL_LINEAR); } + + /** + * Destroy the OpenGL texture name. + */ + void destroy(); + + /** + * Create the OpenGL texture name. + */ + void create(); + + /** + * Bind the texture to the active texture unit. + */ + void bind() const; + + /** + * Sets the size of the texture in pixels. + * + * The internal OpenGL texture might have a different size. To query the + * actual size use getWidth()/getHeight(). + * + * @param width The desired logical width. + * @param height The desired logical height. + */ + void setSize(uint width, uint height); + + /** + * Copy image data to the texture. + * + * @param area The area to update. + * @param src Surface for the whole texture containing the pixel data + * to upload. Only the area described by area will be + * uploaded. + */ + void updateArea(const Common::Rect &area, const Graphics::Surface &src); + + /** + * Query the GL texture's width. + */ + uint getWidth() const { return _width; } + + /** + * Query the GL texture's height. + */ + uint getHeight() const { return _height; } + + /** + * Query the logical texture's width. + */ + uint getLogicalWidth() const { return _logicalWidth; } + + /** + * Query the logical texture's height. + */ + uint getLogicalHeight() const { return _logicalHeight; } + + /** + * Obtain texture coordinates for rectangular drawing. + */ + const GLfloat *getTexCoords() const { return _texCoords; } + + /** + * Obtain texture name. + * + * Beware that the texture name changes whenever create is used. + * destroy will invalidate the texture name. + */ + GLuint getGLTexture() const { return _glTexture; } +private: + const GLenum _glIntFormat; + const GLenum _glFormat; + const GLenum _glType; + + uint _width, _height; + uint _logicalWidth, _logicalHeight; + GLfloat _texCoords[4*2]; + + GLint _glFilter; + + GLuint _glTexture; +}; + +/** + * Interface for OpenGL implementations of a 2D surface. + */ +class Surface { +public: + Surface(); + virtual ~Surface() {} + + /** + * Destroy OpenGL description of surface. + */ + virtual void destroy() = 0; + + /** + * Recreate OpenGL description of surface. + */ + virtual void recreate() = 0; + + /** + * Enable or disable linear texture filtering. + * + * @param enable true to enable and false to disable. + */ + virtual void enableLinearFiltering(bool enable) = 0; + + /** + * Allocate storage for surface. + * + * @param width The desired logical width. + * @param height The desired logical height. + */ + virtual void allocate(uint width, uint height) = 0; + + /** + * Copy image data to the surface. + * + * The format of the input data needs to match the format returned by + * getFormat. + * + * @param x X coordinate of upper left corner to copy data to. + * @param y Y coordinate of upper left corner to copy data to. + * @param w Width of the image data to copy. + * @param h Height of the image data to copy. + * @param src Pointer to image data. + * @param srcPitch The number of bytes in a row of the image data. + */ + void copyRectToTexture(uint x, uint y, uint w, uint h, const void *src, uint srcPitch); + + /** + * Fill the surface with a fixed color. + * + * @param color Color value in format returned by getFormat. + */ + void fill(uint32 color); + + void flagDirty() { _allDirty = true; } + virtual bool isDirty() const { return _allDirty || !_dirtyArea.isEmpty(); } + + virtual uint getWidth() const = 0; + virtual uint getHeight() const = 0; + + /** + * @return The logical format of the texture data. + */ + virtual Graphics::PixelFormat getFormat() const = 0; + + virtual Graphics::Surface *getSurface() = 0; + virtual const Graphics::Surface *getSurface() const = 0; + + /** + * @return Whether the surface is having a palette. + */ + virtual bool hasPalette() const { return false; } + + /** + * Set color key for paletted textures. + * + * This needs to be called after any palette update affecting the color + * key. Calling this multiple times will result in multiple color indices + * to be treated as color keys. + */ + virtual void setColorKey(uint colorKey) {} + virtual void setPalette(uint start, uint colors, const byte *palData) {} + + /** + * Update underlying OpenGL texture to reflect current state. + */ + virtual void updateGLTexture() = 0; + + /** + * Obtain underlying OpenGL texture. + */ + virtual const GLTexture &getGLTexture() const = 0; +protected: + void clearDirty() { _allDirty = false; _dirtyArea = Common::Rect(); } + + Common::Rect getDirtyArea() const; +private: + bool _allDirty; + Common::Rect _dirtyArea; +}; + +/** + * 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 Surface { +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(); + + virtual void destroy(); + + virtual void recreate(); + + virtual void enableLinearFiltering(bool enable); + + virtual void allocate(uint width, uint height); + + virtual uint getWidth() const { return _userPixelData.w; } + virtual uint getHeight() const { return _userPixelData.h; } + + /** + * @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; } + + virtual void updateGLTexture(); + virtual const GLTexture &getGLTexture() const { return _glTexture; } +protected: + const Graphics::PixelFormat _format; + +private: + GLTexture _glTexture; + + Graphics::Surface _textureData; + Graphics::Surface _userPixelData; +}; + +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 setColorKey(uint colorKey); + virtual void setPalette(uint start, uint colors, const byte *palData); + + virtual Graphics::Surface *getSurface() { return &_clut8Data; } + virtual const Graphics::Surface *getSurface() const { return &_clut8Data; } + + virtual void updateGLTexture(); +private: + Graphics::Surface _clut8Data; + byte *_palette; +}; + +#if !USE_FORCED_GL +class FakeTexture : public Texture { +public: + FakeTexture(GLenum glIntFormat, GLenum glFormat, GLenum glType, const Graphics::PixelFormat &format); + virtual ~FakeTexture(); + + virtual void allocate(uint width, uint height); + + virtual Graphics::PixelFormat getFormat() const = 0; + + virtual Graphics::Surface *getSurface() { return &_rgbData; } + virtual const Graphics::Surface *getSurface() const { return &_rgbData; } +protected: + Graphics::Surface _rgbData; +}; + +class TextureRGB555 : public FakeTexture { +public: + TextureRGB555(); + virtual ~TextureRGB555() {}; + + virtual Graphics::PixelFormat getFormat() const; + + virtual void updateGLTexture(); +}; + +class TextureRGBA8888Swap : public FakeTexture { +public: + TextureRGBA8888Swap(); + virtual ~TextureRGBA8888Swap() {}; + + virtual Graphics::PixelFormat getFormat() const; + + virtual void updateGLTexture(); +}; +#endif // !USE_FORCED_GL + +#if !USE_FORCED_GLES +class TextureTarget; +class CLUT8LookUpPipeline; + +class TextureCLUT8GPU : public Surface { +public: + TextureCLUT8GPU(); + virtual ~TextureCLUT8GPU(); + + virtual void destroy(); + + virtual void recreate(); + + virtual void enableLinearFiltering(bool enable); + + virtual void allocate(uint width, uint height); + + virtual bool isDirty() const { return _paletteDirty || Surface::isDirty(); } + + virtual uint getWidth() const { return _userPixelData.w; } + virtual uint getHeight() const { return _userPixelData.h; } + + virtual Graphics::PixelFormat getFormat() const; + + virtual bool hasPalette() const { return true; } + + virtual void setColorKey(uint colorKey); + virtual void setPalette(uint start, uint colors, const byte *palData); + + virtual Graphics::Surface *getSurface() { return &_userPixelData; } + virtual const Graphics::Surface *getSurface() const { return &_userPixelData; } + + virtual void updateGLTexture(); + virtual const GLTexture &getGLTexture() const; + + static bool isSupportedByContext() { + return g_context.shadersSupported + && g_context.multitextureSupported + && g_context.framebufferObjectSupported; + } +private: + void lookUpColors(); + + GLTexture _clut8Texture; + GLTexture _paletteTexture; + + TextureTarget *_target; + CLUT8LookUpPipeline *_clut8Pipeline; + + GLfloat _clut8Vertices[4*2]; + + Graphics::Surface _clut8Data; + Graphics::Surface _userPixelData; + + byte _palette[4 * 256]; + bool _paletteDirty; +}; +#endif // !USE_FORCED_GLES + +} // End of namespace OpenGL + +#endif diff --git a/backends/graphics/openglsdl/openglsdl-graphics.cpp b/backends/graphics/openglsdl/openglsdl-graphics.cpp new file mode 100644 index 00000000000..b4857989f78 --- /dev/null +++ b/backends/graphics/openglsdl/openglsdl-graphics.cpp @@ -0,0 +1,675 @@ +/* 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/openglsdl/openglsdl-graphics.h" +#include "backends/graphics/opengl/texture.h" +#include "backends/events/sdl/sdl-events.h" +#include "backends/platform/sdl/sdl.h" +#include "graphics/scaler/aspect.h" + +#include "common/textconsole.h" +#include "common/config-manager.h" +#ifdef USE_OSD +#include "common/translation.h" +#endif + +OpenGLSdlGraphicsManager::OpenGLSdlGraphicsManager(SdlEventSource *eventSource, SdlWindow *window) + : SdlGraphicsManager(eventSource, window), _lastRequestedHeight(0), +#if SDL_VERSION_ATLEAST(2, 0, 0) + _glContext(), +#else + _lastVideoModeLoad(0), +#endif + _graphicsScale(2), _ignoreLoadVideoMode(false), _gotResize(false), _wantsFullScreen(false), _ignoreResizeEvents(0), + _desiredFullscreenWidth(0), _desiredFullscreenHeight(0) { + // Setup OpenGL attributes for SDL + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + + // Set up proper SDL OpenGL context creation. +#if SDL_VERSION_ATLEAST(2, 0, 0) + OpenGL::ContextType glContextType; + + // Context version 1.4 is choosen arbitrarily based on what most shader + // extensions were written against. + enum { + DEFAULT_GL_MAJOR = 1, + DEFAULT_GL_MINOR = 4, + + DEFAULT_GLES_MAJOR = 1, + DEFAULT_GLES_MINOR = 1, + + DEFAULT_GLES2_MAJOR = 2, + DEFAULT_GLES2_MINOR = 0 + }; + +#if USE_FORCED_GL + glContextType = OpenGL::kContextGL; + _glContextProfileMask = 0; + _glContextMajor = DEFAULT_GL_MAJOR; + _glContextMinor = DEFAULT_GL_MINOR; +#elif USE_FORCED_GLES + glContextType = OpenGL::kContextGLES; + _glContextProfileMask = SDL_GL_CONTEXT_PROFILE_ES; + _glContextMajor = DEFAULT_GLES_MAJOR; + _glContextMinor = DEFAULT_GLES_MINOR; +#elif USE_FORCED_GLES2 + glContextType = OpenGL::kContextGLES2; + _glContextProfileMask = SDL_GL_CONTEXT_PROFILE_ES; + _glContextMajor = DEFAULT_GLES2_MAJOR; + _glContextMinor = DEFAULT_GLES2_MINOR; +#else + bool noDefaults = false; + + // Obtain the default GL(ES) context SDL2 tries to setup. + // + // Please note this might not actually be SDL2's defaults when multiple + // instances of this object have been created. But that is no issue + // because then we already set up what we want to use. + // + // In case no defaults are given we prefer OpenGL over OpenGL ES. + if (SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &_glContextProfileMask) != 0) { + _glContextProfileMask = 0; + noDefaults = true; + } + + if (SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &_glContextMajor) != 0) { + noDefaults = true; + } + + if (SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &_glContextMinor) != 0) { + noDefaults = true; + } + + if (noDefaults) { + if (_glContextProfileMask == SDL_GL_CONTEXT_PROFILE_ES) { + _glContextMajor = DEFAULT_GLES_MAJOR; + _glContextMinor = DEFAULT_GLES_MINOR; + } else { + _glContextProfileMask = 0; + _glContextMajor = DEFAULT_GL_MAJOR; + _glContextMinor = DEFAULT_GL_MINOR; + } + } + + if (_glContextProfileMask == SDL_GL_CONTEXT_PROFILE_ES) { + if (_glContextMajor >= 2) { + glContextType = OpenGL::kContextGLES2; + } else { + glContextType = OpenGL::kContextGLES; + } + } else if (_glContextProfileMask == SDL_GL_CONTEXT_PROFILE_CORE) { + glContextType = OpenGL::kContextGL; + + // Core profile does not allow legacy functionality, which we use. + // Thus we request a standard OpenGL context. + _glContextProfileMask = 0; + _glContextMajor = DEFAULT_GL_MAJOR; + _glContextMinor = DEFAULT_GL_MINOR; + } else { + glContextType = OpenGL::kContextGL; + } +#endif + + setContextType(glContextType); +#else + setContextType(OpenGL::kContextGL); +#endif + + // Retrieve a list of working fullscreen modes +#if SDL_VERSION_ATLEAST(2, 0, 0) + const int numModes = SDL_GetNumDisplayModes(0); + for (int i = 0; i < numModes; ++i) { + SDL_DisplayMode mode; + if (SDL_GetDisplayMode(0, i, &mode)) { + continue; + } + + _fullscreenVideoModes.push_back(VideoMode(mode.w, mode.h)); + } +#else + const SDL_Rect *const *availableModes = SDL_ListModes(NULL, SDL_OPENGL | SDL_FULLSCREEN); + // TODO: NULL means that there are no fullscreen modes supported. We + // should probably use this information and disable any fullscreen support + // in this case. + if (availableModes != NULL && availableModes != (void *)-1) { + for (;*availableModes; ++availableModes) { + const SDL_Rect *mode = *availableModes; + + _fullscreenVideoModes.push_back(VideoMode(mode->w, mode->h)); + } + } +#endif + + // Sort the modes in ascending order. + Common::sort(_fullscreenVideoModes.begin(), _fullscreenVideoModes.end()); + + // Strip duplicates in video modes. + for (uint i = 0; i + 1 < _fullscreenVideoModes.size();) { + if (_fullscreenVideoModes[i] == _fullscreenVideoModes[i + 1]) { + _fullscreenVideoModes.remove_at(i); + } else { + ++i; + } + } + + Common::Rect desktopRes = _window->getDesktopResolution(); + + // In case SDL is fine with every mode we will force the desktop mode. + // TODO? We could also try to add some default resolutions here. + if (_fullscreenVideoModes.empty() && !desktopRes.isEmpty()) { + _fullscreenVideoModes.push_back(VideoMode(desktopRes.width(), desktopRes.height())); + } + + // Get information about display sizes from the previous runs. + if (ConfMan.hasKey("last_fullscreen_mode_width", Common::ConfigManager::kApplicationDomain) && ConfMan.hasKey("last_fullscreen_mode_height", Common::ConfigManager::kApplicationDomain)) { + _desiredFullscreenWidth = ConfMan.getInt("last_fullscreen_mode_width", Common::ConfigManager::kApplicationDomain); + _desiredFullscreenHeight = ConfMan.getInt("last_fullscreen_mode_height", Common::ConfigManager::kApplicationDomain); + } else { + // Use the desktop resolutions when no previous default has been setup. + _desiredFullscreenWidth = desktopRes.width(); + _desiredFullscreenHeight = desktopRes.height(); + } +} + +OpenGLSdlGraphicsManager::~OpenGLSdlGraphicsManager() { +#if SDL_VERSION_ATLEAST(2, 0, 0) + notifyContextDestroy(); + SDL_GL_DeleteContext(_glContext); +#endif +} + +bool OpenGLSdlGraphicsManager::hasFeature(OSystem::Feature f) const { + switch (f) { + case OSystem::kFeatureFullscreenMode: + case OSystem::kFeatureIconifyWindow: + return true; + + default: + return OpenGLGraphicsManager::hasFeature(f); + } +} + +void OpenGLSdlGraphicsManager::setFeatureState(OSystem::Feature f, bool enable) { + switch (f) { + case OSystem::kFeatureFullscreenMode: + assert(getTransactionMode() != kTransactionNone); + _wantsFullScreen = enable; + break; + + case OSystem::kFeatureIconifyWindow: + if (enable) { + _window->iconifyWindow(); + } + break; + + default: + OpenGLGraphicsManager::setFeatureState(f, enable); + } +} + +bool OpenGLSdlGraphicsManager::getFeatureState(OSystem::Feature f) const { + switch (f) { + case OSystem::kFeatureFullscreenMode: +#if SDL_VERSION_ATLEAST(2, 0, 0) + if (_window) { + return (SDL_GetWindowFlags(_window->getSDLWindow()) & SDL_WINDOW_FULLSCREEN) != 0; + } else { + return _wantsFullScreen; + } +#else + if (_hwScreen) { + return (_hwScreen->flags & SDL_FULLSCREEN) != 0; + } else { + return _wantsFullScreen; + } +#endif + + default: + return OpenGLGraphicsManager::getFeatureState(f); + } +} + +void OpenGLSdlGraphicsManager::initSize(uint w, uint h, const Graphics::PixelFormat *format) { + // HACK: This is stupid but the SurfaceSDL backend defaults to 2x. This + // assures that the launcher (which requests 320x200) has a reasonable + // size. It also makes small games have a reasonable size (i.e. at least + // 640x400). We follow the same logic here until we have a better way to + // give hints to our backend for that. + if (w > 320) { + _graphicsScale = 1; + } else { + _graphicsScale = 2; + } + + return OpenGLGraphicsManager::initSize(w, h, format); +} + +void OpenGLSdlGraphicsManager::updateScreen() { + if (_ignoreResizeEvents) { + --_ignoreResizeEvents; + } + + OpenGLGraphicsManager::updateScreen(); +} + +void OpenGLSdlGraphicsManager::notifyVideoExpose() { +} + +void OpenGLSdlGraphicsManager::notifyResize(const int width, const int height) { +#if SDL_VERSION_ATLEAST(2, 0, 0) + // We sometime get outdated resize events from SDL2. So check that the size we get + // is the actual current window size. If not ignore the resize. + // The issue for example occurs when switching from fullscreen to windowed mode or + // when switching between different fullscreen resolutions because SDL_DestroyWindow + // for a fullscreen window that doesn't have the SDL_WINDOW_FULLSCREEN_DESKTOP flag + // causes a SDL_WINDOWEVENT_RESIZED event with the old resolution to be sent, and this + // event is processed after recreating the window at the new resolution. + int currentWidth, currentHeight; + getWindowSizeFromSdl(¤tWidth, ¤tHeight); + if (width != currentWidth || height != currentHeight) + return; + // TODO: Implement high DPI support + handleResize(width, height, 90, 90); +#else + if (!_ignoreResizeEvents && _hwScreen && !(_hwScreen->flags & SDL_FULLSCREEN)) { + // We save that we handled a resize event here. We need to know this + // so we do not overwrite the users requested window size whenever we + // switch aspect ratio or similar. + _gotResize = true; + if (!setupMode(width, height)) { + warning("OpenGLSdlGraphicsManager::notifyResize: Resize failed ('%s')", SDL_GetError()); + g_system->quit(); + } + } +#endif +} + +bool OpenGLSdlGraphicsManager::loadVideoMode(uint requestedWidth, uint requestedHeight, const Graphics::PixelFormat &format) { + // In some cases we might not want to load the requested video mode. This + // will assure that the window size is not altered. + if (_ignoreLoadVideoMode) { + _ignoreLoadVideoMode = false; + return true; + } + + // This function should never be called from notifyResize thus we know + // that the requested size came from somewhere else. + _gotResize = false; + + // Save the requested dimensions. + _lastRequestedWidth = requestedWidth; + _lastRequestedHeight = requestedHeight; + + // Apply the currently saved scale setting. + requestedWidth *= _graphicsScale; + requestedHeight *= _graphicsScale; + + // Set up the mode. + return setupMode(requestedWidth, requestedHeight); +} + +void OpenGLSdlGraphicsManager::refreshScreen() { + // Swap OpenGL buffers +#if SDL_VERSION_ATLEAST(2, 0, 0) + SDL_GL_SwapWindow(_window->getSDLWindow()); +#else + SDL_GL_SwapBuffers(); +#endif +} + +void *OpenGLSdlGraphicsManager::getProcAddress(const char *name) const { + return SDL_GL_GetProcAddress(name); +} + +void OpenGLSdlGraphicsManager::handleResizeImpl(const int width, const int height, const int xdpi, const int ydpi) { + OpenGLGraphicsManager::handleResizeImpl(width, height, xdpi, ydpi); + SdlGraphicsManager::handleResizeImpl(width, height, xdpi, ydpi); +} + +bool OpenGLSdlGraphicsManager::saveScreenshot(const Common::String &filename) const { + return OpenGLGraphicsManager::saveScreenshot(filename); +} + +bool OpenGLSdlGraphicsManager::setupMode(uint width, uint height) { + // In case we request a fullscreen mode we will use the mode the user + // has chosen last time or the biggest mode available. + if (_wantsFullScreen) { + if (_desiredFullscreenWidth && _desiredFullscreenHeight) { + // In case only a distinct set of modes is available we check + // whether the requested mode is actually available. + if (!_fullscreenVideoModes.empty()) { + VideoModeArray::const_iterator i = Common::find(_fullscreenVideoModes.begin(), + _fullscreenVideoModes.end(), + VideoMode(_desiredFullscreenWidth, _desiredFullscreenHeight)); + // It's not available fall back to default. + if (i == _fullscreenVideoModes.end()) { + _desiredFullscreenWidth = 0; + _desiredFullscreenHeight = 0; + } + } + } + + // In case no desired mode has been set we default to the biggest mode + // available or the requested mode in case we don't know any + // any fullscreen modes. + if (!_desiredFullscreenWidth || !_desiredFullscreenHeight) { + if (!_fullscreenVideoModes.empty()) { + VideoModeArray::const_iterator i = _fullscreenVideoModes.end(); + --i; + + _desiredFullscreenWidth = i->width; + _desiredFullscreenHeight = i->height; + } else { + _desiredFullscreenWidth = width; + _desiredFullscreenHeight = height; + } + } + + // Remember our choice. + ConfMan.setInt("last_fullscreen_mode_width", _desiredFullscreenWidth, Common::ConfigManager::kApplicationDomain); + ConfMan.setInt("last_fullscreen_mode_height", _desiredFullscreenHeight, Common::ConfigManager::kApplicationDomain); + } + + // This is pretty confusing since RGBA8888 talks about the memory + // layout here. This is a different logical layout depending on + // whether we run on little endian or big endian. However, we can + // only safely assume that RGBA8888 in memory layout is supported. + // Thus, we chose this one. + const Graphics::PixelFormat rgba8888 = +#ifdef SCUMM_LITTLE_ENDIAN + Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24); +#else + Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0); +#endif + +#if SDL_VERSION_ATLEAST(2, 0, 0) + if (_glContext) { + notifyContextDestroy(); + + SDL_GL_DeleteContext(_glContext); + _glContext = nullptr; + } + + uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE; + if (_wantsFullScreen) { + // On Linux/X11, when toggling to fullscreen, the window manager saves + // the window size to be able to restore it when going back to windowed mode. + // If the user configured ScummVM to start in fullscreen mode, we first + // create a window and then toggle it to fullscreen to give the window manager + // a chance to save the window size. That way if the user switches back + // to windowed mode, the window manager has a window size to apply instead + // of leaving the window at the fullscreen resolution size. + const char *driver = SDL_GetCurrentVideoDriver(); + if (!_window->getSDLWindow() && driver && strcmp(driver, "x11") == 0) { + _window->createOrUpdateWindow(width, height, flags); + } + + width = _desiredFullscreenWidth; + height = _desiredFullscreenHeight; + + flags |= SDL_WINDOW_FULLSCREEN; + } + + // Request a OpenGL (ES) context we can use. + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, _glContextProfileMask); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, _glContextMajor); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, _glContextMinor); + + if (!createOrUpdateWindow(width, height, flags)) { + return false; + } + + _glContext = SDL_GL_CreateContext(_window->getSDLWindow()); + if (!_glContext) { + return false; + } + + notifyContextCreate(rgba8888, rgba8888); + int actualWidth, actualHeight; + getWindowSizeFromSdl(&actualWidth, &actualHeight); + // TODO: Implement high DPI support + handleResize(actualWidth, actualHeight, 90, 90); + return true; +#else + // WORKAROUND: Working around infamous SDL bugs when switching + // resolutions too fast. This might cause the event system to supply + // incorrect mouse position events otherwise. + // Reference: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=665779 + const uint32 curTime = SDL_GetTicks(); + if (_hwScreen && (curTime < _lastVideoModeLoad || curTime - _lastVideoModeLoad < 100)) { + for (int i = 10; i > 0; --i) { + SDL_PumpEvents(); + SDL_Delay(10); + } + } + + uint32 flags = SDL_OPENGL; + if (_wantsFullScreen) { + width = _desiredFullscreenWidth; + height = _desiredFullscreenHeight; + flags |= SDL_FULLSCREEN; + } else { + flags |= SDL_RESIZABLE; + } + + if (_hwScreen) { + // When a video mode has been setup already we notify the manager that + // the context is about to be destroyed. + // We do this because on Windows SDL_SetVideoMode can destroy and + // recreate the OpenGL context. + notifyContextDestroy(); + } + + _hwScreen = SDL_SetVideoMode(width, height, 32, flags); + + if (!_hwScreen) { + // We treat fullscreen requests as a "hint" for now. This means in + // case it is not available we simply ignore it. + if (_wantsFullScreen) { + _hwScreen = SDL_SetVideoMode(width, height, 32, SDL_OPENGL | SDL_RESIZABLE); + } + } + + // Part of the WORKAROUND mentioned above. + _lastVideoModeLoad = SDL_GetTicks(); + + if (_hwScreen) { + notifyContextCreate(rgba8888, rgba8888); + handleResize(_hwScreen->w, _hwScreen->h, 90, 90); + } + + // Ignore resize events (from SDL) for a few frames, if this isn't + // caused by a notification from SDL. This avoids bad resizes to a + // (former) resolution for which we haven't processed an event yet. + if (!_gotResize) + _ignoreResizeEvents = 10; + + return _hwScreen != nullptr; +#endif +} + +bool OpenGLSdlGraphicsManager::notifyEvent(const Common::Event &event) { + if (event.type != Common::EVENT_CUSTOM_BACKEND_ACTION_START) { + return SdlGraphicsManager::notifyEvent(event); + } + + switch ((CustomEventAction) event.customType) { + case kActionIncreaseScaleFactor: + case kActionDecreaseScaleFactor: { + const int direction = event.customType == kActionIncreaseScaleFactor ? +1 : -1; + + if (getFeatureState(OSystem::kFeatureFullscreenMode)) { + // In case we are in fullscreen we will choose the previous + // or next mode. + + // In case no modes are available we do nothing. + if (_fullscreenVideoModes.empty()) { + return true; + } + + // Look for the current mode. + VideoModeArray::const_iterator i = Common::find(_fullscreenVideoModes.begin(), + _fullscreenVideoModes.end(), + VideoMode(_desiredFullscreenWidth, _desiredFullscreenHeight)); + if (i == _fullscreenVideoModes.end()) { + return true; + } + + // Cycle through the modes in the specified direction. + if (direction > 0) { + ++i; + if (i == _fullscreenVideoModes.end()) { + i = _fullscreenVideoModes.begin(); + } + } else { + if (i == _fullscreenVideoModes.begin()) { + i = _fullscreenVideoModes.end(); + } + --i; + } + + _desiredFullscreenWidth = i->width; + _desiredFullscreenHeight = i->height; + + // Try to setup the mode. + if (!setupMode(_lastRequestedWidth, _lastRequestedHeight)) { + warning("OpenGLSdlGraphicsManager::notifyEvent: Fullscreen resize failed ('%s')", SDL_GetError()); + g_system->quit(); + } + } else { + // Calculate the next scaling setting. We approximate the + // current scale setting in case the user resized the + // window. Then we apply the direction change. + int windowWidth = 0, windowHeight = 0; + getWindowSizeFromSdl(&windowWidth, &windowHeight); + _graphicsScale = MAX(windowWidth / _lastRequestedWidth, windowHeight / _lastRequestedHeight); + _graphicsScale = MAX(_graphicsScale + direction, 1); + + // Since we overwrite a user resize here we reset its + // flag here. This makes enabling AR smoother because it + // will change the window size like in surface SDL. + _gotResize = false; + + // Try to setup the mode. + if (!setupMode(_lastRequestedWidth * _graphicsScale, _lastRequestedHeight * _graphicsScale)) { + warning("OpenGLSdlGraphicsManager::notifyEvent: Window resize failed ('%s')", SDL_GetError()); + g_system->quit(); + } + } + +#ifdef USE_OSD + int windowWidth = 0, windowHeight = 0; + getWindowSizeFromSdl(&windowWidth, &windowHeight); + const Common::U32String osdMsg = Common::U32String::format(_("Resolution: %dx%d"), windowWidth, windowHeight); + displayMessageOnOSD(osdMsg); +#endif + + return true; + } + + case kActionToggleAspectRatioCorrection: + // In case the user changed the window size manually we will + // not change the window size again here. + _ignoreLoadVideoMode = _gotResize; + + // Toggles the aspect ratio correction state. + beginGFXTransaction(); + setFeatureState(OSystem::kFeatureAspectRatioCorrection, !getFeatureState(OSystem::kFeatureAspectRatioCorrection)); + endGFXTransaction(); + + // Make sure we do not ignore the next resize. This + // effectively checks whether loadVideoMode has been called. + assert(!_ignoreLoadVideoMode); + +#ifdef USE_OSD + if (getFeatureState(OSystem::kFeatureAspectRatioCorrection)) + displayMessageOnOSD(_("Enabled aspect ratio correction")); + else + displayMessageOnOSD(_("Disabled aspect ratio correction")); +#endif + + return true; + + case kActionToggleFilteredScaling: + // Never ever try to resize the window when we simply want to enable or disable filtering. + // This assures that the window size does not change. + _ignoreLoadVideoMode = true; + + // Ctrl+Alt+f toggles filtering on/off + beginGFXTransaction(); + setFeatureState(OSystem::kFeatureFilteringMode, !getFeatureState(OSystem::kFeatureFilteringMode)); + endGFXTransaction(); + + // Make sure we do not ignore the next resize. This + // effectively checks whether loadVideoMode has been called. + assert(!_ignoreLoadVideoMode); + +#ifdef USE_OSD + if (getFeatureState(OSystem::kFeatureFilteringMode)) { + displayMessageOnOSD(_("Filtering enabled")); + } else { + displayMessageOnOSD(_("Filtering disabled")); + } +#endif + + return true; + + case kActionCycleStretchMode: { + // Never try to resize the window when changing the scaling mode. + _ignoreLoadVideoMode = true; + + // Ctrl+Alt+s cycles through stretch mode + int index = 0; + const OSystem::GraphicsMode *stretchModes = getSupportedStretchModes(); + const OSystem::GraphicsMode *sm = stretchModes; + while (sm->name) { + if (sm->id == getStretchMode()) + break; + sm++; + index++; + } + index++; + if (!stretchModes[index].name) + index = 0; + beginGFXTransaction(); + setStretchMode(stretchModes[index].id); + endGFXTransaction(); + +#ifdef USE_OSD + Common::U32String message = Common::U32String::format("%S: %S", + _("Stretch mode").c_str(), + _(stretchModes[index].description).c_str() + ); + displayMessageOnOSD(message); +#endif + + return true; + } + + default: + return SdlGraphicsManager::notifyEvent(event); + } +} diff --git a/backends/graphics/openglsdl/openglsdl-graphics.h b/backends/graphics/openglsdl/openglsdl-graphics.h new file mode 100644 index 00000000000..3f75fa7923e --- /dev/null +++ b/backends/graphics/openglsdl/openglsdl-graphics.h @@ -0,0 +1,115 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef BACKENDS_GRAPHICS_OPENGLSDL_OPENGLSDL_GRAPHICS_H +#define BACKENDS_GRAPHICS_OPENGLSDL_OPENGLSDL_GRAPHICS_H + +#include "backends/graphics/opengl/opengl-graphics.h" +#include "backends/graphics/sdl/sdl-graphics.h" +#include "backends/platform/sdl/sdl-sys.h" + +#include "common/array.h" +#include "common/events.h" + +class OpenGLSdlGraphicsManager : public OpenGL::OpenGLGraphicsManager, public SdlGraphicsManager { +public: + OpenGLSdlGraphicsManager(SdlEventSource *eventSource, SdlWindow *window); + virtual ~OpenGLSdlGraphicsManager(); + + virtual bool hasFeature(OSystem::Feature f) const override; + virtual void setFeatureState(OSystem::Feature f, bool enable) override; + virtual bool getFeatureState(OSystem::Feature f) const override; + + virtual void initSize(uint w, uint h, const Graphics::PixelFormat *format) override; + virtual void updateScreen() override; + + // EventObserver API + virtual bool notifyEvent(const Common::Event &event) override; + + // SdlGraphicsManager API + virtual void notifyVideoExpose() override; + virtual void notifyResize(const int width, const int height) override; + +protected: + virtual bool loadVideoMode(uint requestedWidth, uint requestedHeight, const Graphics::PixelFormat &format) override; + + virtual void refreshScreen() override; + + virtual void *getProcAddress(const char *name) const override; + + virtual void handleResizeImpl(const int width, const int height, const int xdpi, const int ydpi) override; + + virtual bool saveScreenshot(const Common::String &filename) const override; + + virtual int getGraphicsModeScale(int mode) const override { return 1; } + +private: + bool setupMode(uint width, uint height); + +#if SDL_VERSION_ATLEAST(2, 0, 0) + int _glContextProfileMask, _glContextMajor, _glContextMinor; + SDL_GLContext _glContext; +#else + uint32 _lastVideoModeLoad; +#endif + + uint _lastRequestedWidth; + uint _lastRequestedHeight; + uint _graphicsScale; + bool _ignoreLoadVideoMode; + bool _gotResize; + + bool _wantsFullScreen; + uint _ignoreResizeEvents; + + struct VideoMode { + VideoMode() : width(0), height(0) {} + VideoMode(uint w, uint h) : width(w), height(h) {} + + bool operator<(const VideoMode &right) const { + if (width < right.width) { + return true; + } else if (width == right.width && height < right.height) { + return true; + } else { + return false; + } + } + + bool operator==(const VideoMode &right) const { + return width == right.width && height == right.height; + } + + bool operator!=(const VideoMode &right) const { + return !(*this == right); + } + + uint width, height; + }; + typedef Common::Array VideoModeArray; + VideoModeArray _fullscreenVideoModes; + + uint _desiredFullscreenWidth; + uint _desiredFullscreenHeight; +}; + +#endif diff --git a/backends/graphics/sdl/sdl-graphics.cpp b/backends/graphics/sdl/sdl-graphics.cpp new file mode 100644 index 00000000000..969746d926a --- /dev/null +++ b/backends/graphics/sdl/sdl-graphics.cpp @@ -0,0 +1,450 @@ +/* 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/sdl/sdl-graphics.h" +#include "backends/platform/sdl/sdl-sys.h" +#include "backends/platform/sdl/sdl.h" +#include "backends/events/sdl/sdl-events.h" +#include "backends/keymapper/action.h" +#include "backends/keymapper/keymap.h" +#include "common/config-manager.h" +#include "common/fs.h" +#include "common/textconsole.h" +#include "common/translation.h" +#include "graphics/scaler/aspect.h" +#ifdef USE_OSD +#include "common/translation.h" +#endif + +SdlGraphicsManager::SdlGraphicsManager(SdlEventSource *source, SdlWindow *window) + : _eventSource(source), _window(window), _hwScreen(nullptr) +#if SDL_VERSION_ATLEAST(2, 0, 0) + , _allowWindowSizeReset(false), _hintedWidth(0), _hintedHeight(0), _lastFlags(0) +#endif +{ + SDL_GetMouseState(&_cursorX, &_cursorY); +} + +void SdlGraphicsManager::activateManager() { + _eventSource->setGraphicsManager(dynamic_cast(this)); + + // Register the graphics manager as a event observer + g_system->getEventManager()->getEventDispatcher()->registerObserver(this, 10, false); +} + +void SdlGraphicsManager::deactivateManager() { + // Unregister the event observer + if (g_system->getEventManager()->getEventDispatcher()) { + g_system->getEventManager()->getEventDispatcher()->unregisterObserver(this); + } + + _eventSource->setGraphicsManager(0); +} + +SdlGraphicsManager::State SdlGraphicsManager::getState() const { + State state; + + state.screenWidth = getWidth(); + state.screenHeight = getHeight(); + state.aspectRatio = getFeatureState(OSystem::kFeatureAspectRatioCorrection); + state.fullscreen = getFeatureState(OSystem::kFeatureFullscreenMode); + state.cursorPalette = getFeatureState(OSystem::kFeatureCursorPalette); +#ifdef USE_RGB_COLOR + state.pixelFormat = getScreenFormat(); +#endif + return state; +} + +bool SdlGraphicsManager::setState(const State &state) { + beginGFXTransaction(); +#ifdef USE_RGB_COLOR + initSize(state.screenWidth, state.screenHeight, &state.pixelFormat); +#else + initSize(state.screenWidth, state.screenHeight, nullptr); +#endif + setFeatureState(OSystem::kFeatureAspectRatioCorrection, state.aspectRatio); + setFeatureState(OSystem::kFeatureFullscreenMode, state.fullscreen); + setFeatureState(OSystem::kFeatureCursorPalette, state.cursorPalette); + + if (endGFXTransaction() != OSystem::kTransactionSuccess) { + return false; + } else { + return true; + } +} + +bool SdlGraphicsManager::defaultGraphicsModeConfig() const { + const Common::ConfigManager::Domain *transientDomain = ConfMan.getDomain(Common::ConfigManager::kTransientDomain); + if (transientDomain && transientDomain->contains("gfx_mode")) { + const Common::String &mode = transientDomain->getVal("gfx_mode"); + if (!mode.equalsIgnoreCase("normal") && !mode.equalsIgnoreCase("default")) { + return false; + } + } + + const Common::ConfigManager::Domain *gameDomain = ConfMan.getActiveDomain(); + if (gameDomain && gameDomain->contains("gfx_mode")) { + const Common::String &mode = gameDomain->getVal("gfx_mode"); + if (!mode.equalsIgnoreCase("normal") && !mode.equalsIgnoreCase("default")) { + return false; + } + } + + return true; +} + +int SdlGraphicsManager::getGraphicsModeIdByName(const Common::String &name) const { + if (name == "normal" || name == "default") { + return getDefaultGraphicsMode(); + } + + const OSystem::GraphicsMode *mode = getSupportedGraphicsModes(); + while (mode && mode->name != nullptr) { + if (name.equalsIgnoreCase(mode->name)) { + return mode->id; + } + ++mode; + } + return -1; +} + +void SdlGraphicsManager::initSizeHint(const Graphics::ModeList &modes) { +#if SDL_VERSION_ATLEAST(2, 0, 0) + const bool useDefault = defaultGraphicsModeConfig(); + + int scale = getGraphicsModeScale(getGraphicsModeIdByName(ConfMan.get("gfx_mode"))); + if (scale == -1) { + warning("Unknown scaler; defaulting to 1"); + scale = 1; + } + + int16 bestWidth = 0, bestHeight = 0; + const Graphics::ModeList::const_iterator end = modes.end(); + for (Graphics::ModeList::const_iterator it = modes.begin(); it != end; ++it) { + int16 width = it->width, height = it->height; + + // TODO: Normalize AR correction by passing a PAR in the mode list + // instead of checking the dimensions here like this, since not all + // 320x200/640x400 uses are with non-square pixels (e.g. DreamWeb). + if (ConfMan.getBool("aspect_ratio")) { + if ((width == 320 && height == 200) || (width == 640 && height == 400)) { + height = real2Aspect(height); + } + } + + if (!useDefault || width <= 320) { + width *= scale; + height *= scale; + } + + if (bestWidth < width) { + bestWidth = width; + } + + if (bestHeight < height) { + bestHeight = height; + } + } + + _hintedWidth = bestWidth; + _hintedHeight = bestHeight; +#endif +} + +bool SdlGraphicsManager::showMouse(bool visible) { + if (visible == _cursorVisible) { + return visible; + } + + int showCursor = SDL_DISABLE; + if (visible) { + // _cursorX and _cursorY are currently always clipped to the active + // area, so we need to ask SDL where the system's mouse cursor is + // instead + int x, y; + SDL_GetMouseState(&x, &y); + if (!_activeArea.drawRect.contains(Common::Point(x, y))) { + showCursor = SDL_ENABLE; + } + } + SDL_ShowCursor(showCursor); + + return WindowedGraphicsManager::showMouse(visible); +} + +bool SdlGraphicsManager::notifyMousePosition(Common::Point &mouse) { + mouse.x = CLIP(mouse.x, 0, _windowWidth - 1); + mouse.y = CLIP(mouse.y, 0, _windowHeight - 1); + + int showCursor = SDL_DISABLE; + bool valid = true; + if (_activeArea.drawRect.contains(mouse)) { + _cursorLastInActiveArea = true; + } else { + mouse.x = CLIP(mouse.x, _activeArea.drawRect.left, _activeArea.drawRect.right - 1); + mouse.y = CLIP(mouse.y, _activeArea.drawRect.top, _activeArea.drawRect.bottom - 1); + + if (_window->mouseIsGrabbed() || + // Keep the mouse inside the game area during dragging to prevent an + // event mismatch where the mouseup event gets lost because it is + // performed outside of the game area + (_cursorLastInActiveArea && SDL_GetMouseState(nullptr, nullptr) != 0)) { + setSystemMousePosition(mouse.x, mouse.y); + } else { + // Allow the in-game mouse to get a final movement event to the edge + // of the window if the mouse was moved out of the game area + if (_cursorLastInActiveArea) { + _cursorLastInActiveArea = false; + } else if (_cursorVisible) { + // Keep sending events to the game if the cursor is invisible, + // since otherwise if a game lets you skip a cutscene by + // clicking and the user moved the mouse outside the active + // area, the clicks wouldn't do anything, which would be + // confusing + valid = false; + } + + if (_cursorVisible) { + showCursor = SDL_ENABLE; + } + } + } + + SDL_ShowCursor(showCursor); + if (valid) { + setMousePosition(mouse.x, mouse.y); + mouse = convertWindowToVirtual(mouse.x, mouse.y); + } + return valid; +} + +void SdlGraphicsManager::setSystemMousePosition(const int x, const int y) { + assert(_window); + if (!_window->warpMouseInWindow(x, y)) { + const Common::Point mouse = convertWindowToVirtual(x, y); + _eventSource->fakeWarpMouse(mouse.x, mouse.y); + } +} + +void SdlGraphicsManager::handleResizeImpl(const int width, const int height, const int xdpi, const int ydpi) { + _forceRedraw = true; +} + +#if SDL_VERSION_ATLEAST(2, 0, 0) +bool SdlGraphicsManager::createOrUpdateWindow(int width, int height, const Uint32 flags) { + if (!_window) { + return false; + } + + // We only update the actual window when flags change (which usually means + // fullscreen mode is entered/exited), when updates are forced so that we + // do not reset the window size whenever a game makes a call to change the + // size or pixel format of the internal game surface (since a user may have + // resized the game window), or when the launcher is visible (since a user + // may change the scaler, which should reset the window size) + if (!_window->getSDLWindow() || _lastFlags != flags || _overlayVisible || _allowWindowSizeReset) { + const bool fullscreen = (flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_FULLSCREEN_DESKTOP)) != 0; + if (!fullscreen) { + if (_hintedWidth) { + width = _hintedWidth; + } + if (_hintedHeight) { + height = _hintedHeight; + } + } + + if (!_window->createOrUpdateWindow(width, height, flags)) { + return false; + } + + _lastFlags = flags; + _allowWindowSizeReset = false; + } + + return true; +} +#endif + +void SdlGraphicsManager::saveScreenshot() { + Common::String filename; + + Common::String screenshotsPath; + OSystem_SDL *sdl_g_system = dynamic_cast(g_system); + if (sdl_g_system) + screenshotsPath = sdl_g_system->getScreenshotsPath(); + + // Use the name of the running target as a base for screenshot file names + Common::String currentTarget = ConfMan.getActiveDomainName(); + +#ifdef USE_PNG + const char *extension = "png"; +#else + const char *extension = "bmp"; +#endif + + for (int n = 0;; n++) { + filename = Common::String::format("scummvm%s%s-%05d.%s", currentTarget.empty() ? "" : "-", + currentTarget.c_str(), n, extension); + + Common::FSNode file = Common::FSNode(screenshotsPath + filename); + if (!file.exists()) { + break; + } + } + + if (saveScreenshot(screenshotsPath + filename)) { + if (screenshotsPath.empty()) + debug("Saved screenshot '%s' in current directory", filename.c_str()); + else + debug("Saved screenshot '%s' in directory '%s'", filename.c_str(), screenshotsPath.c_str()); + } else { + if (screenshotsPath.empty()) + warning("Could not save screenshot in current directory"); + else + warning("Could not save screenshot in directory '%s'", screenshotsPath.c_str()); + } +} + +bool SdlGraphicsManager::notifyEvent(const Common::Event &event) { + if (event.type != Common::EVENT_CUSTOM_BACKEND_ACTION_START) { + return false; + } + + switch ((CustomEventAction) event.customType) { + case kActionToggleMouseCapture: + getWindow()->toggleMouseGrab(); + return true; + + case kActionToggleFullscreen: + toggleFullScreen(); + return true; + + case kActionSaveScreenshot: + saveScreenshot(); + return true; + + default: + return false; + } +} + +void SdlGraphicsManager::toggleFullScreen() { + if (!g_system->hasFeature(OSystem::kFeatureFullscreenMode)) + return; + + beginGFXTransaction(); + setFeatureState(OSystem::kFeatureFullscreenMode, !getFeatureState(OSystem::kFeatureFullscreenMode)); + endGFXTransaction(); +#ifdef USE_OSD + if (getFeatureState(OSystem::kFeatureFullscreenMode)) + displayMessageOnOSD(_("Fullscreen mode")); + else + displayMessageOnOSD(_("Windowed mode")); +#endif +} + +Common::Keymap *SdlGraphicsManager::getKeymap() { + using namespace Common; + + Keymap *keymap = new Keymap(Keymap::kKeymapTypeGlobal, "sdl-graphics", _("Graphics")); + Action *act; + + if (g_system->hasFeature(OSystem::kFeatureFullscreenMode)) { + act = new Action("FULS", _("Toggle fullscreen")); + act->addDefaultInputMapping("A+RETURN"); + act->addDefaultInputMapping("A+KP_ENTER"); + act->setCustomBackendActionEvent(kActionToggleFullscreen); + keymap->addAction(act); + } + + act = new Action("CAPT", _("Toggle mouse capture")); + act->addDefaultInputMapping("C+m"); + act->setCustomBackendActionEvent(kActionToggleMouseCapture); + keymap->addAction(act); + + act = new Action("SCRS", _("Save screenshot")); + act->addDefaultInputMapping("A+s"); + act->setCustomBackendActionEvent(kActionSaveScreenshot); + keymap->addAction(act); + + if (hasFeature(OSystem::kFeatureAspectRatioCorrection)) { + act = new Action("ASPT", _("Toggle aspect ratio correction")); + act->addDefaultInputMapping("C+A+a"); + act->setCustomBackendActionEvent(kActionToggleAspectRatioCorrection); + keymap->addAction(act); + } + + if (hasFeature(OSystem::kFeatureFilteringMode)) { + act = new Action("FILT", _("Toggle linear filtered scaling")); + act->addDefaultInputMapping("C+A+f"); + act->setCustomBackendActionEvent(kActionToggleFilteredScaling); + keymap->addAction(act); + } + + if (hasFeature(OSystem::kFeatureStretchMode)) { + act = new Action("STCH", _("Cycle through stretch modes")); + act->addDefaultInputMapping("C+A+s"); + act->setCustomBackendActionEvent(kActionCycleStretchMode); + keymap->addAction(act); + } + + act = new Action("SCL+", _("Increase the scale factor")); + act->addDefaultInputMapping("C+A+PLUS"); + act->addDefaultInputMapping("C+A+KP_PLUS"); + act->setCustomBackendActionEvent(kActionIncreaseScaleFactor); + keymap->addAction(act); + + act = new Action("SCL-", _("Decrease the scale factor")); + act->addDefaultInputMapping("C+A+MINUS"); + act->addDefaultInputMapping("C+A+KP_MINUS"); + act->setCustomBackendActionEvent(kActionDecreaseScaleFactor); + keymap->addAction(act); + +#ifdef USE_SCALERS + struct ActionEntry { + const char *id; + const char *description; + }; + static const ActionEntry filters[] = { + { "FLT1", _s("Switch to nearest neighbour scaling") }, + { "FLT2", _s("Switch to AdvMame 2x/3x scaling") }, +#ifdef USE_HQ_SCALERS + { "FLT3", _s("Switch to HQ 2x/3x scaling") }, +#endif + { "FLT4", _s("Switch to 2xSai scaling") }, + { "FLT5", _s("Switch to Super2xSai scaling") }, + { "FLT6", _s("Switch to SuperEagle scaling") }, + { "FLT7", _s("Switch to TV 2x scaling") }, + { "FLT8", _s("Switch to DotMatrix scaling") } + }; + + for (uint i = 0; i < ARRAYSIZE(filters); i++) { + act = new Action(filters[i].id, _(filters[i].description)); + act->addDefaultInputMapping(String::format("C+A+%d", i + 1)); + act->addDefaultInputMapping(String::format("C+A+KP%d", i + 1)); + act->setCustomBackendActionEvent(kActionSetScaleFilter1 + i); + keymap->addAction(act); + } +#endif + + return keymap; +} diff --git a/backends/graphics/sdl/sdl-graphics.h b/backends/graphics/sdl/sdl-graphics.h new file mode 100644 index 00000000000..f32c5fc2e1d --- /dev/null +++ b/backends/graphics/sdl/sdl-graphics.h @@ -0,0 +1,213 @@ +/* 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_SDL_SDLGRAPHICS_H +#define BACKENDS_GRAPHICS_SDL_SDLGRAPHICS_H + +#include "backends/graphics/windowed.h" +#include "backends/platform/sdl/sdl-window.h" + +#include "common/events.h" +#include "common/rect.h" + +class SdlEventSource; + +#ifndef __SYMBIAN32__ +#define USE_OSD 1 +#endif + +/** + * Base class for a SDL based graphics manager. + */ +class SdlGraphicsManager : virtual public WindowedGraphicsManager, public Common::EventObserver { +public: + SdlGraphicsManager(SdlEventSource *source, SdlWindow *window); + virtual ~SdlGraphicsManager() {} + + /** + * Makes this graphics manager active. That means it should be ready to + * process inputs now. However, even without being active it should be + * able to query the supported modes and other bits. + */ + virtual void activateManager(); + + /** + * Makes this graphics manager inactive. This should allow another + * graphics manager to become active again. + */ + virtual void deactivateManager(); + + /** + * Notify the graphics manager that the graphics needs to be redrawn, since + * the application window was modified. + * + * This is basically called when SDL_VIDEOEXPOSE was received. + */ + virtual void notifyVideoExpose() = 0; + + /** + * Notify the graphics manager about a resize event. + * + * It is noteworthy that the requested width/height should actually be set + * up as is and not changed by the graphics manager, since otherwise it may + * lead to odd behavior for certain window managers. + * + * It is only required to overwrite this method in case you want a + * resizable window. The default implementation just does nothing. + * + * @param width Requested window width. + * @param height Requested window height. + */ + virtual void notifyResize(const int width, const int height) {} + + /** + * Notifies the graphics manager about a mouse position change. + * + * The passed point *must* be converted from window coordinates to virtual + * coordinates in order for the event to be processed correctly by the game + * engine. Just use `convertWindowToVirtual` for this unless you need to do + * something special. + * + * @param mouse The mouse position in window coordinates, which must be + * converted synchronously to virtual coordinates. + * @returns true if the mouse was in a valid position for the game and + * should cause the event to be sent to the game. + */ + virtual bool notifyMousePosition(Common::Point &mouse); + + virtual bool showMouse(bool visible) override; + + virtual bool saveScreenshot(const Common::String &filename) const { return false; } + void saveScreenshot() override; + + // Override from Common::EventObserver + virtual bool notifyEvent(const Common::Event &event) override; + + /** + * A (subset) of the graphic manager's state. This is used when switching + * between different SDL graphic managers at runtime. + */ + struct State { + int screenWidth, screenHeight; + bool aspectRatio; + bool fullscreen; + bool cursorPalette; + +#ifdef USE_RGB_COLOR + Graphics::PixelFormat pixelFormat; +#endif + }; + + /** + * Gets the current state of the graphics manager. + */ + State getState() const; + + /** + * Sets up a basic state of the graphics manager. + */ + bool setState(const State &state); + + /** + * @returns the SDL window. + */ + SdlWindow *getWindow() const { return _window; } + + virtual void initSizeHint(const Graphics::ModeList &modes) override; + + Common::Keymap *getKeymap(); + +protected: + enum CustomEventAction { + kActionToggleFullscreen = 100, + kActionToggleMouseCapture, + kActionSaveScreenshot, + kActionToggleAspectRatioCorrection, + kActionToggleFilteredScaling, + kActionCycleStretchMode, + kActionIncreaseScaleFactor, + kActionDecreaseScaleFactor, + kActionSetScaleFilter1, + kActionSetScaleFilter2, + kActionSetScaleFilter3, + kActionSetScaleFilter4, + kActionSetScaleFilter5, + kActionSetScaleFilter6, + kActionSetScaleFilter7, + kActionSetScaleFilter8 + }; + + virtual int getGraphicsModeScale(int mode) const = 0; + + bool defaultGraphicsModeConfig() const; + int getGraphicsModeIdByName(const Common::String &name) const; + + /** + * Gets the dimensions of the window directly from SDL instead of from the + * values stored by the graphics manager. + */ + void getWindowSizeFromSdl(int *width, int *height) const { +#if SDL_VERSION_ATLEAST(2, 0, 0) + assert(_window); + SDL_GetWindowSize(_window->getSDLWindow(), width, height); +#else + assert(_hwScreen); + + if (width) { + *width = _hwScreen->w; + } + + if (height) { + *height = _hwScreen->h; + } +#endif + } + + virtual void setSystemMousePosition(const int x, const int y) override; + + virtual void handleResizeImpl(const int width, const int height, const int xdpi, const int ydpi) override; + +#if SDL_VERSION_ATLEAST(2, 0, 0) +public: + void unlockWindowSize() { + _allowWindowSizeReset = true; + _hintedWidth = 0; + _hintedHeight = 0; + } + +protected: + Uint32 _lastFlags; + bool _allowWindowSizeReset; + int _hintedWidth, _hintedHeight; + + bool createOrUpdateWindow(const int width, const int height, const Uint32 flags); +#endif + + SDL_Surface *_hwScreen; + SdlEventSource *_eventSource; + SdlWindow *_window; + +private: + void toggleFullScreen(); +}; + +#endif diff --git a/backends/graphics/surfacesdl/surfacesdl-graphics.cpp b/backends/graphics/surfacesdl/surfacesdl-graphics.cpp new file mode 100644 index 00000000000..2ba3cb4e5ad --- /dev/null +++ b/backends/graphics/surfacesdl/surfacesdl-graphics.cpp @@ -0,0 +1,2711 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" + +#if defined(SDL_BACKEND) +#include "backends/graphics/surfacesdl/surfacesdl-graphics.h" +#include "backends/events/sdl/sdl-events.h" +#include "common/config-manager.h" +#include "common/mutex.h" +#include "common/textconsole.h" +#include "common/translation.h" +#include "common/util.h" +#include "common/file.h" +#include "common/frac.h" +#ifdef USE_RGB_COLOR +#include "common/list.h" +#endif +#include "graphics/font.h" +#include "graphics/fontman.h" +#include "graphics/scaler.h" +#include "graphics/scaler/aspect.h" +#include "graphics/surface.h" +#include "gui/debugger.h" +#include "gui/EventRecorder.h" +#ifdef USE_PNG +#include "image/png.h" +#else +#include "image/bmp.h" +#endif +#ifdef USE_TTS +#include "common/text-to-speech.h" +#endif + +// SDL surface flags which got removed in SDL2. +#if SDL_VERSION_ATLEAST(2, 0, 0) +#define SDL_SRCCOLORKEY 0 +#define SDL_SRCALPHA 0 +#define SDL_FULLSCREEN 0x40000000 +#endif + +static const OSystem::GraphicsMode s_supportedGraphicsModes[] = { + {"1x", _s("Normal (no scaling)"), GFX_NORMAL}, +#ifdef USE_SCALERS + {"2x", "2x", GFX_DOUBLESIZE}, + {"3x", "3x", GFX_TRIPLESIZE}, + {"2xsai", "2xSAI", GFX_2XSAI}, + {"super2xsai", "Super2xSAI", GFX_SUPER2XSAI}, + {"supereagle", "SuperEagle", GFX_SUPEREAGLE}, + {"advmame2x", "AdvMAME2x", GFX_ADVMAME2X}, + {"advmame3x", "AdvMAME3x", GFX_ADVMAME3X}, +#ifdef USE_HQ_SCALERS + {"hq2x", "HQ2x", GFX_HQ2X}, + {"hq3x", "HQ3x", GFX_HQ3X}, +#endif + {"tv2x", "TV2x", GFX_TV2X}, + {"dotmatrix", "DotMatrix", GFX_DOTMATRIX}, +#endif + {0, 0, 0} +}; + +#if SDL_VERSION_ATLEAST(2, 0, 0) +const OSystem::GraphicsMode s_supportedStretchModes[] = { + {"center", _s("Center"), STRETCH_CENTER}, + {"pixel-perfect", _s("Pixel-perfect scaling"), STRETCH_INTEGRAL}, + {"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} +}; +#endif + +DECLARE_TRANSLATION_ADDITIONAL_CONTEXT("Normal (no scaling)", "lowres") + +// Table of the cursor scalers [scaleFactor - 1] +static ScalerProc *scalersMagn[3] = { +#ifdef USE_SCALERS + Normal1x, AdvMame2x, AdvMame3x +#else // remove dependencies on other scalers + Normal1x, Normal1x, Normal1x +#endif +}; + +static const int s_gfxModeSwitchTable[][4] = { + { GFX_NORMAL, GFX_DOUBLESIZE, GFX_TRIPLESIZE, -1 }, + { GFX_NORMAL, GFX_ADVMAME2X, GFX_ADVMAME3X, -1 }, + { GFX_NORMAL, GFX_HQ2X, GFX_HQ3X, -1 }, + { GFX_NORMAL, GFX_2XSAI, -1, -1 }, + { GFX_NORMAL, GFX_SUPER2XSAI, -1, -1 }, + { GFX_NORMAL, GFX_SUPEREAGLE, -1, -1 }, + { GFX_NORMAL, GFX_TV2X, -1, -1 }, + { GFX_NORMAL, GFX_DOTMATRIX, -1, -1 } + }; + +AspectRatio::AspectRatio(int w, int h) { + // TODO : Validation and so on... + // Currently, we just ensure the program don't instantiate non-supported aspect ratios + _kw = w; + _kh = h; +} + +#if !defined(__SYMBIAN32__) && defined(USE_SCALERS) +static AspectRatio getDesiredAspectRatio() { + const size_t AR_COUNT = 4; + const char *desiredAspectRatioAsStrings[AR_COUNT] = { "auto", "4/3", "16/9", "16/10" }; + const AspectRatio desiredAspectRatios[AR_COUNT] = { AspectRatio(0, 0), AspectRatio(4,3), AspectRatio(16,9), AspectRatio(16,10) }; + + //TODO : We could parse an arbitrary string, if we code enough proper validation + Common::String desiredAspectRatio = ConfMan.get("desired_screen_aspect_ratio"); + + for (size_t i = 0; i < AR_COUNT; i++) { + assert(desiredAspectRatioAsStrings[i] != NULL); + + if (!scumm_stricmp(desiredAspectRatio.c_str(), desiredAspectRatioAsStrings[i])) { + return desiredAspectRatios[i]; + } + } + // TODO : Report a warning + return AspectRatio(0, 0); +} +#endif + +SurfaceSdlGraphicsManager::SurfaceSdlGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window) + : + SdlGraphicsManager(sdlEventSource, window), +#ifdef USE_OSD + _osdMessageSurface(nullptr), _osdMessageAlpha(SDL_ALPHA_TRANSPARENT), _osdMessageFadeStartTime(0), + _osdIconSurface(nullptr), +#endif +#if SDL_VERSION_ATLEAST(2, 0, 0) + _renderer(nullptr), _screenTexture(nullptr), +#endif +#if defined(WIN32) && !SDL_VERSION_ATLEAST(2, 0, 0) + _originalBitsPerPixel(0), +#endif + _screen(0), _tmpscreen(0), + _screenFormat(Graphics::PixelFormat::createFormatCLUT8()), + _cursorFormat(Graphics::PixelFormat::createFormatCLUT8()), + _overlayscreen(0), _tmpscreen2(0), + _scalerProc(0), _screenChangeCount(0), + _mouseData(nullptr), _mouseSurface(nullptr), + _mouseOrigSurface(nullptr), _cursorDontScale(false), _cursorPaletteDisabled(true), + _currentShakeXOffset(0), _currentShakeYOffset(0), + _paletteDirtyStart(0), _paletteDirtyEnd(0), + _screenIsLocked(false), + _displayDisabled(false), +#ifdef USE_SDL_DEBUG_FOCUSRECT + _enableFocusRectDebugCode(false), _enableFocusRect(false), _focusRect(), +#endif + _transactionMode(kTransactionNone) { + + // allocate palette storage + _currentPalette = (SDL_Color *)calloc(sizeof(SDL_Color), 256); + _cursorPalette = (SDL_Color *)calloc(sizeof(SDL_Color), 256); + + _mouseBackup.x = _mouseBackup.y = _mouseBackup.w = _mouseBackup.h = 0; + +#ifdef USE_SDL_DEBUG_FOCUSRECT + if (ConfMan.hasKey("use_sdl_debug_focusrect")) + _enableFocusRectDebugCode = ConfMan.getBool("use_sdl_debug_focusrect"); +#endif + +#if !defined(__SYMBIAN32__) && defined(USE_SCALERS) + _videoMode.mode = GFX_DOUBLESIZE; + _videoMode.scaleFactor = 2; + _videoMode.aspectRatioCorrection = ConfMan.getBool("aspect_ratio"); + _videoMode.desiredAspectRatio = getDesiredAspectRatio(); + _scalerProc = Normal2x; +#else // for small screen platforms + _videoMode.mode = GFX_NORMAL; + _videoMode.scaleFactor = 1; + _videoMode.aspectRatioCorrection = false; + _scalerProc = Normal1x; +#endif + _scalerType = 0; + + _videoMode.fullscreen = ConfMan.getBool("fullscreen"); + _videoMode.filtering = ConfMan.getBool("filtering"); +#if SDL_VERSION_ATLEAST(2, 0, 0) + _videoMode.stretchMode = STRETCH_FIT; +#endif +} + +SurfaceSdlGraphicsManager::~SurfaceSdlGraphicsManager() { + unloadGFXMode(); + if (_mouseOrigSurface) { + SDL_FreeSurface(_mouseOrigSurface); + if (_mouseOrigSurface == _mouseSurface) { + _mouseSurface = nullptr; + } + } + if (_mouseSurface) { + SDL_FreeSurface(_mouseSurface); + } + free(_currentPalette); + free(_cursorPalette); + delete[] _mouseData; +} + +bool SurfaceSdlGraphicsManager::hasFeature(OSystem::Feature f) const { + return + (f == OSystem::kFeatureFullscreenMode) || + (f == OSystem::kFeatureAspectRatioCorrection) || + (f == OSystem::kFeatureFilteringMode) || +#if SDL_VERSION_ATLEAST(2, 0, 0) + (f == OSystem::kFeatureStretchMode) || +#endif + (f == OSystem::kFeatureCursorPalette) || + (f == OSystem::kFeatureIconifyWindow); +} + +void SurfaceSdlGraphicsManager::setFeatureState(OSystem::Feature f, bool enable) { + switch (f) { + case OSystem::kFeatureFullscreenMode: + setFullscreenMode(enable); + break; + case OSystem::kFeatureAspectRatioCorrection: + setAspectRatioCorrection(enable); + break; + case OSystem::kFeatureFilteringMode: + setFilteringMode(enable); + break; + case OSystem::kFeatureCursorPalette: + _cursorPaletteDisabled = !enable; + blitCursor(); + break; + case OSystem::kFeatureIconifyWindow: + if (enable) + _window->iconifyWindow(); + break; + default: + break; + } +} + +bool SurfaceSdlGraphicsManager::getFeatureState(OSystem::Feature f) const { + // We need to allow this to be called from within a transaction, since we + // currently use it to retreive the graphics state, when switching from + // SDL->OpenGL mode for example. + //assert(_transactionMode == kTransactionNone); + + switch (f) { + case OSystem::kFeatureFullscreenMode: + return _videoMode.fullscreen; + case OSystem::kFeatureAspectRatioCorrection: + return _videoMode.aspectRatioCorrection; + case OSystem::kFeatureFilteringMode: + return _videoMode.filtering; + case OSystem::kFeatureCursorPalette: + return !_cursorPaletteDisabled; + default: + return false; + } +} + +const OSystem::GraphicsMode *SurfaceSdlGraphicsManager::getSupportedGraphicsModes() const { + return s_supportedGraphicsModes; +} + +int SurfaceSdlGraphicsManager::getDefaultGraphicsMode() const { +#ifdef USE_SCALERS + return GFX_DOUBLESIZE; +#else + return GFX_NORMAL; +#endif +} + +void SurfaceSdlGraphicsManager::resetGraphicsScale() { + setGraphicsMode(s_gfxModeSwitchTable[_scalerType][0]); +} + +void SurfaceSdlGraphicsManager::beginGFXTransaction() { + assert(_transactionMode == kTransactionNone); + + _transactionMode = kTransactionActive; + + _transactionDetails.sizeChanged = false; + + _transactionDetails.needHotswap = false; + _transactionDetails.needUpdatescreen = false; + +#if SDL_VERSION_ATLEAST(2, 0, 0) + _transactionDetails.needDisplayResize = false; + _transactionDetails.needTextureUpdate = false; +#endif +#ifdef USE_RGB_COLOR + _transactionDetails.formatChanged = false; +#endif + + _oldVideoMode = _videoMode; +} + +OSystem::TransactionError SurfaceSdlGraphicsManager::endGFXTransaction() { + int errors = OSystem::kTransactionSuccess; + + assert(_transactionMode != kTransactionNone); + + if (_transactionMode == kTransactionRollback) { + if (_videoMode.fullscreen != _oldVideoMode.fullscreen) { + errors |= OSystem::kTransactionFullscreenFailed; + + _videoMode.fullscreen = _oldVideoMode.fullscreen; + } else if (_videoMode.aspectRatioCorrection != _oldVideoMode.aspectRatioCorrection) { + errors |= OSystem::kTransactionAspectRatioFailed; + + _videoMode.aspectRatioCorrection = _oldVideoMode.aspectRatioCorrection; + } else if (_videoMode.mode != _oldVideoMode.mode) { + errors |= OSystem::kTransactionModeSwitchFailed; + + _videoMode.mode = _oldVideoMode.mode; + _videoMode.scaleFactor = _oldVideoMode.scaleFactor; +#if SDL_VERSION_ATLEAST(2, 0, 0) + } else if (_videoMode.stretchMode != _oldVideoMode.stretchMode) { + errors |= OSystem::kTransactionStretchModeSwitchFailed; + + _videoMode.stretchMode = _oldVideoMode.stretchMode; +#endif + } else if (_videoMode.filtering != _oldVideoMode.filtering) { + errors |= OSystem::kTransactionFilteringFailed; + + _videoMode.filtering = _oldVideoMode.filtering; +#ifdef USE_RGB_COLOR + } else if (_videoMode.format != _oldVideoMode.format) { + errors |= OSystem::kTransactionFormatNotSupported; + + _videoMode.format = _oldVideoMode.format; + _screenFormat = _videoMode.format; +#endif + } else if (_videoMode.screenWidth != _oldVideoMode.screenWidth || _videoMode.screenHeight != _oldVideoMode.screenHeight) { + errors |= OSystem::kTransactionSizeChangeFailed; + + _videoMode.screenWidth = _oldVideoMode.screenWidth; + _videoMode.screenHeight = _oldVideoMode.screenHeight; + _videoMode.overlayWidth = _oldVideoMode.overlayWidth; + _videoMode.overlayHeight = _oldVideoMode.overlayHeight; + } + + if (_videoMode.fullscreen == _oldVideoMode.fullscreen && + _videoMode.aspectRatioCorrection == _oldVideoMode.aspectRatioCorrection && + _videoMode.mode == _oldVideoMode.mode && + _videoMode.filtering == _oldVideoMode.filtering && + _videoMode.screenWidth == _oldVideoMode.screenWidth && + _videoMode.screenHeight == _oldVideoMode.screenHeight) { + + // Our new video mode would now be exactly the same as the + // old one. Since we still can not assume SDL_SetVideoMode + // to be working fine, we need to invalidate the old video + // mode, so loadGFXMode would error out properly. + _oldVideoMode.setup = false; + } + } + +#ifdef USE_RGB_COLOR + if (_transactionDetails.sizeChanged || _transactionDetails.formatChanged) { +#else + if (_transactionDetails.sizeChanged) { +#endif + unloadGFXMode(); + if (!loadGFXMode()) { + if (_oldVideoMode.setup) { + _transactionMode = kTransactionRollback; + errors |= endGFXTransaction(); + } + } else { + setGraphicsModeIntern(); + clearOverlay(); + + _videoMode.setup = true; + // OSystem_SDL::pollEvent used to update the screen change count, + // but actually it gives problems when a video mode was changed + // but OSystem_SDL::pollEvent was not called. This for example + // caused a crash under certain circumstances when returning to launcher. + // To fix this issue we update the screen change count right here. + _screenChangeCount++; + } + } else if (_transactionDetails.needHotswap) { + setGraphicsModeIntern(); + if (!hotswapGFXMode()) { + if (_oldVideoMode.setup) { + _transactionMode = kTransactionRollback; + errors |= endGFXTransaction(); + } + } else { + _videoMode.setup = true; + // OSystem_SDL::pollEvent used to update the screen change count, + // but actually it gives problems when a video mode was changed + // but OSystem_SDL::pollEvent was not called. This for example + // caused a crash under certain circumstances when returning to launcher. + // To fix this issue we update the screen change count right here. + _screenChangeCount++; + +#if SDL_VERSION_ATLEAST(2, 0, 0) + if (_transactionDetails.needDisplayResize) + recalculateDisplayAreas(); +#endif + if (_transactionDetails.needUpdatescreen) + internUpdateScreen(); + } +#if SDL_VERSION_ATLEAST(2, 0, 0) + } else if (_transactionDetails.needTextureUpdate) { + setGraphicsModeIntern(); + recreateScreenTexture(); + if (_transactionDetails.needDisplayResize) + recalculateDisplayAreas(); + internUpdateScreen(); +#endif + } else if (_transactionDetails.needUpdatescreen) { + setGraphicsModeIntern(); +#if SDL_VERSION_ATLEAST(2, 0, 0) + if (_transactionDetails.needDisplayResize) + recalculateDisplayAreas(); +#endif + internUpdateScreen(); + } + + _transactionMode = kTransactionNone; + return (OSystem::TransactionError)errors; +} + +Graphics::PixelFormat SurfaceSdlGraphicsManager::convertSDLPixelFormat(SDL_PixelFormat *in) const { + Graphics::PixelFormat out(in->BytesPerPixel, + 8 - in->Rloss, 8 - in->Gloss, + 8 - in->Bloss, 8 - in->Aloss, + in->Rshift, in->Gshift, + in->Bshift, in->Ashift); + + // Workaround to SDL not providing an accurate Aloss value on some platforms. + if (in->Amask == 0) + out.aLoss = 8; + + return out; +} + +#ifdef USE_RGB_COLOR +Common::List SurfaceSdlGraphicsManager::getSupportedFormats() const { + assert(!_supportedFormats.empty()); + return _supportedFormats; +} + +#if SDL_VERSION_ATLEAST(2, 0, 0) +static void maskToBitCount(Uint32 mask, uint8 &numBits, uint8 &shift) { + numBits = 0; + shift = 32; + for (int i = 0; i < 32; ++i) { + if (mask & 1) { + if (i < shift) { + shift = i; + } + ++numBits; + } + + mask >>= 1; + } +} +#endif + +void SurfaceSdlGraphicsManager::detectSupportedFormats() { + _supportedFormats.clear(); + + Graphics::PixelFormat format = Graphics::PixelFormat::createFormatCLUT8(); + +#if SDL_VERSION_ATLEAST(2, 0, 0) + { + SDL_Window *window = _window->getSDLWindow(); + if (window == nullptr) { + error("Could not find ScummVM window for retrieving default display mode"); + } + + const int displayIndex = SDL_GetWindowDisplayIndex(window); + if (displayIndex < 0) { + error("Could not find ScummVM window display index"); + } + + SDL_DisplayMode defaultMode; + if (SDL_GetDesktopDisplayMode(displayIndex, &defaultMode) != 0) { + error("Could not get default system display mode"); + } + + int bpp; + Uint32 rMask, gMask, bMask, aMask; + if (SDL_PixelFormatEnumToMasks(defaultMode.format, &bpp, &rMask, &gMask, &bMask, &aMask) != SDL_TRUE) { + error("Could not convert system pixel format %s to masks", SDL_GetPixelFormatName(defaultMode.format)); + } + + const uint8 bytesPerPixel = SDL_BYTESPERPIXEL(defaultMode.format); + uint8 rBits, rShift, gBits, gShift, bBits, bShift, aBits, aShift; + maskToBitCount(rMask, rBits, rShift); + maskToBitCount(gMask, gBits, gShift); + maskToBitCount(bMask, bBits, bShift); + maskToBitCount(aMask, aBits, aShift); + + format = Graphics::PixelFormat(bytesPerPixel, rBits, gBits, bBits, aBits, rShift, gShift, bShift, aShift); + + _supportedFormats.push_back(format); + } +#endif + + // Some tables with standard formats that we always list + // as "supported". If frontend code tries to use one of + // these, we will perform the necessary format + // conversion in the background. Of course this incurs a + // performance hit, but on desktop ports this should not + // matter. We still push the currently active format to + // the front, so if frontend code just uses the first + // available format, it will get one that is "cheap" to + // use. + const Graphics::PixelFormat RGBList[] = { + // RGBA8888, ARGB8888, RGB888 + Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0), + Graphics::PixelFormat(4, 8, 8, 8, 8, 16, 8, 0, 24), + Graphics::PixelFormat(3, 8, 8, 8, 0, 16, 8, 0, 0), + // RGB565, XRGB1555, RGB555, RGBA4444, ARGB4444 + Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0), + Graphics::PixelFormat(2, 5, 5, 5, 1, 10, 5, 0, 15), + Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0), + Graphics::PixelFormat(2, 4, 4, 4, 4, 12, 8, 4, 0), + Graphics::PixelFormat(2, 4, 4, 4, 4, 8, 4, 0, 12) + }; + const Graphics::PixelFormat BGRList[] = { + // ABGR8888, BGRA8888, BGR888 + Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24), + Graphics::PixelFormat(4, 8, 8, 8, 8, 8, 16, 24, 0), + Graphics::PixelFormat(3, 8, 8, 8, 0, 0, 8, 16, 0), + // BGR565, XBGR1555, BGR555, ABGR4444, BGRA4444 + Graphics::PixelFormat(2, 5, 6, 5, 0, 0, 5, 11, 0), + Graphics::PixelFormat(2, 5, 5, 5, 1, 0, 5, 10, 15), + Graphics::PixelFormat(2, 5, 5, 5, 0, 0, 5, 10, 0), + Graphics::PixelFormat(2, 4, 4, 4, 4, 0, 4, 8, 12), + Graphics::PixelFormat(2, 4, 4, 4, 4, 4, 8, 12, 0) + }; + + if (_hwScreen) { + // Get our currently set hardware format + Graphics::PixelFormat hwFormat = convertSDLPixelFormat(_hwScreen->format); + + _supportedFormats.push_back(hwFormat); + +#if !SDL_VERSION_ATLEAST(2, 0, 0) + format = hwFormat; +#endif + } + + // TODO: prioritize matching alpha masks + int i; + + // Push some RGB formats + for (i = 0; i < ARRAYSIZE(RGBList); i++) { + if (_hwScreen && (RGBList[i].bytesPerPixel > format.bytesPerPixel)) + continue; + if (RGBList[i] != format) + _supportedFormats.push_back(RGBList[i]); + } + + // Push some BGR formats + for (i = 0; i < ARRAYSIZE(BGRList); i++) { + if (_hwScreen && (BGRList[i].bytesPerPixel > format.bytesPerPixel)) + continue; + if (BGRList[i] != format) + _supportedFormats.push_back(BGRList[i]); + } + + // Finally, we always supposed 8 bit palette graphics + _supportedFormats.push_back(Graphics::PixelFormat::createFormatCLUT8()); +} +#endif + +int SurfaceSdlGraphicsManager::getGraphicsModeScale(int mode) const { + int scale; + switch (mode) { + case GFX_NORMAL: + scale = 1; + break; +#ifdef USE_SCALERS + case GFX_DOUBLESIZE: + case GFX_2XSAI: + case GFX_SUPER2XSAI: + case GFX_SUPEREAGLE: + case GFX_ADVMAME2X: + case GFX_TV2X: + case GFX_DOTMATRIX: +#ifdef USE_HQ_SCALERS + case GFX_HQ2X: +#endif + scale = 2; + break; + case GFX_TRIPLESIZE: + case GFX_ADVMAME3X: +#ifdef USE_HQ_SCALERS + case GFX_HQ3X: +#endif + scale = 3; + break; +#endif + default: + scale = -1; + } + + return scale; +} + +bool SurfaceSdlGraphicsManager::setGraphicsMode(int mode) { + Common::StackLock lock(_graphicsMutex); + + assert(_transactionMode == kTransactionActive); + + if (_oldVideoMode.setup && _oldVideoMode.mode == mode) + return true; + + int newScaleFactor = getGraphicsModeScale(mode); + + if (newScaleFactor == -1) { + warning("unknown gfx mode %d", mode); + return false; + } + + if (_oldVideoMode.setup && _oldVideoMode.scaleFactor != newScaleFactor) + _transactionDetails.needHotswap = true; + + _transactionDetails.needUpdatescreen = true; + + _videoMode.mode = mode; + _videoMode.scaleFactor = newScaleFactor; + + return true; +} + +ScalerProc *SurfaceSdlGraphicsManager::getGraphicsScalerProc(int mode) const { + ScalerProc *newScalerProc = 0; + switch (_videoMode.mode) { + case GFX_NORMAL: + newScalerProc = Normal1x; + break; +#ifdef USE_SCALERS + case GFX_DOUBLESIZE: + newScalerProc = Normal2x; + break; + case GFX_TRIPLESIZE: + newScalerProc = Normal3x; + break; + + case GFX_2XSAI: + newScalerProc = _2xSaI; + break; + case GFX_SUPER2XSAI: + newScalerProc = Super2xSaI; + break; + case GFX_SUPEREAGLE: + newScalerProc = SuperEagle; + break; + case GFX_ADVMAME2X: + newScalerProc = AdvMame2x; + break; + case GFX_ADVMAME3X: + newScalerProc = AdvMame3x; + break; +#ifdef USE_HQ_SCALERS + case GFX_HQ2X: + newScalerProc = HQ2x; + break; + case GFX_HQ3X: + newScalerProc = HQ3x; + break; +#endif + case GFX_TV2X: + newScalerProc = TV2x; + break; + case GFX_DOTMATRIX: + newScalerProc = DotMatrix; + break; +#endif // USE_SCALERS + default: + break; + } + + return newScalerProc; +} + +void SurfaceSdlGraphicsManager::setGraphicsModeIntern() { + Common::StackLock lock(_graphicsMutex); + + ScalerProc *newScalerProc = getGraphicsScalerProc(_videoMode.mode); + + if (!newScalerProc) { + error("Unknown gfx mode %d", _videoMode.mode); + } + + _scalerProc = newScalerProc; + + if (_videoMode.mode != GFX_NORMAL) { + for (int i = 0; i < ARRAYSIZE(s_gfxModeSwitchTable); i++) { + if (s_gfxModeSwitchTable[i][1] == _videoMode.mode || s_gfxModeSwitchTable[i][2] == _videoMode.mode) { + _scalerType = i; + break; + } + } + } + + if (!_screen || !_hwScreen) + return; + + // Blit everything to the screen + _forceRedraw = true; + + // Even if the old and new scale factors are the same, we may have a + // different scaler for the cursor now. + blitCursor(); +} + +int SurfaceSdlGraphicsManager::getGraphicsMode() const { + assert(_transactionMode == kTransactionNone); + return _videoMode.mode; +} + +#if SDL_VERSION_ATLEAST(2, 0, 0) +const OSystem::GraphicsMode *SurfaceSdlGraphicsManager::getSupportedStretchModes() const { + return s_supportedStretchModes; +} + +int SurfaceSdlGraphicsManager::getDefaultStretchMode() const { + return STRETCH_FIT; +} + +bool SurfaceSdlGraphicsManager::setStretchMode(int mode) { + Common::StackLock lock(_graphicsMutex); + + assert(_transactionMode == kTransactionActive); + + if (_oldVideoMode.setup && _oldVideoMode.stretchMode == mode) + return true; + + // Check this is a valid mode + const OSystem::GraphicsMode *sm = s_supportedStretchModes; + bool found = false; + while (sm->name) { + if (sm->id == mode) { + found = true; + break; + } + sm++; + } + if (!found) { + warning("unknown stretch mode %d", mode); + return false; + } + + _transactionDetails.needUpdatescreen = true; + _transactionDetails.needDisplayResize = true; + + _videoMode.stretchMode = mode; + + return true; +} + +int SurfaceSdlGraphicsManager::getStretchMode() const { + return _videoMode.stretchMode; +} +#endif + +void SurfaceSdlGraphicsManager::initSize(uint w, uint h, const Graphics::PixelFormat *format) { + assert(_transactionMode == kTransactionActive); + + _gameScreenShakeXOffset = 0; + _gameScreenShakeYOffset = 0; + +#ifdef USE_RGB_COLOR + //avoid redundant format changes + Graphics::PixelFormat newFormat; + if (!format) + newFormat = Graphics::PixelFormat::createFormatCLUT8(); + else + newFormat = *format; + + assert(newFormat.bytesPerPixel > 0); + + if (newFormat != _videoMode.format) { + _videoMode.format = newFormat; + _transactionDetails.formatChanged = true; + _screenFormat = newFormat; + } +#endif + +#if !SDL_VERSION_ATLEAST(2, 0, 0) + // Avoid redundant res changes, only in SDL1. In SDL2, redundancies may not + // actually be redundant if ScummVM is switching between game engines and + // the screen dimensions are being reinitialized, since window resizing is + // supposed to reset when this happens + if ((int)w == _videoMode.screenWidth && (int)h == _videoMode.screenHeight) + return; +#endif + + if ((int)w != _videoMode.screenWidth || (int)h != _videoMode.screenHeight) { + const bool useDefault = defaultGraphicsModeConfig(); + if (useDefault && w > 320) { + resetGraphicsScale(); + } else { + setGraphicsMode(getGraphicsModeIdByName(ConfMan.get("gfx_mode"))); + } + } + + _videoMode.screenWidth = w; + _videoMode.screenHeight = h; + + _transactionDetails.sizeChanged = true; +} + +static void fixupResolutionForAspectRatio(AspectRatio desiredAspectRatio, int &width, int &height) { + assert(&width != &height); + + if (desiredAspectRatio.isAuto()) + return; + + int kw = desiredAspectRatio.kw(); + int kh = desiredAspectRatio.kh(); + + const int w = width; + const int h = height; + + int bestW = 0, bestH = 0; + uint bestMetric = (uint)-1; // Metric is wasted space + +#if SDL_VERSION_ATLEAST(2, 0, 0) + const int numModes = SDL_GetNumDisplayModes(0); + SDL_DisplayMode modeData, *mode = &modeData; + for (int i = 0; i < numModes; ++i) { + if (SDL_GetDisplayMode(0, i, &modeData)) { + continue; + } +#else + SDL_Rect const* const*availableModes = SDL_ListModes(NULL, SDL_FULLSCREEN|SDL_SWSURFACE); //TODO : Maybe specify a pixel format + assert(availableModes); + + while (const SDL_Rect *mode = *availableModes++) { +#endif + if (mode->w < w) + continue; + if (mode->h < h) + continue; + if (mode->h * kw != mode->w * kh) + continue; + + uint metric = mode->w * mode->h - w * h; + if (metric > bestMetric) + continue; + + bestMetric = metric; + bestW = mode->w; + bestH = mode->h; + + // Make editors a bit more happy by having the same amount of closing as + // opening curley braces. +#if SDL_VERSION_ATLEAST(2, 0, 0) + } +#else + } +#endif + + if (!bestW || !bestH) { + warning("Unable to enforce the desired aspect ratio"); + return; + } + width = bestW; + height = bestH; +} + +void SurfaceSdlGraphicsManager::setupHardwareSize() { + _videoMode.overlayWidth = _videoMode.screenWidth * _videoMode.scaleFactor; + _videoMode.overlayHeight = _videoMode.screenHeight * _videoMode.scaleFactor; + + if (_videoMode.screenHeight != 200 && _videoMode.screenHeight != 400) + _videoMode.aspectRatioCorrection = false; + + _videoMode.hardwareWidth = _videoMode.screenWidth * _videoMode.scaleFactor; + _videoMode.hardwareHeight = _videoMode.screenHeight * _videoMode.scaleFactor; + + if (_videoMode.aspectRatioCorrection) { + _videoMode.overlayHeight = real2Aspect(_videoMode.overlayHeight); + _videoMode.hardwareHeight = real2Aspect(_videoMode.hardwareHeight); + } +} + +bool SurfaceSdlGraphicsManager::loadGFXMode() { + _forceRedraw = true; + + setupHardwareSize(); + + // + // Create the surface that contains the 8 bit game data + // + + const Graphics::PixelFormat &format = _screenFormat; + const Uint32 rMask = ((0xFF >> format.rLoss) << format.rShift); + const Uint32 gMask = ((0xFF >> format.gLoss) << format.gShift); + const Uint32 bMask = ((0xFF >> format.bLoss) << format.bShift); + const Uint32 aMask = ((0xFF >> format.aLoss) << format.aShift); + _screen = SDL_CreateRGBSurface(SDL_SWSURFACE, _videoMode.screenWidth, _videoMode.screenHeight, + _screenFormat.bytesPerPixel * 8, rMask, gMask, bMask, aMask); + if (_screen == NULL) + error("allocating _screen failed"); + +#ifdef USE_RGB_COLOR + // Avoid having SDL_SRCALPHA set even if we supplied an alpha-channel in the format. + SDL_SetAlpha(_screen, 0, 255); +#endif + + // SDL 1.2 palettes default to all black, + // SDL 1.3 palettes default to all white, + // Thus set our own default palette to all black. + // SDL_SetColors does nothing for non indexed surfaces. + SDL_SetColors(_screen, _currentPalette, 0, 256); + + // + // Create the surface that contains the scaled graphics in 16 bit mode + // + + if (_videoMode.fullscreen) { + fixupResolutionForAspectRatio(_videoMode.desiredAspectRatio, _videoMode.hardwareWidth, _videoMode.hardwareHeight); + } + + +#ifdef ENABLE_EVENTRECORDER + _displayDisabled = ConfMan.getBool("disable_display"); + + if (_displayDisabled) { + _hwScreen = g_eventRec.getSurface(_videoMode.hardwareWidth, _videoMode.hardwareHeight); + } else +#endif + { +#if defined(WIN32) && !SDL_VERSION_ATLEAST(2, 0, 0) + // Save the original bpp to be able to restore the video mode on + // unload. See _originalBitsPerPixel documentation. + if (_originalBitsPerPixel == 0) { + const SDL_VideoInfo *videoInfo = SDL_GetVideoInfo(); + _originalBitsPerPixel = videoInfo->vfmt->BitsPerPixel; + } +#endif + + _hwScreen = SDL_SetVideoMode(_videoMode.hardwareWidth, _videoMode.hardwareHeight, 16, + _videoMode.fullscreen ? (SDL_FULLSCREEN|SDL_SWSURFACE) : SDL_SWSURFACE + ); + } + +#ifdef USE_RGB_COLOR + detectSupportedFormats(); +#endif + + if (_hwScreen == NULL) { + // DON'T use error(), as this tries to bring up the debug + // console, which WON'T WORK now that _hwScreen is hosed. + + if (!_oldVideoMode.setup) { + warning("SDL_SetVideoMode says we can't switch to that mode (%s)", SDL_GetError()); + g_system->quit(); + } else { + return false; + } + } + +#if !SDL_VERSION_ATLEAST(2, 0, 0) + handleResize(_videoMode.hardwareWidth, _videoMode.hardwareHeight, 90, 90); +#endif + + // + // Create the surface used for the graphics in 16 bit before scaling, and also the overlay + // + + // Need some extra bytes around when using 2xSaI + _tmpscreen = SDL_CreateRGBSurface(SDL_SWSURFACE, _videoMode.screenWidth + 3, _videoMode.screenHeight + 3, + 16, + _hwScreen->format->Rmask, + _hwScreen->format->Gmask, + _hwScreen->format->Bmask, + _hwScreen->format->Amask); + + if (_tmpscreen == NULL) + error("allocating _tmpscreen failed"); + + _overlayscreen = SDL_CreateRGBSurface(SDL_SWSURFACE, _videoMode.overlayWidth, _videoMode.overlayHeight, + 16, + _hwScreen->format->Rmask, + _hwScreen->format->Gmask, + _hwScreen->format->Bmask, + _hwScreen->format->Amask); + + if (_overlayscreen == NULL) + error("allocating _overlayscreen failed"); + + _overlayFormat = convertSDLPixelFormat(_overlayscreen->format); + + _tmpscreen2 = SDL_CreateRGBSurface(SDL_SWSURFACE, _videoMode.overlayWidth + 3, _videoMode.overlayHeight + 3, + 16, + _hwScreen->format->Rmask, + _hwScreen->format->Gmask, + _hwScreen->format->Bmask, + _hwScreen->format->Amask); + + if (_tmpscreen2 == NULL) + error("allocating _tmpscreen2 failed"); + + // Distinguish 555 and 565 mode + if (_hwScreen->format->Gmask == 0x3E0) + InitScalers(555); + else + InitScalers(565); + + return true; +} + +void SurfaceSdlGraphicsManager::unloadGFXMode() { + if (_screen) { + SDL_FreeSurface(_screen); + _screen = NULL; + } + +#if SDL_VERSION_ATLEAST(2, 0, 0) + deinitializeRenderer(); +#endif + + if (_hwScreen) { + SDL_FreeSurface(_hwScreen); + _hwScreen = NULL; + } + + if (_tmpscreen) { + SDL_FreeSurface(_tmpscreen); + _tmpscreen = NULL; + } + + if (_tmpscreen2) { + SDL_FreeSurface(_tmpscreen2); + _tmpscreen2 = NULL; + } + + if (_overlayscreen) { + SDL_FreeSurface(_overlayscreen); + _overlayscreen = NULL; + } + +#ifdef USE_OSD + if (_osdMessageSurface) { + SDL_FreeSurface(_osdMessageSurface); + _osdMessageSurface = NULL; + } + + if (_osdIconSurface) { + SDL_FreeSurface(_osdIconSurface); + _osdIconSurface = NULL; + } +#endif + DestroyScalers(); + +#if defined(WIN32) && !SDL_VERSION_ATLEAST(2, 0, 0) + // Reset video mode to original. + // This will ensure that any new graphic manager will use the initial BPP + // when listing available modes. See _originalBitsPerPixel documentation. + if (_originalBitsPerPixel != 0) + SDL_SetVideoMode(_videoMode.screenWidth, _videoMode.screenHeight, _originalBitsPerPixel, _videoMode.fullscreen ? (SDL_FULLSCREEN | SDL_SWSURFACE) : SDL_SWSURFACE); +#endif +} + +bool SurfaceSdlGraphicsManager::hotswapGFXMode() { + if (!_screen) + return false; + + // Keep around the old _screen & _overlayscreen so we can restore the screen data + // after the mode switch. + SDL_Surface *old_screen = _screen; + _screen = NULL; + SDL_Surface *old_overlayscreen = _overlayscreen; + _overlayscreen = NULL; + + // Release the HW screen surface + if (_hwScreen) { + SDL_FreeSurface(_hwScreen); + _hwScreen = NULL; + } + if (_tmpscreen) { + SDL_FreeSurface(_tmpscreen); + _tmpscreen = NULL; + } + if (_tmpscreen2) { + SDL_FreeSurface(_tmpscreen2); + _tmpscreen2 = NULL; + } + + // Setup the new GFX mode + if (!loadGFXMode()) { + unloadGFXMode(); + + _screen = old_screen; + _overlayscreen = old_overlayscreen; + + return false; + } + + // reset palette + SDL_SetColors(_screen, _currentPalette, 0, 256); + + // Restore old screen content + SDL_BlitSurface(old_screen, NULL, _screen, NULL); + SDL_BlitSurface(old_overlayscreen, NULL, _overlayscreen, NULL); + + // Free the old surfaces + SDL_FreeSurface(old_screen); + SDL_FreeSurface(old_overlayscreen); + + // Update cursor to new scale + blitCursor(); + + // Blit everything to the screen + internUpdateScreen(); + + return true; +} + +void SurfaceSdlGraphicsManager::updateScreen() { + assert(_transactionMode == kTransactionNone); + + Common::StackLock lock(_graphicsMutex); // Lock the mutex until this function ends + + internUpdateScreen(); +} + +void SurfaceSdlGraphicsManager::internUpdateScreen() { + SDL_Surface *srcSurf, *origSurf; + int height, width; + ScalerProc *scalerProc; + int scale1; + + // If there's an active debugger, update it + GUI::Debugger *debugger = g_engine ? g_engine->getDebugger() : nullptr; + if (debugger) + debugger->onFrame(); + +#if !SDL_VERSION_ATLEAST(2, 0, 0) + // If the shake position changed, fill the dirty area with blackness + // When building with SDL2, the shake offset is added to the active rect instead, + // so this isn't needed there. + if (_currentShakeXOffset != _gameScreenShakeXOffset || + (_cursorNeedsRedraw && _mouseBackup.x <= _currentShakeXOffset)) { + SDL_Rect blackrect = {0, 0, (Uint16)(_gameScreenShakeXOffset * _videoMode.scaleFactor), (Uint16)(_videoMode.screenHeight * _videoMode.scaleFactor)}; + + if (_videoMode.aspectRatioCorrection && !_overlayVisible) + blackrect.h = real2Aspect(blackrect.h - 1) + 1; + + SDL_FillRect(_hwScreen, &blackrect, 0); + + _currentShakeXOffset = _gameScreenShakeXOffset; + + _forceRedraw = true; + } + if (_currentShakeYOffset != _gameScreenShakeYOffset || + (_cursorNeedsRedraw && _mouseBackup.y <= _currentShakeYOffset)) { + SDL_Rect blackrect = {0, 0, (Uint16)(_videoMode.screenWidth * _videoMode.scaleFactor), (Uint16)(_gameScreenShakeYOffset * _videoMode.scaleFactor)}; + + if (_videoMode.aspectRatioCorrection && !_overlayVisible) + blackrect.h = real2Aspect(blackrect.h - 1) + 1; + + SDL_FillRect(_hwScreen, &blackrect, 0); + + _currentShakeYOffset = _gameScreenShakeYOffset; + + _forceRedraw = true; + } +#endif + + // Check whether the palette was changed in the meantime and update the + // screen surface accordingly. + if (_screen && _paletteDirtyEnd != 0) { + SDL_SetColors(_screen, _currentPalette + _paletteDirtyStart, + _paletteDirtyStart, + _paletteDirtyEnd - _paletteDirtyStart); + + _paletteDirtyEnd = 0; + + _forceRedraw = true; + } + + if (!_overlayVisible) { + origSurf = _screen; + srcSurf = _tmpscreen; + width = _videoMode.screenWidth; + height = _videoMode.screenHeight; + scalerProc = _scalerProc; + scale1 = _videoMode.scaleFactor; + } else { + origSurf = _overlayscreen; + srcSurf = _tmpscreen2; + width = _videoMode.overlayWidth; + height = _videoMode.overlayHeight; + scalerProc = Normal1x; + + scale1 = 1; + } + + // Add the area covered by the mouse cursor to the list of dirty rects if + // we have to redraw the mouse, or if the cursor is alpha-blended since + // alpha-blended cursors will happily blend into themselves if the surface + // under the cursor is not reset first + if (_cursorNeedsRedraw || _cursorFormat.bytesPerPixel == 4) + undrawMouse(); + +#ifdef USE_OSD + updateOSD(); +#endif + + // Force a full redraw if requested + if (_forceRedraw) { + _numDirtyRects = 1; + _dirtyRectList[0].x = 0; + _dirtyRectList[0].y = 0; + _dirtyRectList[0].w = width; + _dirtyRectList[0].h = height; + } + + // Only draw anything if necessary + if (_numDirtyRects > 0 || _cursorNeedsRedraw) { + SDL_Rect *r; + SDL_Rect dst; + uint32 srcPitch, dstPitch; + SDL_Rect *lastRect = _dirtyRectList + _numDirtyRects; + + for (r = _dirtyRectList; r != lastRect; ++r) { + dst = *r; + dst.x++; // Shift rect by one since 2xSai needs to access the data around + dst.y++; // any pixel to scale it, and we want to avoid mem access crashes. + + if (SDL_BlitSurface(origSurf, r, srcSurf, &dst) != 0) + error("SDL_BlitSurface failed: %s", SDL_GetError()); + } + + SDL_LockSurface(srcSurf); + SDL_LockSurface(_hwScreen); + + srcPitch = srcSurf->pitch; + dstPitch = _hwScreen->pitch; + + for (r = _dirtyRectList; r != lastRect; ++r) { + int dst_x = r->x + _currentShakeXOffset; + int dst_y = r->y + _currentShakeYOffset; + int dst_w = 0; + int dst_h = 0; +#ifdef USE_SCALERS + int orig_dst_y = 0; +#endif + + if (dst_x < width && dst_y < height) { + dst_w = r->w; + if (dst_w > width - dst_x) + dst_w = width - dst_x; + + dst_h = r->h; + if (dst_h > height - dst_y) + dst_h = height - dst_y; + +#ifdef USE_SCALERS + orig_dst_y = dst_y; +#endif + dst_x *= scale1; + dst_y *= scale1; + + if (_videoMode.aspectRatioCorrection && !_overlayVisible) + dst_y = real2Aspect(dst_y); + + assert(scalerProc != NULL); + scalerProc((byte *)srcSurf->pixels + (r->x * 2 + 2) + (r->y + 1) * srcPitch, srcPitch, + (byte *)_hwScreen->pixels + dst_x * 2 + dst_y * dstPitch, dstPitch, dst_w, dst_h); + } + + r->x = dst_x; + r->y = dst_y; + r->w = dst_w * scale1; + r->h = dst_h * scale1; + +#ifdef USE_SCALERS + if (_videoMode.aspectRatioCorrection && orig_dst_y < height && !_overlayVisible) + r->h = stretch200To240((uint8 *) _hwScreen->pixels, dstPitch, r->w, r->h, r->x, r->y, orig_dst_y * scale1, _videoMode.filtering); +#endif + } + SDL_UnlockSurface(srcSurf); + SDL_UnlockSurface(_hwScreen); + + // Readjust the dirty rect list in case we are doing a full update. + // This is necessary if shaking is active. + if (_forceRedraw) { + _dirtyRectList[0].x = 0; + _dirtyRectList[0].y = 0; + _dirtyRectList[0].w = _videoMode.hardwareWidth; + _dirtyRectList[0].h = _videoMode.hardwareHeight; + } + + drawMouse(); + +#ifdef USE_OSD + drawOSD(); +#endif + +#ifdef USE_SDL_DEBUG_FOCUSRECT + // We draw the focus rectangle on top of everything, to assure it's easily visible. + // Of course when the overlay is visible we do not show it, since it is only for game + // specific focus. + if (_enableFocusRect && !_overlayVisible) { + int x = _focusRect.left + _currentShakeXOffset; + int y = _focusRect.top + _currentShakeYOffset; + + if (x < width && y < height) { + int w = _focusRect.width(); + if (w > width - x) + w = width - x; + + int h = _focusRect.height(); + if (h > height - y) + h = height - y; + + x *= scale1; + y *= scale1; + w *= scale1; + h *= scale1; + + if (_videoMode.aspectRatioCorrection && !_overlayVisible) + y = real2Aspect(y); + + if (h > 0 && w > 0) { + SDL_LockSurface(_hwScreen); + + // Use white as color for now. + Uint32 rectColor = SDL_MapRGB(_hwScreen->format, 0xFF, 0xFF, 0xFF); + + // First draw the top and bottom lines + // then draw the left and right lines + if (_hwScreen->format->BytesPerPixel == 2) { + uint16 *top = (uint16 *)((byte *)_hwScreen->pixels + y * _hwScreen->pitch + x * 2); + uint16 *bottom = (uint16 *)((byte *)_hwScreen->pixels + (y + h) * _hwScreen->pitch + x * 2); + byte *left = ((byte *)_hwScreen->pixels + y * _hwScreen->pitch + x * 2); + byte *right = ((byte *)_hwScreen->pixels + y * _hwScreen->pitch + (x + w - 1) * 2); + + while (w--) { + *top++ = rectColor; + *bottom++ = rectColor; + } + + while (h--) { + *(uint16 *)left = rectColor; + *(uint16 *)right = rectColor; + + left += _hwScreen->pitch; + right += _hwScreen->pitch; + } + } else if (_hwScreen->format->BytesPerPixel == 4) { + uint32 *top = (uint32 *)((byte *)_hwScreen->pixels + y * _hwScreen->pitch + x * 4); + uint32 *bottom = (uint32 *)((byte *)_hwScreen->pixels + (y + h) * _hwScreen->pitch + x * 4); + byte *left = ((byte *)_hwScreen->pixels + y * _hwScreen->pitch + x * 4); + byte *right = ((byte *)_hwScreen->pixels + y * _hwScreen->pitch + (x + w - 1) * 4); + + while (w--) { + *top++ = rectColor; + *bottom++ = rectColor; + } + + while (h--) { + *(uint32 *)left = rectColor; + *(uint32 *)right = rectColor; + + left += _hwScreen->pitch; + right += _hwScreen->pitch; + } + } + + SDL_UnlockSurface(_hwScreen); + } + } + } +#endif + + // Finally, blit all our changes to the screen + if (!_displayDisabled) { + SDL_UpdateRects(_hwScreen, _numDirtyRects, _dirtyRectList); + } + } + + _numDirtyRects = 0; + _forceRedraw = false; + _cursorNeedsRedraw = false; +} + +bool SurfaceSdlGraphicsManager::saveScreenshot(const Common::String &filename) const { + assert(_hwScreen != NULL); + + Common::StackLock lock(_graphicsMutex); + + Common::DumpFile out; + if (!out.open(filename)) { + return false; + } + + int result = SDL_LockSurface(_hwScreen); + if (result < 0) { + warning("Could not lock RGB surface"); + return false; + } + + Graphics::PixelFormat format = convertSDLPixelFormat(_hwScreen->format); + Graphics::Surface data; + data.init(_hwScreen->w, _hwScreen->h, _hwScreen->pitch, _hwScreen->pixels, format); +#ifdef USE_PNG + const bool success = Image::writePNG(out, data); +#else + const bool success = Image::writeBMP(out, data); +#endif + + SDL_UnlockSurface(_hwScreen); + + return success; +} + +void SurfaceSdlGraphicsManager::setFullscreenMode(bool enable) { + Common::StackLock lock(_graphicsMutex); + + if (!g_system->hasFeature(OSystem::kFeatureFullscreenMode)) + return; + + if (_oldVideoMode.setup && _oldVideoMode.fullscreen == enable) + return; + + if (_transactionMode == kTransactionActive) { + _videoMode.fullscreen = enable; + _transactionDetails.needHotswap = true; + } +} + +void SurfaceSdlGraphicsManager::setAspectRatioCorrection(bool enable) { + Common::StackLock lock(_graphicsMutex); + + if (_oldVideoMode.setup && _oldVideoMode.aspectRatioCorrection == enable) + return; + + if (_transactionMode == kTransactionActive) { + _videoMode.aspectRatioCorrection = enable; + _transactionDetails.needHotswap = true; + } +} + +void SurfaceSdlGraphicsManager::setFilteringMode(bool enable) { + Common::StackLock lock(_graphicsMutex); + + if (_oldVideoMode.setup && _oldVideoMode.filtering == enable) + return; + + if (_transactionMode == kTransactionActive) { + _videoMode.filtering = enable; +#if SDL_VERSION_ATLEAST(2, 0, 0) + _transactionDetails.needTextureUpdate = true; +#endif + } +} + +void SurfaceSdlGraphicsManager::copyRectToScreen(const void *buf, int pitch, int x, int y, int w, int h) { + assert(_transactionMode == kTransactionNone); + assert(buf); + + if (_screen == NULL) { + warning("SurfaceSdlGraphicsManager::copyRectToScreen: _screen == NULL"); + return; + } + + Common::StackLock lock(_graphicsMutex); // Lock the mutex until this function ends + + assert(x >= 0 && x < _videoMode.screenWidth); + assert(y >= 0 && y < _videoMode.screenHeight); + assert(h > 0 && y + h <= _videoMode.screenHeight); + assert(w > 0 && x + w <= _videoMode.screenWidth); + + addDirtyRect(x, y, w, h); + + // Try to lock the screen surface + if (SDL_LockSurface(_screen) == -1) + error("SDL_LockSurface failed: %s", SDL_GetError()); + + byte *dst = (byte *)_screen->pixels + y * _screen->pitch + x * _screenFormat.bytesPerPixel; + if (_videoMode.screenWidth == w && pitch == _screen->pitch) { + memcpy(dst, buf, h*pitch); + } else { + const byte *src = (const byte *)buf; + do { + memcpy(dst, src, w * _screenFormat.bytesPerPixel); + src += pitch; + dst += _screen->pitch; + } while (--h); + } + + // Unlock the screen surface + SDL_UnlockSurface(_screen); +} + +Graphics::Surface *SurfaceSdlGraphicsManager::lockScreen() { + assert(_transactionMode == kTransactionNone); + + // Lock the graphics mutex + _graphicsMutex.lock(); + + // paranoia check + assert(!_screenIsLocked); + _screenIsLocked = true; + + // Try to lock the screen surface + if (SDL_LockSurface(_screen) == -1) + error("SDL_LockSurface failed: %s", SDL_GetError()); + + _framebuffer.init(_screen->w, _screen->h, _screen->pitch, _screen->pixels, _screenFormat); + + return &_framebuffer; +} + +void SurfaceSdlGraphicsManager::unlockScreen() { + assert(_transactionMode == kTransactionNone); + + // paranoia check + assert(_screenIsLocked); + _screenIsLocked = false; + + // Unlock the screen surface + SDL_UnlockSurface(_screen); + + // Trigger a full screen update + _forceRedraw = true; + + // Finally unlock the graphics mutex + _graphicsMutex.unlock(); +} + +void SurfaceSdlGraphicsManager::fillScreen(uint32 col) { + Graphics::Surface *screen = lockScreen(); + if (screen) + screen->fillRect(Common::Rect(screen->w, screen->h), col); + unlockScreen(); +} + +void SurfaceSdlGraphicsManager::addDirtyRect(int x, int y, int w, int h, bool realCoordinates) { + if (_forceRedraw) + return; + + if (_numDirtyRects == NUM_DIRTY_RECT) { + _forceRedraw = true; + return; + } + + int height, width; + + if (!_overlayVisible && !realCoordinates) { + width = _videoMode.screenWidth; + height = _videoMode.screenHeight; + } else { + width = _videoMode.overlayWidth; + height = _videoMode.overlayHeight; + } + + // Extend the dirty region by 1 pixel for scalers + // that "smear" the screen, e.g. 2xSAI + if (!realCoordinates) { + x--; + y--; + w += 2; + h += 2; + } + + // clip + if (x < 0) { + w += x; + x = 0; + } + + if (y < 0) { + h += y; + y = 0; + } + + if (w > width - x) { + w = width - x; + } + + if (h > height - y) { + h = height - y; + } + +#ifdef USE_SCALERS + if (_videoMode.aspectRatioCorrection && !_overlayVisible && !realCoordinates) + makeRectStretchable(x, y, w, h, _videoMode.filtering); +#endif + + if (w == width && h == height) { + _forceRedraw = true; + return; + } + + if (w > 0 && h > 0) { + SDL_Rect *r = &_dirtyRectList[_numDirtyRects++]; + + r->x = x; + r->y = y; + r->w = w; + r->h = h; + } +} + +int16 SurfaceSdlGraphicsManager::getHeight() const { + return _videoMode.screenHeight; +} + +int16 SurfaceSdlGraphicsManager::getWidth() const { + return _videoMode.screenWidth; +} + +void SurfaceSdlGraphicsManager::setPalette(const byte *colors, uint start, uint num) { + assert(colors); + assert(_screenFormat.bytesPerPixel == 1); + + // Setting the palette before _screen is created is allowed - for now - + // since we don't actually set the palette until the screen is updated. + // But it could indicate a programming error, so let's warn about it. + + if (!_screen) + warning("SurfaceSdlGraphicsManager::setPalette: _screen == NULL"); + + const byte *b = colors; + uint i; + SDL_Color *base = _currentPalette + start; + for (i = 0; i < num; i++, b += 3) { + base[i].r = b[0]; + base[i].g = b[1]; + base[i].b = b[2]; +#if SDL_VERSION_ATLEAST(2, 0, 0) + base[i].a = 255; +#endif + } + + if (start < _paletteDirtyStart) + _paletteDirtyStart = start; + + if (start + num > _paletteDirtyEnd) + _paletteDirtyEnd = start + num; + + // Some games blink cursors with palette + if (_cursorPaletteDisabled) + blitCursor(); +} + +void SurfaceSdlGraphicsManager::grabPalette(byte *colors, uint start, uint num) const { + assert(colors); + assert(_screenFormat.bytesPerPixel == 1); + + const SDL_Color *base = _currentPalette + start; + + for (uint i = 0; i < num; ++i) { + colors[i * 3] = base[i].r; + colors[i * 3 + 1] = base[i].g; + colors[i * 3 + 2] = base[i].b; + } +} + +void SurfaceSdlGraphicsManager::setCursorPalette(const byte *colors, uint start, uint num) { + assert(colors); + const byte *b = colors; + uint i; + SDL_Color *base = _cursorPalette + start; + for (i = 0; i < num; i++, b += 3) { + base[i].r = b[0]; + base[i].g = b[1]; + base[i].b = b[2]; +#if SDL_VERSION_ATLEAST(2, 0, 0) + base[i].a = 255; +#endif + } + + _cursorPaletteDisabled = false; + blitCursor(); +} + +void SurfaceSdlGraphicsManager::setFocusRectangle(const Common::Rect &rect) { +#ifdef USE_SDL_DEBUG_FOCUSRECT + // Only enable focus rectangle debug code, when the user wants it + if (!_enableFocusRectDebugCode) + return; + + _enableFocusRect = true; + _focusRect = rect; + + if (rect.left < 0 || rect.top < 0 || rect.right > _videoMode.screenWidth || rect.bottom > _videoMode.screenHeight) + warning("SurfaceSdlGraphicsManager::setFocusRectangle: Got a rect which does not fit inside the screen bounds: %d,%d,%d,%d", rect.left, rect.top, rect.right, rect.bottom); + + // It's gross but we actually sometimes get rects, which are not inside the screen bounds, + // thus we need to clip the rect here... + _focusRect.clip(_videoMode.screenWidth, _videoMode.screenHeight); + + // We just fake this as a dirty rect for now, to easily force an screen update whenever + // the rect changes. + addDirtyRect(_focusRect.left, _focusRect.top, _focusRect.width(), _focusRect.height()); +#endif +} + +void SurfaceSdlGraphicsManager::clearFocusRectangle() { +#ifdef USE_SDL_DEBUG_FOCUSRECT + // Only enable focus rectangle debug code, when the user wants it + if (!_enableFocusRectDebugCode) + return; + + _enableFocusRect = false; + + // We just fake this as a dirty rect for now, to easily force an screen update whenever + // the rect changes. + addDirtyRect(_focusRect.left, _focusRect.top, _focusRect.width(), _focusRect.height()); +#endif +} + +#pragma mark - +#pragma mark --- Overlays --- +#pragma mark - + +void SurfaceSdlGraphicsManager::clearOverlay() { + Common::StackLock lock(_graphicsMutex); // Lock the mutex until this function ends + + if (!_overlayVisible) + return; + + // Clear the overlay by making the game screen "look through" everywhere. + SDL_Rect src, dst; + src.x = src.y = 0; + dst.x = dst.y = 1; + src.w = dst.w = _videoMode.screenWidth; + src.h = dst.h = _videoMode.screenHeight; + if (SDL_BlitSurface(_screen, &src, _tmpscreen, &dst) != 0) + error("SDL_BlitSurface failed: %s", SDL_GetError()); + + SDL_LockSurface(_tmpscreen); + SDL_LockSurface(_overlayscreen); + _scalerProc((byte *)(_tmpscreen->pixels) + _tmpscreen->pitch + 2, _tmpscreen->pitch, + (byte *)_overlayscreen->pixels, _overlayscreen->pitch, _videoMode.screenWidth, _videoMode.screenHeight); + +#ifdef USE_SCALERS + if (_videoMode.aspectRatioCorrection) + stretch200To240((uint8 *)_overlayscreen->pixels, _overlayscreen->pitch, + _videoMode.overlayWidth, _videoMode.screenHeight * _videoMode.scaleFactor, 0, 0, 0, + _videoMode.filtering); +#endif + SDL_UnlockSurface(_tmpscreen); + SDL_UnlockSurface(_overlayscreen); + + _forceRedraw = true; +} + +void SurfaceSdlGraphicsManager::grabOverlay(void *buf, int pitch) const { + assert(_transactionMode == kTransactionNone); + + if (_overlayscreen == NULL) + return; + + if (SDL_LockSurface(_overlayscreen) == -1) + error("SDL_LockSurface failed: %s", SDL_GetError()); + + byte *src = (byte *)_overlayscreen->pixels; + byte *dst = (byte *)buf; + int h = _videoMode.overlayHeight; + do { + memcpy(dst, src, _videoMode.overlayWidth * 2); + src += _overlayscreen->pitch; + dst += pitch; + } while (--h); + + SDL_UnlockSurface(_overlayscreen); +} + +void SurfaceSdlGraphicsManager::copyRectToOverlay(const void *buf, int pitch, int x, int y, int w, int h) { + assert(_transactionMode == kTransactionNone); + + if (_overlayscreen == NULL) + return; + + const byte *src = (const byte *)buf; + + // Clip the coordinates + if (x < 0) { + w += x; + src -= x * 2; + x = 0; + } + + if (y < 0) { + h += y; + src -= y * pitch; + y = 0; + } + + if (w > _videoMode.overlayWidth - x) { + w = _videoMode.overlayWidth - x; + } + + if (h > _videoMode.overlayHeight - y) { + h = _videoMode.overlayHeight - y; + } + + if (w <= 0 || h <= 0) + return; + + // Mark the modified region as dirty + addDirtyRect(x, y, w, h); + + if (SDL_LockSurface(_overlayscreen) == -1) + error("SDL_LockSurface failed: %s", SDL_GetError()); + + byte *dst = (byte *)_overlayscreen->pixels + y * _overlayscreen->pitch + x * 2; + do { + memcpy(dst, src, w * 2); + dst += _overlayscreen->pitch; + src += pitch; + } while (--h); + + SDL_UnlockSurface(_overlayscreen); +} + + +#pragma mark - +#pragma mark --- Mouse --- +#pragma mark - + +void SurfaceSdlGraphicsManager::setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keyColor, bool dontScale, const Graphics::PixelFormat *format) { + bool formatChanged = false; + + if (format) { +#ifndef USE_RGB_COLOR + assert(format->bytesPerPixel == 1); +#endif + if (format->bytesPerPixel != _cursorFormat.bytesPerPixel) { + formatChanged = true; + } + _cursorFormat = *format; + } else { + if (_cursorFormat.bytesPerPixel != 1) { + formatChanged = true; + } + _cursorFormat = Graphics::PixelFormat::createFormatCLUT8(); + } + + if (_cursorFormat.bytesPerPixel < 4) { + assert(keyColor < 1U << (_cursorFormat.bytesPerPixel * 8)); + } + + _mouseCurState.hotX = hotspotX; + _mouseCurState.hotY = hotspotY; + + _mouseKeyColor = keyColor; + + _cursorDontScale = dontScale; + + if (_mouseCurState.w != (int)w || _mouseCurState.h != (int)h || formatChanged) { + _mouseCurState.w = w; + _mouseCurState.h = h; + + if (_mouseOrigSurface) { + SDL_FreeSurface(_mouseOrigSurface); + + if (_mouseSurface == _mouseOrigSurface) { + _mouseSurface = nullptr; + } + + _mouseOrigSurface = nullptr; + } + + if ((formatChanged || _cursorFormat.bytesPerPixel == 4) && _mouseSurface) { + SDL_FreeSurface(_mouseSurface); + _mouseSurface = nullptr; + } + + if (!w || !h) { + return; + } + + if (_cursorFormat.bytesPerPixel == 4) { + assert(!_mouseSurface); + assert(!_mouseOrigSurface); + + const Uint32 rMask = ((0xFF >> format->rLoss) << format->rShift); + const Uint32 gMask = ((0xFF >> format->gLoss) << format->gShift); + const Uint32 bMask = ((0xFF >> format->bLoss) << format->bShift); + const Uint32 aMask = ((0xFF >> format->aLoss) << format->aShift); + _mouseSurface = _mouseOrigSurface = SDL_CreateRGBSurfaceFrom(const_cast(buf), w, h, format->bytesPerPixel * 8, w * format->bytesPerPixel, rMask, gMask, bMask, aMask); + } else { + assert(!_mouseOrigSurface); + + // Allocate bigger surface because AdvMame2x adds black pixel at [0,0] + _mouseOrigSurface = SDL_CreateRGBSurface(SDL_SWSURFACE | SDL_RLEACCEL | SDL_SRCCOLORKEY | SDL_SRCALPHA, + _mouseCurState.w + 2, + _mouseCurState.h + 2, + 16, + _hwScreen->format->Rmask, + _hwScreen->format->Gmask, + _hwScreen->format->Bmask, + _hwScreen->format->Amask); + } + + if (_mouseOrigSurface == nullptr) { + error("Allocating _mouseOrigSurface failed"); + } + + if (_cursorFormat.bytesPerPixel == 4) { + SDL_SetColorKey(_mouseOrigSurface, SDL_SRCCOLORKEY | SDL_SRCALPHA, _mouseKeyColor); + } else { + SDL_SetColorKey(_mouseOrigSurface, SDL_RLEACCEL | SDL_SRCCOLORKEY | SDL_SRCALPHA, kMouseColorKey); + } + } + + delete[] _mouseData; + if (_cursorFormat.bytesPerPixel == 4) { + _mouseData = nullptr; + } else { + _mouseData = new byte[w * h * _cursorFormat.bytesPerPixel]; + assert(_mouseData); + memcpy(_mouseData, buf, w * h * _cursorFormat.bytesPerPixel); + } + + blitCursor(); +} + +void SurfaceSdlGraphicsManager::blitCursor() { + const int w = _mouseCurState.w; + const int h = _mouseCurState.h; + + if (!w || !h || !_mouseOrigSurface) { + return; + } + + if (_cursorFormat.bytesPerPixel != 4 && !_mouseData) { + return; + } + + _cursorNeedsRedraw = true; + + int cursorScale; + if (_cursorDontScale) { + // Don't scale the cursor at all if the user requests this behavior. + cursorScale = 1; + } else { + // Scale the cursor with the game screen scale factor. + cursorScale = _videoMode.scaleFactor; + } + + // Adapt the real hotspot according to the scale factor. + int rW = w * cursorScale; + int rH = h * cursorScale; + _mouseCurState.rHotX = _mouseCurState.hotX * cursorScale; + _mouseCurState.rHotY = _mouseCurState.hotY * cursorScale; + + // The virtual dimensions will be the same as the original. + + _mouseCurState.vW = w; + _mouseCurState.vH = h; + _mouseCurState.vHotX = _mouseCurState.hotX; + _mouseCurState.vHotY = _mouseCurState.hotY; + +#ifdef USE_SCALERS + // store original to pass to aspect-correction function later + const int rH1 = rH; +#endif + + if (!_cursorDontScale && _videoMode.aspectRatioCorrection) { + rH = real2Aspect(rH - 1) + 1; + _mouseCurState.rHotY = real2Aspect(_mouseCurState.rHotY); + } + + bool sizeChanged = false; + if (_mouseCurState.rW != rW || _mouseCurState.rH != rH) { + _mouseCurState.rW = rW; + _mouseCurState.rH = rH; + sizeChanged = true; + } + + if (_cursorFormat.bytesPerPixel == 4) { + if (_mouseSurface != _mouseOrigSurface) { + SDL_FreeSurface(_mouseSurface); + } + + if (cursorScale == 1) { + _mouseSurface = _mouseOrigSurface; + return; + } + + SDL_PixelFormat *format = _mouseOrigSurface->format; + _mouseSurface = SDL_CreateRGBSurface(SDL_SWSURFACE | SDL_SRCCOLORKEY | SDL_SRCALPHA, + rW, rH, + format->BitsPerPixel, + format->Rmask, + format->Gmask, + format->Bmask, + format->Amask); + + SDL_SetColorKey(_mouseSurface, SDL_SRCCOLORKEY | SDL_SRCALPHA, _mouseKeyColor); + + // At least SDL 2.0.4 on Windows apparently has a broken SDL_BlitScaled + // implementation, and SDL 1 has no such API at all, and our other + // scalers operate exclusively at 16bpp, so here is a scrappy 32bpp + // point scaler + SDL_LockSurface(_mouseOrigSurface); + SDL_LockSurface(_mouseSurface); + + const byte *src = (const byte *)_mouseOrigSurface->pixels; + byte *dst = (byte *)_mouseSurface->pixels; + for (int y = 0; y < _mouseOrigSurface->h; ++y) { + uint32 *rowDst = (uint32 *)dst; + const uint32 *rowSrc = (const uint32 *)src; + for (int x = 0; x < _mouseOrigSurface->w; ++x) { + for (int scaleX = 0; scaleX < cursorScale; ++scaleX) { + *rowDst++ = *rowSrc; + } + ++rowSrc; + } + for (int scaleY = 0; scaleY < cursorScale - 1; ++scaleY) { + memcpy(dst + _mouseSurface->pitch, dst, _mouseSurface->pitch); + dst += _mouseSurface->pitch; + } + dst += _mouseSurface->pitch; + src += _mouseOrigSurface->pitch; + } + + SDL_UnlockSurface(_mouseSurface); + SDL_UnlockSurface(_mouseOrigSurface); + return; + } + + SDL_LockSurface(_mouseOrigSurface); + + byte *dstPtr; + const byte *srcPtr = _mouseData; + uint32 color; + + // Make whole surface transparent + for (int i = 0; i < h + 2; i++) { + dstPtr = (byte *)_mouseOrigSurface->pixels + _mouseOrigSurface->pitch * i; + for (int j = 0; j < w + 2; j++) { + *(uint16 *)dstPtr = kMouseColorKey; + dstPtr += 2; + } + } + + // Draw from [1,1] since AdvMame2x adds artefact at 0,0 + dstPtr = (byte *)_mouseOrigSurface->pixels + _mouseOrigSurface->pitch + 2; + + SDL_Color *palette; + + if (_cursorPaletteDisabled) + palette = _currentPalette; + else + palette = _cursorPalette; + + for (int i = 0; i < h; i++) { + for (int j = 0; j < w; j++) { + if (_cursorFormat.bytesPerPixel == 2) { + color = *(const uint16 *)srcPtr; + if (color != _mouseKeyColor) { + uint8 r, g, b; + _cursorFormat.colorToRGB(color, r, g, b); + *(uint16 *)dstPtr = SDL_MapRGB(_mouseOrigSurface->format, r, g, b); + } + dstPtr += 2; + srcPtr += _cursorFormat.bytesPerPixel; + } else { + color = *srcPtr; + if (color != _mouseKeyColor) { + *(uint16 *)dstPtr = SDL_MapRGB(_mouseOrigSurface->format, + palette[color].r, palette[color].g, palette[color].b); + } + dstPtr += 2; + srcPtr++; + } + } + dstPtr += _mouseOrigSurface->pitch - w * 2; + } + + if (sizeChanged || !_mouseSurface) { + if (_mouseSurface) + SDL_FreeSurface(_mouseSurface); + + _mouseSurface = SDL_CreateRGBSurface(SDL_SWSURFACE | SDL_RLEACCEL | SDL_SRCCOLORKEY | SDL_SRCALPHA, + _mouseCurState.rW, + _mouseCurState.rH, + 16, + _hwScreen->format->Rmask, + _hwScreen->format->Gmask, + _hwScreen->format->Bmask, + _hwScreen->format->Amask); + + if (_mouseSurface == nullptr) + error("Allocating _mouseSurface failed"); + + SDL_SetColorKey(_mouseSurface, SDL_RLEACCEL | SDL_SRCCOLORKEY | SDL_SRCALPHA, kMouseColorKey); + } + + SDL_LockSurface(_mouseSurface); + + ScalerProc *scalerProc; + + // Only apply scaling, when the user allows it. + if (!_cursorDontScale) { + // If possible, use the same scaler for the cursor as for the rest of + // the game. This only works well with the non-blurring scalers so we + // actually only use the 1x, 2x and AdvMame scalers. + if (_videoMode.mode == GFX_DOUBLESIZE || _videoMode.mode == GFX_TRIPLESIZE) + scalerProc = _scalerProc; + else + scalerProc = scalersMagn[_videoMode.scaleFactor - 1]; + } else { + scalerProc = Normal1x; + } + + scalerProc((byte *)_mouseOrigSurface->pixels + _mouseOrigSurface->pitch + 2, + _mouseOrigSurface->pitch, (byte *)_mouseSurface->pixels, _mouseSurface->pitch, + _mouseCurState.w, _mouseCurState.h); + +#ifdef USE_SCALERS + if (!_cursorDontScale && _videoMode.aspectRatioCorrection) + stretch200To240Nearest((uint8 *)_mouseSurface->pixels, _mouseSurface->pitch, rW, rH1, 0, 0, 0); +#endif + + SDL_UnlockSurface(_mouseSurface); + SDL_UnlockSurface(_mouseOrigSurface); +} + +void SurfaceSdlGraphicsManager::undrawMouse() { + const int x = _mouseBackup.x; + const int y = _mouseBackup.y; + + // When we switch bigger overlay off mouse jumps. Argh! + // This is intended to prevent undrawing offscreen mouse + if (!_overlayVisible && (x >= _videoMode.screenWidth || y >= _videoMode.screenHeight)) + return; + + if (_mouseBackup.w != 0 && _mouseBackup.h != 0) + addDirtyRect(x, y, _mouseBackup.w, _mouseBackup.h); +} + +void SurfaceSdlGraphicsManager::drawMouse() { + if (!_cursorVisible || !_mouseSurface || !_mouseCurState.w || !_mouseCurState.h) { + _mouseBackup.x = _mouseBackup.y = _mouseBackup.w = _mouseBackup.h = 0; + return; + } + + SDL_Rect dst; + int scale; + int hotX, hotY; + + const Common::Point virtualCursor = convertWindowToVirtual(_cursorX, _cursorY); + + dst.x = virtualCursor.x; + dst.y = virtualCursor.y; + + if (!_overlayVisible) { + scale = _videoMode.scaleFactor; + dst.w = _mouseCurState.vW; + dst.h = _mouseCurState.vH; + hotX = _mouseCurState.vHotX; + hotY = _mouseCurState.vHotY; + } else { + scale = 1; + dst.w = _mouseCurState.rW; + dst.h = _mouseCurState.rH; + hotX = _mouseCurState.rHotX; + hotY = _mouseCurState.rHotY; + } + + // The mouse is undrawn using virtual coordinates, i.e. they may be + // scaled and aspect-ratio corrected. + + _mouseBackup.x = dst.x - hotX; + _mouseBackup.y = dst.y - hotY; + _mouseBackup.w = dst.w; + _mouseBackup.h = dst.h; + + // We draw the pre-scaled cursor image, so now we need to adjust for + // scaling, shake position and aspect ratio correction manually. + + dst.x += _currentShakeXOffset; + dst.y += _currentShakeYOffset; + + if (_videoMode.aspectRatioCorrection && !_overlayVisible) + dst.y = real2Aspect(dst.y); + + dst.x = scale * dst.x - _mouseCurState.rHotX; + dst.y = scale * dst.y - _mouseCurState.rHotY; + dst.w = _mouseCurState.rW; + dst.h = _mouseCurState.rH; + + // Note that SDL_BlitSurface() and addDirtyRect() will both perform any + // clipping necessary + + if (SDL_BlitSurface(_mouseSurface, nullptr, _hwScreen, &dst) != 0) + error("SDL_BlitSurface failed: %s", SDL_GetError()); + + // The screen will be updated using real surface coordinates, i.e. + // they will not be scaled or aspect-ratio corrected. + + addDirtyRect(dst.x, dst.y, dst.w, dst.h, true); +} + +#pragma mark - +#pragma mark --- On Screen Display --- +#pragma mark - + +#ifdef USE_OSD +void SurfaceSdlGraphicsManager::displayMessageOnOSD(const Common::U32String &msg) { + assert(_transactionMode == kTransactionNone); + assert(!msg.empty()); +#ifdef USE_TTS + Common::U32String textToSay = msg; +#endif // USE_TTS + + Common::StackLock lock(_graphicsMutex); // Lock the mutex until this function ends + + removeOSDMessage(); + + // The font we are going to use: + const Graphics::Font *font = FontMan.getFontByUsage(Graphics::FontManager::kLocalizedFont); + + // Split the message into separate lines. + Common::Array lines; + Common::U32String::const_iterator strLineItrBegin = msg.begin(); + + for (Common::U32String::const_iterator itr = msg.begin(); itr != msg.end(); itr++) { + if (*itr == '\n') { + lines.push_back(Common::U32String(strLineItrBegin, itr)); + strLineItrBegin = itr + 1; + } + } + if (strLineItrBegin != msg.end()) + lines.push_back(Common::U32String(strLineItrBegin, msg.end())); + + // Determine a rect which would contain the message string (clipped to the + // screen dimensions). + const int vOffset = 6; + const int lineSpacing = 1; + const int lineHeight = font->getFontHeight() + 2 * lineSpacing; + int width = 0; + int height = lineHeight * lines.size() + 2 * vOffset; + uint i; + for (i = 0; i < lines.size(); i++) { + width = MAX(width, font->getStringWidth(lines[i]) + 14); + } + + // Clip the rect + if (width > _hwScreen->w) + width = _hwScreen->w; + if (height > _hwScreen->h) + height = _hwScreen->h; + + _osdMessageSurface = SDL_CreateRGBSurface( + SDL_SWSURFACE | SDL_RLEACCEL | SDL_SRCALPHA, + width, height, 16, _hwScreen->format->Rmask, _hwScreen->format->Gmask, _hwScreen->format->Bmask, _hwScreen->format->Amask + ); + + // Lock the surface + if (SDL_LockSurface(_osdMessageSurface)) + error("displayMessageOnOSD: SDL_LockSurface failed: %s", SDL_GetError()); + + // Draw a dark gray rect + // TODO: Rounded corners ? Border? + SDL_FillRect(_osdMessageSurface, nullptr, SDL_MapRGB(_osdMessageSurface->format, 64, 64, 64)); + + Graphics::Surface dst; + dst.init(_osdMessageSurface->w, _osdMessageSurface->h, _osdMessageSurface->pitch, _osdMessageSurface->pixels, + convertSDLPixelFormat(_osdMessageSurface->format)); + + // Render the message, centered, and in white + for (i = 0; i < lines.size(); i++) { + font->drawString(&dst, lines[i], + 0, 0 + i * lineHeight + vOffset + lineSpacing, width, + SDL_MapRGB(_osdMessageSurface->format, 255, 255, 255), + Graphics::kTextAlignCenter); + } + + // Finished drawing, so unlock the OSD message surface + SDL_UnlockSurface(_osdMessageSurface); + + // Init the OSD display parameters, and the fade out + _osdMessageAlpha = SDL_ALPHA_TRANSPARENT + kOSDInitialAlpha * (SDL_ALPHA_OPAQUE - SDL_ALPHA_TRANSPARENT) / 100; + _osdMessageFadeStartTime = SDL_GetTicks() + kOSDFadeOutDelay; + // Enable alpha blending + SDL_SetAlpha(_osdMessageSurface, SDL_RLEACCEL | SDL_SRCALPHA, _osdMessageAlpha); + + // Ensure a full redraw takes place next time the screen is updated + _forceRedraw = true; +#ifdef USE_TTS + if (ConfMan.hasKey("tts_enabled", "scummvm") && + ConfMan.getBool("tts_enabled", "scummvm")) { + Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager(); + if (ttsMan) + ttsMan->say(textToSay); + } +#endif // USE_TTS +} + +SDL_Rect SurfaceSdlGraphicsManager::getOSDMessageRect() const { + SDL_Rect rect; + rect.x = (_hwScreen->w - _osdMessageSurface->w) / 2; + rect.y = (_hwScreen->h - _osdMessageSurface->h) / 2; + rect.w = _osdMessageSurface->w; + rect.h = _osdMessageSurface->h; + return rect; +} + +void SurfaceSdlGraphicsManager::displayActivityIconOnOSD(const Graphics::Surface *icon) { + assert(_transactionMode == kTransactionNone); + + Common::StackLock lock(_graphicsMutex); // Lock the mutex until this function ends + + if (_osdIconSurface && !icon) { + // Force a redraw to clear the icon on the next update + _forceRedraw = true; + } + + if (_osdIconSurface) { + SDL_FreeSurface(_osdIconSurface); + _osdIconSurface = nullptr; + } + + if (icon) { + const Graphics::PixelFormat &iconFormat = icon->format; + + _osdIconSurface = SDL_CreateRGBSurface( + SDL_SWSURFACE | SDL_RLEACCEL | SDL_SRCALPHA, + icon->w, icon->h, iconFormat.bytesPerPixel * 8, + ((0xFF >> iconFormat.rLoss) << iconFormat.rShift), + ((0xFF >> iconFormat.gLoss) << iconFormat.gShift), + ((0xFF >> iconFormat.bLoss) << iconFormat.bShift), + ((0xFF >> iconFormat.aLoss) << iconFormat.aShift) + ); + + // Lock the surface + if (SDL_LockSurface(_osdIconSurface)) + error("displayActivityIconOnOSD: SDL_LockSurface failed: %s", SDL_GetError()); + + byte *dst = (byte *) _osdIconSurface->pixels; + const byte *src = (const byte *) icon->getPixels(); + for (uint y = 0; y < icon->h; y++) { + memcpy(dst, src, icon->w * iconFormat.bytesPerPixel); + src += icon->pitch; + dst += _osdIconSurface->pitch; + } + + // Finished drawing, so unlock the OSD icon surface + SDL_UnlockSurface(_osdIconSurface); + } +} + +SDL_Rect SurfaceSdlGraphicsManager::getOSDIconRect() const { + SDL_Rect dstRect; + dstRect.x = _hwScreen->w - _osdIconSurface->w - 10; + dstRect.y = 10; + dstRect.w = _osdIconSurface->w; + dstRect.h = _osdIconSurface->h; + return dstRect; +} + +void SurfaceSdlGraphicsManager::removeOSDMessage() { + // Remove the previous message + if (_osdMessageSurface) { + SDL_FreeSurface(_osdMessageSurface); + _forceRedraw = true; + } + + _osdMessageSurface = NULL; + _osdMessageAlpha = SDL_ALPHA_TRANSPARENT; +} + +void SurfaceSdlGraphicsManager::updateOSD() { + // OSD message visible (i.e. non-transparent)? + if (_osdMessageAlpha != SDL_ALPHA_TRANSPARENT) { + // Updated alpha value + const int diff = SDL_GetTicks() - _osdMessageFadeStartTime; + if (diff > 0) { + if (diff >= kOSDFadeOutDuration) { + // Back to full transparency + _osdMessageAlpha = SDL_ALPHA_TRANSPARENT; + } else { + // Do a linear fade out... + const int startAlpha = SDL_ALPHA_TRANSPARENT + kOSDInitialAlpha * (SDL_ALPHA_OPAQUE - SDL_ALPHA_TRANSPARENT) / 100; + _osdMessageAlpha = startAlpha + diff * (SDL_ALPHA_TRANSPARENT - startAlpha) / kOSDFadeOutDuration; + } + SDL_SetAlpha(_osdMessageSurface, SDL_RLEACCEL | SDL_SRCALPHA, _osdMessageAlpha); + } + + if (_osdMessageAlpha == SDL_ALPHA_TRANSPARENT) { + removeOSDMessage(); + } + } + + if (_osdIconSurface || _osdMessageSurface) { + // Redraw the area below the icon and message for the transparent blit to give correct results. + _forceRedraw = true; + } +} + +void SurfaceSdlGraphicsManager::drawOSD() { + if (_osdMessageSurface) { + SDL_Rect dstRect = getOSDMessageRect(); + SDL_BlitSurface(_osdMessageSurface, 0, _hwScreen, &dstRect); + } + + if (_osdIconSurface) { + SDL_Rect dstRect = getOSDIconRect(); + SDL_BlitSurface(_osdIconSurface, 0, _hwScreen, &dstRect); + } +} + +#endif + +void SurfaceSdlGraphicsManager::handleResizeImpl(const int width, const int height, const int xdpi, const int ydpi) { + SdlGraphicsManager::handleResizeImpl(width, height, xdpi, ydpi); + recalculateDisplayAreas(); +} + +void SurfaceSdlGraphicsManager::handleScalerHotkeys(int scalefactor, int scalerType) { + assert(scalerType >= 0 && scalerType < ARRAYSIZE(s_gfxModeSwitchTable)); + + int factor = CLIP(scalefactor - 1, 0, 4); + + while (s_gfxModeSwitchTable[scalerType][factor] < 0) { + assert(factor > 0); + factor--; + } + +#if SDL_VERSION_ATLEAST(2, 0, 0) + bool sizeChanged = _videoMode.scaleFactor != factor; +#endif + + int newMode = s_gfxModeSwitchTable[scalerType][factor]; + if (newMode >= 0) { + _scalerType = scalerType; + + beginGFXTransaction(); + setGraphicsMode(newMode); + endGFXTransaction(); +#ifdef USE_OSD + const char *newScalerName = 0; + const OSystem::GraphicsMode *g = getSupportedGraphicsModes(); + while (g->name) { + if (g->id == _videoMode.mode) { + newScalerName = g->description; + break; + } + g++; + } + if (newScalerName) { + const Common::U32String message = Common::U32String::format( + "%S %s\n%d x %d -> %d x %d", + _("Active graphics filter:").c_str(), + newScalerName, + _videoMode.screenWidth, _videoMode.screenHeight, + _hwScreen->w, _hwScreen->h); + displayMessageOnOSD(message); + } +#endif + +#if SDL_VERSION_ATLEAST(2, 0, 0) + if (sizeChanged) { + // Forcibly resizing the window here since a user switching scaler + // size will not normally cause the window to update + _window->createOrUpdateWindow(_hwScreen->w, _hwScreen->h, _lastFlags); + } +#endif + + internUpdateScreen(); + } +} + +bool SurfaceSdlGraphicsManager::notifyEvent(const Common::Event &event) { + if (event.type != Common::EVENT_CUSTOM_BACKEND_ACTION_START) { + return SdlGraphicsManager::notifyEvent(event); + } + + switch ((CustomEventAction) event.customType) { + case kActionToggleAspectRatioCorrection: { + beginGFXTransaction(); + setFeatureState(OSystem::kFeatureAspectRatioCorrection, !_videoMode.aspectRatioCorrection); + endGFXTransaction(); +#ifdef USE_OSD + Common::U32String message; + if (_videoMode.aspectRatioCorrection) + message = Common::U32String::format("%S\n%d x %d -> %d x %d", + _("Enabled aspect ratio correction").c_str(), + _videoMode.screenWidth, _videoMode.screenHeight, + _hwScreen->w, _hwScreen->h + ); + else + message = Common::U32String::format("%S\n%d x %d -> %d x %d", + _("Disabled aspect ratio correction").c_str(), + _videoMode.screenWidth, _videoMode.screenHeight, + _hwScreen->w, _hwScreen->h + ); + displayMessageOnOSD(message); +#endif + internUpdateScreen(); + return true; + } + +#if SDL_VERSION_ATLEAST(2, 0, 0) + case kActionToggleFilteredScaling: + beginGFXTransaction(); + setFeatureState(OSystem::kFeatureFilteringMode, !_videoMode.filtering); + endGFXTransaction(); +#ifdef USE_OSD + if (getFeatureState(OSystem::kFeatureFilteringMode)) { + displayMessageOnOSD(_("Filtering enabled")); + } else { + displayMessageOnOSD(_("Filtering disabled")); + } +#endif + _forceRedraw = true; + internUpdateScreen(); + return true; +#endif + +#if SDL_VERSION_ATLEAST(2, 0, 0) + case kActionCycleStretchMode: { + int index = 0; + const OSystem::GraphicsMode *sm = s_supportedStretchModes; + while (sm->name) { + if (sm->id == _videoMode.stretchMode) + break; + sm++; + index++; + } + index++; + if (!s_supportedStretchModes[index].name) + index = 0; + + beginGFXTransaction(); + setStretchMode(s_supportedStretchModes[index].id); + endGFXTransaction(); + +#ifdef USE_OSD + Common::U32String message = Common::U32String::format("%S: %S", + _("Stretch mode").c_str(), + _(s_supportedStretchModes[index].description).c_str() + ); + displayMessageOnOSD(message); +#endif + _forceRedraw = true; + internUpdateScreen(); + return true; + } +#endif + + case kActionIncreaseScaleFactor: + handleScalerHotkeys(_videoMode.scaleFactor + 1, _scalerType); + return true; + + case kActionDecreaseScaleFactor: + handleScalerHotkeys(_videoMode.scaleFactor - 1, _scalerType); + return true; + + case kActionSetScaleFilter1: + case kActionSetScaleFilter2: + case kActionSetScaleFilter3: + case kActionSetScaleFilter4: + case kActionSetScaleFilter5: + case kActionSetScaleFilter6: + case kActionSetScaleFilter7: + case kActionSetScaleFilter8: + handleScalerHotkeys(_videoMode.scaleFactor, event.customType - kActionSetScaleFilter1); + return true; + + default: + return SdlGraphicsManager::notifyEvent(event); + } +} + +void SurfaceSdlGraphicsManager::notifyVideoExpose() { + _forceRedraw = true; +} + +void SurfaceSdlGraphicsManager::notifyResize(const int width, const int height) { +#if SDL_VERSION_ATLEAST(2, 0, 0) + handleResize(width, height, _xdpi, _ydpi); +#endif +} + +#if SDL_VERSION_ATLEAST(2, 0, 0) +void SurfaceSdlGraphicsManager::deinitializeRenderer() { + if (_screenTexture) + SDL_DestroyTexture(_screenTexture); + _screenTexture = nullptr; + + if (_renderer) + SDL_DestroyRenderer(_renderer); + _renderer = nullptr; +} + +void SurfaceSdlGraphicsManager::recreateScreenTexture() { + if (!_renderer) + return; + + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, _videoMode.filtering ? "linear" : "nearest"); + + SDL_Texture *oldTexture = _screenTexture; + _screenTexture = SDL_CreateTexture(_renderer, SDL_PIXELFORMAT_RGB565, SDL_TEXTUREACCESS_STREAMING, _videoMode.hardwareWidth, _videoMode.hardwareHeight); + if (_screenTexture) + SDL_DestroyTexture(oldTexture); + else + _screenTexture = oldTexture; +} + +SDL_Surface *SurfaceSdlGraphicsManager::SDL_SetVideoMode(int width, int height, int bpp, Uint32 flags) { + deinitializeRenderer(); + + uint32 createWindowFlags = SDL_WINDOW_RESIZABLE; + if ((flags & SDL_FULLSCREEN) != 0) { + createWindowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + } + + if (!createOrUpdateWindow(width, height, createWindowFlags)) { + return nullptr; + } + + _renderer = SDL_CreateRenderer(_window->getSDLWindow(), -1, 0); + if (!_renderer) { + deinitializeRenderer(); + return nullptr; + } + + // TODO: Implement high DPI support + getWindowSizeFromSdl(&_windowWidth, &_windowHeight); + handleResize(_windowWidth, _windowHeight, 90, 90); + + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, _videoMode.filtering ? "linear" : "nearest"); + + _screenTexture = SDL_CreateTexture(_renderer, SDL_PIXELFORMAT_RGB565, SDL_TEXTUREACCESS_STREAMING, width, height); + if (!_screenTexture) { + deinitializeRenderer(); + return nullptr; + } + + SDL_Surface *screen = SDL_CreateRGBSurface(0, width, height, 16, 0xF800, 0x7E0, 0x1F, 0); + if (!screen) { + deinitializeRenderer(); + return nullptr; + } else { + return screen; + } +} + +void SurfaceSdlGraphicsManager::SDL_UpdateRects(SDL_Surface *screen, int numrects, SDL_Rect *rects) { + SDL_UpdateTexture(_screenTexture, nullptr, screen->pixels, screen->pitch); + + SDL_Rect viewport; + viewport.x = _activeArea.drawRect.left; + viewport.y = _activeArea.drawRect.top; + viewport.w = _activeArea.drawRect.width(); + viewport.h = _activeArea.drawRect.height(); + + SDL_RenderClear(_renderer); + SDL_RenderCopy(_renderer, _screenTexture, NULL, &viewport); + SDL_RenderPresent(_renderer); +} + +int SurfaceSdlGraphicsManager::SDL_SetColors(SDL_Surface *surface, SDL_Color *colors, int firstcolor, int ncolors) { + if (surface->format->palette) { + return !SDL_SetPaletteColors(surface->format->palette, colors, firstcolor, ncolors) ? 1 : 0; + } else { + return 0; + } +} + +int SurfaceSdlGraphicsManager::SDL_SetAlpha(SDL_Surface *surface, Uint32 flag, Uint8 alpha) { + if (SDL_SetSurfaceAlphaMod(surface, alpha)) { + return -1; + } + + if (alpha == 255 || !flag) { + if (SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_NONE)) { + return -1; + } + } else { + if (SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_BLEND)) { + return -1; + } + } + + return 0; +} + +int SurfaceSdlGraphicsManager::SDL_SetColorKey(SDL_Surface *surface, Uint32 flag, Uint32 key) { + return ::SDL_SetColorKey(surface, SDL_TRUE, key) ? -1 : 0; +} + +#endif // SDL_VERSION_ATLEAST(2, 0, 0) + +#endif diff --git a/backends/graphics/surfacesdl/surfacesdl-graphics.h b/backends/graphics/surfacesdl/surfacesdl-graphics.h new file mode 100644 index 00000000000..138186fdb53 --- /dev/null +++ b/backends/graphics/surfacesdl/surfacesdl-graphics.h @@ -0,0 +1,457 @@ +/* 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_SURFACESDL_GRAPHICS_H +#define BACKENDS_GRAPHICS_SURFACESDL_GRAPHICS_H + +#include "backends/graphics/graphics.h" +#include "backends/graphics/sdl/sdl-graphics.h" +#include "graphics/pixelformat.h" +#include "graphics/scaler.h" +#include "common/events.h" +#include "common/mutex.h" + +#include "backends/events/sdl/sdl-events.h" + +#include "backends/platform/sdl/sdl-sys.h" + +#ifndef RELEASE_BUILD +// Define this to allow for focus rectangle debugging +#define USE_SDL_DEBUG_FOCUSRECT +#endif + +enum { + GFX_NORMAL = 0, + GFX_DOUBLESIZE = 1, + GFX_TRIPLESIZE = 2, + GFX_2XSAI = 3, + GFX_SUPER2XSAI = 4, + GFX_SUPEREAGLE = 5, + GFX_ADVMAME2X = 6, + GFX_ADVMAME3X = 7, + GFX_HQ2X = 8, + GFX_HQ3X = 9, + GFX_TV2X = 10, + GFX_DOTMATRIX = 11 +}; + + +class AspectRatio { + int _kw, _kh; +public: + AspectRatio() { _kw = _kh = 0; } + AspectRatio(int w, int h); + + bool isAuto() const { return (_kw | _kh) == 0; } + + int kw() const { return _kw; } + int kh() const { return _kh; } +}; + +/** + * SDL graphics manager + */ +class SurfaceSdlGraphicsManager : public SdlGraphicsManager { +public: + SurfaceSdlGraphicsManager(SdlEventSource *sdlEventSource, SdlWindow *window); + virtual ~SurfaceSdlGraphicsManager(); + + virtual bool hasFeature(OSystem::Feature f) const override; + virtual void setFeatureState(OSystem::Feature f, bool enable) override; + virtual bool getFeatureState(OSystem::Feature f) const override; + + virtual const OSystem::GraphicsMode *getSupportedGraphicsModes() const override; + virtual int getDefaultGraphicsMode() const override; + virtual bool setGraphicsMode(int mode) override; + virtual int getGraphicsMode() const override; + virtual void resetGraphicsScale() override; +#ifdef USE_RGB_COLOR + virtual Graphics::PixelFormat getScreenFormat() const override { return _screenFormat; } + virtual Common::List getSupportedFormats() const override; +#endif +#if SDL_VERSION_ATLEAST(2, 0, 0) + virtual const OSystem::GraphicsMode *getSupportedStretchModes() const override; + virtual int getDefaultStretchMode() const override; + virtual bool setStretchMode(int mode) override; + virtual int getStretchMode() const override; +#endif + virtual void initSize(uint w, uint h, const Graphics::PixelFormat *format = NULL) override; + virtual int getScreenChangeID() const override { return _screenChangeCount; } + + virtual void beginGFXTransaction() override; + virtual OSystem::TransactionError endGFXTransaction() override; + + virtual int16 getHeight() const override; + virtual int16 getWidth() const override; + +protected: + // PaletteManager API + virtual void setPalette(const byte *colors, uint start, uint num) override; + virtual void grabPalette(byte *colors, uint start, uint num) const override; + + /** + * Convert from the SDL pixel format to Graphics::PixelFormat + * @param in The SDL pixel format to convert + * @param out A pixel format to be written to + */ + Graphics::PixelFormat convertSDLPixelFormat(SDL_PixelFormat *in) const; +public: + virtual void copyRectToScreen(const void *buf, int pitch, int x, int y, int w, int h) override; + virtual Graphics::Surface *lockScreen() override; + virtual void unlockScreen() override; + virtual void fillScreen(uint32 col) override; + virtual void updateScreen() override; + virtual void setFocusRectangle(const Common::Rect& rect) override; + virtual void clearFocusRectangle() override; + + virtual Graphics::PixelFormat getOverlayFormat() const override { return _overlayFormat; } + virtual void clearOverlay() override; + virtual void grabOverlay(void *buf, int pitch) const override; + virtual void copyRectToOverlay(const void *buf, int pitch, int x, int y, int w, int h) override; + virtual int16 getOverlayHeight() const override { return _videoMode.overlayHeight; } + virtual int16 getOverlayWidth() const override { return _videoMode.overlayWidth; } + + virtual void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale = false, const Graphics::PixelFormat *format = NULL) override; + virtual void setCursorPalette(const byte *colors, uint start, uint num) override; + +#ifdef USE_OSD + virtual void displayMessageOnOSD(const Common::U32String &msg) override; + virtual void displayActivityIconOnOSD(const Graphics::Surface *icon) override; +#endif + + // Override from Common::EventObserver + virtual bool notifyEvent(const Common::Event &event) override; + + // SdlGraphicsManager interface + virtual void notifyVideoExpose() override; + virtual void notifyResize(const int width, const int height) override; + +protected: +#ifdef USE_OSD + /** Surface containing the OSD message */ + SDL_Surface *_osdMessageSurface; + /** Transparency level of the OSD message */ + uint8 _osdMessageAlpha; + /** When to start the fade out */ + uint32 _osdMessageFadeStartTime; + /** Enum with OSD options */ + enum { + kOSDFadeOutDelay = 2 * 1000, /** < Delay before the OSD is faded out (in milliseconds) */ + kOSDFadeOutDuration = 500, /** < Duration of the OSD fade out (in milliseconds) */ + kOSDInitialAlpha = 80 /** < Initial alpha level, in percent */ + }; + /** Screen rectangle where the OSD message is drawn */ + SDL_Rect getOSDMessageRect() const; + /** Clear the currently displayed OSD message if any */ + void removeOSDMessage(); + /** Surface containing the OSD background activity icon */ + SDL_Surface *_osdIconSurface; + /** Screen rectangle where the OSD background activity icon is drawn */ + SDL_Rect getOSDIconRect() const; + + void updateOSD(); + void drawOSD(); +#endif + + virtual bool gameNeedsAspectRatioCorrection() const override { + return _videoMode.aspectRatioCorrection; + } + virtual int getGameRenderScale() const override { + return _videoMode.scaleFactor; + } + + virtual void handleResizeImpl(const int width, const int height, const int xdpi, const int ydpi) override; + + virtual int getGraphicsModeScale(int mode) const override; + virtual ScalerProc *getGraphicsScalerProc(int mode) const; + + virtual void setupHardwareSize(); + +#if SDL_VERSION_ATLEAST(2, 0, 0) + /* SDL2 features a different API for 2D graphics. We create a wrapper + * around this API to keep the code paths as close as possible. */ + SDL_Renderer *_renderer; + SDL_Texture *_screenTexture; + void deinitializeRenderer(); + void recreateScreenTexture(); + + virtual SDL_Surface *SDL_SetVideoMode(int width, int height, int bpp, Uint32 flags); + virtual void SDL_UpdateRects(SDL_Surface *screen, int numrects, SDL_Rect *rects); + int SDL_SetColors(SDL_Surface *surface, SDL_Color *colors, int firstcolor, int ncolors); + int SDL_SetAlpha(SDL_Surface *surface, Uint32 flag, Uint8 alpha); + int SDL_SetColorKey(SDL_Surface *surface, Uint32 flag, Uint32 key); +#endif + + /** Unseen game screen */ + SDL_Surface *_screen; + Graphics::PixelFormat _screenFormat; + Graphics::PixelFormat _cursorFormat; +#ifdef USE_RGB_COLOR + Common::List _supportedFormats; + + /** + * Update the list of supported pixel formats. + * This method is invoked by loadGFXMode(). + */ + void detectSupportedFormats(); +#endif + + /** Temporary screen (for scalers) */ + SDL_Surface *_tmpscreen; + /** Temporary screen (for scalers) */ + SDL_Surface *_tmpscreen2; + + SDL_Surface *_overlayscreen; + Graphics::PixelFormat _overlayFormat; + + enum { + kTransactionNone = 0, + kTransactionActive = 1, + kTransactionRollback = 2 + }; + + struct TransactionDetails { + bool sizeChanged; + bool needHotswap; + bool needUpdatescreen; +#if SDL_VERSION_ATLEAST(2, 0, 0) + bool needTextureUpdate; + bool needDisplayResize; +#endif +#ifdef USE_RGB_COLOR + bool formatChanged; +#endif + + TransactionDetails() { + sizeChanged = false; + needHotswap = false; + needUpdatescreen = false; + +#if SDL_VERSION_ATLEAST(2, 0, 0) + needTextureUpdate = false; + needDisplayResize = false; +#endif +#ifdef USE_RGB_COLOR + formatChanged = false; +#endif + } + }; + TransactionDetails _transactionDetails; + + struct VideoState { + bool setup; + + bool fullscreen; + bool aspectRatioCorrection; + AspectRatio desiredAspectRatio; + bool filtering; + +#if SDL_VERSION_ATLEAST(2, 0, 0) + int stretchMode; +#endif + + int mode; + int scaleFactor; + + int screenWidth, screenHeight; + int overlayWidth, overlayHeight; + int hardwareWidth, hardwareHeight; +#ifdef USE_RGB_COLOR + Graphics::PixelFormat format; +#endif + + VideoState() { + setup = false; + fullscreen = false; + aspectRatioCorrection = false; + // desiredAspectRatio set to (0, 0) by AspectRatio constructor + filtering = false; + +#if SDL_VERSION_ATLEAST(2, 0, 0) + stretchMode = 0; +#endif + + mode = 0; + scaleFactor = 0; + + screenWidth = 0; + screenHeight = 0; + overlayWidth = 0; + overlayHeight = 0; + hardwareWidth = 0; + hardwareHeight = 0; +#ifdef USE_RGB_COLOR + // format set to 0 values by Graphics::PixelFormat constructor +#endif + } + }; + VideoState _videoMode, _oldVideoMode; + +#if defined(WIN32) && !SDL_VERSION_ATLEAST(2, 0, 0) + /** + * Original BPP to restore the video mode on unload. + * + * This is required to make listing video modes for the OpenGL output work + * on Windows 8+. On these systems OpenGL modes are only available for + * 32bit formats. However, we setup a 16bit format and thus mode listings + * for OpenGL will return an empty list afterwards. + * + * In theory we might require this behavior on non-Win32 platforms too. + * However, SDL sometimes gives us invalid pixel formats for X11 outputs + * causing crashes when trying to setup the original pixel format. + * See bug #7038 "IRIX: X BadMatch when trying to start any 640x480 game". + */ + uint8 _originalBitsPerPixel; +#endif + + ScalerProc *_scalerProc; + int _scalerType; + int _transactionMode; + + // Indicates whether it is needed to free _hwSurface in destructor + bool _displayDisabled; + + bool _screenIsLocked; + Graphics::Surface _framebuffer; + + int _screenChangeCount; + + enum { + NUM_DIRTY_RECT = 100, + MAX_SCALING = 3 + }; + + // Dirty rect management + SDL_Rect _dirtyRectList[NUM_DIRTY_RECT]; + int _numDirtyRects; + + struct MousePos { + // The size and hotspot of the original cursor image. + int16 w, h; + int16 hotX, hotY; + + // The size and hotspot of the pre-scaled cursor image, in real + // coordinates. + int16 rW, rH; + int16 rHotX, rHotY; + + // The size and hotspot of the pre-scaled cursor image, in game + // coordinates. + int16 vW, vH; + int16 vHotX, vHotY; + + MousePos() : w(0), h(0), hotX(0), hotY(0), + rW(0), rH(0), rHotX(0), rHotY(0), vW(0), vH(0), + vHotX(0), vHotY(0) + { } + }; + + byte *_mouseData; + SDL_Rect _mouseBackup; + MousePos _mouseCurState; +#ifdef USE_RGB_COLOR + uint32 _mouseKeyColor; +#else + byte _mouseKeyColor; +#endif + bool _cursorDontScale; + bool _cursorPaletteDisabled; + SDL_Surface *_mouseOrigSurface; + SDL_Surface *_mouseSurface; + enum { + kMouseColorKey = 1 + }; + + // Shake mode + // This is always set to 0 when building with SDL2. + int _currentShakeXOffset; + int _currentShakeYOffset; + + // Palette data + SDL_Color *_currentPalette; + uint _paletteDirtyStart, _paletteDirtyEnd; + + // Cursor palette data + SDL_Color *_cursorPalette; + + /** + * Mutex which prevents multiple threads from interfering with each other + * when accessing the screen. + */ + Common::Mutex _graphicsMutex; + +#ifdef USE_SDL_DEBUG_FOCUSRECT + bool _enableFocusRectDebugCode; + bool _enableFocusRect; + Common::Rect _focusRect; +#endif + + virtual void addDirtyRect(int x, int y, int w, int h, bool realCoordinates = false); + + virtual void drawMouse(); + virtual void undrawMouse(); + virtual void blitCursor(); + + virtual void internUpdateScreen(); + + virtual bool loadGFXMode(); + virtual void unloadGFXMode(); + virtual bool hotswapGFXMode(); + + virtual void setAspectRatioCorrection(bool enable); + void setFilteringMode(bool enable); + + virtual bool saveScreenshot(const Common::String &filename) const override; + virtual void setGraphicsModeIntern(); + +private: + void setFullscreenMode(bool enable); + void handleScalerHotkeys(int scalefactor, int scalerType); + + /** + * Converts the given point from the overlay's coordinate space to the + * game's coordinate space. + */ + Common::Point convertOverlayToGame(const int x, const int y) const { + if (getOverlayWidth() == 0 || getOverlayHeight() == 0) { + error("convertOverlayToGame called without a valid overlay"); + } + + return Common::Point(x * getWidth() / getOverlayWidth(), + y * getHeight() / getOverlayHeight()); + } + + /** + * Converts the given point from the game's coordinate space to the + * overlay's coordinate space. + */ + Common::Point convertGameToOverlay(const int x, const int y) const { + if (getWidth() == 0 || getHeight() == 0) { + error("convertGameToOverlay called without a valid overlay"); + } + + return Common::Point(x * getOverlayWidth() / getWidth(), + y * getOverlayHeight() / getHeight()); + } +}; + +#endif diff --git a/backends/graphics3d/sdl/sdl-graphics3d.cpp b/backends/graphics3d/sdl/sdl-graphics3d.cpp index 9c5bdb57a30..d390b0074c8 100644 --- a/backends/graphics3d/sdl/sdl-graphics3d.cpp +++ b/backends/graphics3d/sdl/sdl-graphics3d.cpp @@ -20,6 +20,7 @@ * */ +#include "backends/graphics/sdl/sdl-graphics.h" #include "backends/graphics3d/sdl/sdl-graphics3d.h" #include "backends/platform/sdl/sdl-sys.h" #include "backends/events/sdl/resvm-sdl-events.h" diff --git a/backends/module.mk b/backends/module.mk index d9c2cb2b76c..f9fbb1fafe6 100644 --- a/backends/module.mk +++ b/backends/module.mk @@ -112,6 +112,21 @@ MODULE_OBJS += \ vkeybd/virtual-keyboard-parser.o endif +# OpenGL specific source files. +ifdef USE_OPENGL +MODULE_OBJS += \ + graphics/opengl/context.o \ + graphics/opengl/debug.o \ + graphics/opengl/framebuffer.o \ + graphics/opengl/opengl-graphics.o \ + graphics/opengl/shader.o \ + graphics/opengl/texture.o \ + graphics/opengl/pipelines/clut8.o \ + graphics/opengl/pipelines/fixed.o \ + graphics/opengl/pipelines/pipeline.o \ + graphics/opengl/pipelines/shader.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. @@ -120,6 +135,8 @@ MODULE_OBJS += \ events/sdl/legacy-sdl-events.o \ events/sdl/sdl-events.o \ events/sdl/resvm-sdl-events.o \ + graphics/sdl/sdl-graphics.o \ + graphics/surfacesdl/surfacesdl-graphics.o \ graphics3d/sdl/sdl-graphics3d.o \ graphics3d/surfacesdl/surfacesdl-graphics3d.o \ mixer/sdl/sdl-mixer.o \ @@ -135,6 +152,7 @@ endif ifdef USE_OPENGL MODULE_OBJS += \ + graphics/openglsdl/openglsdl-graphics.o \ graphics3d/openglsdl/openglsdl-graphics3d.o endif diff --git a/backends/platform/sdl/sdl.cpp b/backends/platform/sdl/sdl.cpp index b114b5c4f99..1c3473574eb 100644 --- a/backends/platform/sdl/sdl.cpp +++ b/backends/platform/sdl/sdl.cpp @@ -49,9 +49,10 @@ #include "backends/keymapper/hardware-input.h" #include "backends/mutex/sdl/sdl-mutex.h" #include "backends/timer/sdl/sdl-timer.h" +#include "backends/graphics/surfacesdl/surfacesdl-graphics.h" #include "backends/graphics3d/surfacesdl/surfacesdl-graphics3d.h" // ResidualVM specific - #ifdef USE_OPENGL +#include "backends/graphics/openglsdl/openglsdl-graphics.h" #include "backends/graphics3d/openglsdl/openglsdl-graphics3d.h" // ResidualVM specific #include "graphics/opengl/context.h" // ResidualVM specific #include "graphics/cursorman.h" @@ -233,7 +234,6 @@ void OSystem_SDL::initBackend() { } if (_graphicsManager == 0) { -#if 0 // ResidualVM - not used #ifdef USE_OPENGL // Setup a list with both SDL and OpenGL graphics modes. We only do // this whenever the subclass did not already set up an graphics @@ -254,7 +254,6 @@ void OSystem_SDL::initBackend() { } } } -#endif // ResidualVM #endif if (_graphicsManager == 0) { @@ -723,7 +722,6 @@ Common::String OSystem_SDL::getScreenshotsPath() { return path; } -#if 0 // ResidualVM - not used #ifdef USE_OPENGL const OSystem::GraphicsMode *OSystem_SDL::getSupportedGraphicsModes() const { @@ -772,14 +770,14 @@ bool OSystem_SDL::setGraphicsMode(int mode) { debug(1, "switching to plain SDL graphics"); sdlGraphicsManager->deactivateManager(); delete _graphicsManager; - _graphicsManager = sdlGraphicsManager = new SurfaceSdlGraphics3dManager(_eventSource, _window); + _graphicsManager = sdlGraphicsManager = new SurfaceSdlGraphicsManager(_eventSource, _window); switchedManager = true; } else if (_graphicsMode < _firstGLMode && mode >= _firstGLMode) { debug(1, "switching to OpenGL graphics"); sdlGraphicsManager->deactivateManager(); delete _graphicsManager; - _graphicsManager = sdlGraphicsManager = new OpenGLSdlGraphics3dManager(_eventSource, _window); + _graphicsManager = sdlGraphicsManager = new OpenGLSdlGraphicsManager(_eventSource, _window); switchedManager = true; } @@ -829,7 +827,7 @@ void OSystem_SDL::setupGraphicsModes() { const OSystem::GraphicsMode *srcMode; int defaultMode; - GraphicsManager *manager = new SurfaceSdlGraphics3dManager(_eventSource, _window); + GraphicsManager *manager = new SurfaceSdlGraphicsManager(_eventSource, _window); srcMode = manager->getSupportedGraphicsModes(); defaultMode = manager->getDefaultGraphicsMode(); while (srcMode->name) { @@ -872,7 +870,6 @@ void OSystem_SDL::setupGraphicsModes() { } } #endif -#endif // ResidualVM char *OSystem_SDL::convertEncoding(const char *to, const char *from, const char *string, size_t length) { #if SDL_VERSION_ATLEAST(1, 2, 10) && !defined(__MORPHOS__) diff --git a/backends/platform/sdl/sdl.h b/backends/platform/sdl/sdl.h index b47080cc031..db4916d1ae0 100644 --- a/backends/platform/sdl/sdl.h +++ b/backends/platform/sdl/sdl.h @@ -170,12 +170,11 @@ protected: * Creates the merged graphics modes list */ void setupGraphicsModes(); -// ResidualVM - Start - //virtual const OSystem::GraphicsMode *getSupportedGraphicsModes() const override; - //virtual int getDefaultGraphicsMode() const override; - //virtual bool setGraphicsMode(int mode) override; - //virtual int getGraphicsMode() const override; -// ResidualVM - End + + virtual const OSystem::GraphicsMode *getSupportedGraphicsModes() const override; + virtual int getDefaultGraphicsMode() const override; + virtual bool setGraphicsMode(int mode) override; + virtual int getGraphicsMode() const override; #endif protected: virtual char *convertEncoding(const char *to, const char *from, const char *string, size_t length) override;