Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

393
graphics/opengl/context.cpp Normal file
View File

@@ -0,0 +1,393 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#define GLAD_GL_IMPLEMENTATION
// sscanf_s is used by glad on MSVC only
// we can't know before config.h is loaded that GLAD will be used
// but at this time it will be too late to allow sscanf_s
#ifdef _MSC_VER
#define FORBIDDEN_SYMBOL_EXCEPTION_sscanf_s
#endif
#include "graphics/opengl/context.h"
#include "common/debug.h"
#include "common/str.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "common/tokenizer.h"
#include "graphics/opengl/system_headers.h"
#if defined(USE_OPENGL)
#ifdef USE_GLAD
static GLADapiproc loadFunc(const char *name) {
return (GLADapiproc)g_system->getOpenGLProcAddress(name);
}
#endif
namespace Common {
DECLARE_SINGLETON(OpenGL::Context);
}
namespace OpenGL {
Context::Context() {
reset();
}
void Context::reset() {
type = kContextNone;
maxTextureSize = 0;
majorVersion = 0;
minorVersion = 0;
glslVersion = 0;
NPOTSupported = false;
imagingSupported = false;
shadersSupported = false;
enginesShadersSupported = false;
multitextureSupported = false;
framebufferObjectSupported = false;
framebufferObjectMultisampleSupported = false;
multisampleMaxSamples = -1;
bgraSupported = false;
packedPixelsSupported = false;
packedDepthStencilSupported = false;
unpackSubImageSupported = false;
OESDepth24 = false;
textureEdgeClampSupported = false;
textureBorderClampSupported = false;
textureMirrorRepeatSupported = false;
textureMaxLevelSupported = false;
textureLookupPrecision = 0;
}
void Context::initialize(ContextType contextType) {
// Initialize default state.
reset();
if (contextType == kContextNone) {
return;
}
type = contextType;
#ifdef USE_GLAD
int gladVersion;
switch (type) {
case kContextGL:
gladVersion = gladLoadGL(loadFunc);
break;
case kContextGLES:
gladVersion = gladLoadGLES1(loadFunc);
break;
case kContextGLES2:
gladVersion = gladLoadGLES2(loadFunc);
break;
default:
gladVersion = 0;
break;
}
majorVersion = GLAD_VERSION_MAJOR(gladVersion);
minorVersion = GLAD_VERSION_MINOR(gladVersion);
if (!gladVersion) {
// If gladVersion is 0 it means that loading failed and glad didn't set up anything
error("Couldn't initialize OpenGL");
}
#else
const char *verString = (const char *)glGetString(GL_VERSION);
if (!verString) {
majorVersion = minorVersion = 0;
int errorCode = glGetError();
warning("Could not fetch GL_VERSION: %d", errorCode);
return;
} else if (type == kContextGL) {
// OpenGL version number is either of the form major.minor or major.minor.release,
// where the numbers all have one or more digits
if (sscanf(verString, "%d.%d", &majorVersion, &minorVersion) != 2) {
majorVersion = minorVersion = 0;
warning("Could not parse GL version '%s'", verString);
}
} else if (type == kContextGLES) {
// The form of the string is "OpenGL ES-<profile> <major>.<minor>",
// where <profile> is either "CM" (Common) or "CL" (Common-Lite),
// and <major> and <minor> are integers.
char profile[3];
if (sscanf(verString, "OpenGL ES-%2s %d.%d", profile,
&majorVersion, &minorVersion) != 3) {
majorVersion = minorVersion = 0;
warning("Could not parse GL ES version '%s'", verString);
}
} else if (type == kContextGLES2) {
// The version is of the form
// OpenGL<space>ES<space><version number><space><vendor-specific information>
// version number format is not defined
// There is only OpenGL ES 2.0 anyway
if (sscanf(verString, "OpenGL ES %d.%d", &majorVersion, &minorVersion) != 2) {
minorVersion = 0;
if (sscanf(verString, "OpenGL ES %d ", &majorVersion) != 1) {
majorVersion = 0;
warning("Could not parse GL ES 2 version '%s'", verString);
}
}
}
#endif
glslVersion = getGLSLVersion();
// Obtain maximum texture size.
glGetIntegerv(GL_MAX_TEXTURE_SIZE, (GLint *)&maxTextureSize);
const char *extString = (const char *)glGetString(GL_EXTENSIONS);
if (!extString) {
extString = "";
}
bool EXTFramebufferMultisample = false;
bool EXTFramebufferBlit = 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") {
NPOTSupported = true;
} else if (token == "GL_ARB_imaging") {
imagingSupported = true;
} else if (token == "GL_ARB_multitexture") {
multitextureSupported = true;
} else if (token == "GL_ARB_framebuffer_object") {
framebufferObjectSupported = true;
} else if (token == "GL_EXT_bgra" || token == "GL_EXT_texture_format_BGRA8888") {
bgraSupported = true;
} else if (token == "GL_EXT_packed_pixels" || token == "GL_APPLE_packed_pixels") {
packedPixelsSupported = true;
} else if (token == "GL_EXT_packed_depth_stencil" || token == "GL_OES_packed_depth_stencil") {
packedDepthStencilSupported = true;
} else if (token == "GL_EXT_unpack_subimage") {
unpackSubImageSupported = true;
} else if (token == "GL_EXT_framebuffer_multisample") {
EXTFramebufferMultisample = true;
} else if (token == "GL_EXT_framebuffer_blit") {
EXTFramebufferBlit = true;
} else if (token == "GL_OES_depth24") {
OESDepth24 = true;
} else if (token == "GL_SGIS_texture_edge_clamp") {
textureEdgeClampSupported = true;
} else if (token == "GL_ARB_texture_border_clamp" || token == "GL_SGIS_texture_border_clamp" || token == "GL_OES_texture_border_clamp" ||
token == "GL_EXT_texture_border_clamp" || token == "GL_NV_texture_border_clamp") {
textureBorderClampSupported = true;
} else if (token == "GL_ARB_texture_mirrored_repeat" || token == "GL_OES_texture_mirrored_repeat" || token == "GL_IBM_texture_mirrored_repeat") {
textureMirrorRepeatSupported = true;
} else if (token == "GL_SGIS_texture_lod" || token == "GL_APPLE_texture_max_level") {
textureMaxLevelSupported = true;
}
}
if (type == kContextGLES2) {
// OGLES2 on AmigaOS reports GLSL version as 0.9 but we do what is needed to make it work
// so let's pretend it supports 1.00
#if defined(__amigaos4__)
if (glslVersion < 100) {
glslVersion = 100;
}
#endif
// GLES2 always has (limited) NPOT support.
NPOTSupported = true;
// GLES2 always has imaging support
imagingSupported = true;
// GLES2 always has shader support.
shadersSupported = true;
// GLES2 should always have GLSL ES 1.00 support but let's make sure
enginesShadersSupported = (glslVersion >= 100);
// GLES2 always has multi texture support.
multitextureSupported = true;
// GLES2 always has FBO support.
framebufferObjectSupported = true;
packedPixelsSupported = true;
textureEdgeClampSupported = true;
textureMirrorRepeatSupported = true;
// OpenGL ES 3.0 and later adds multisample FBOs, and always has the following extensions
if (isGLVersionOrHigher(3, 0)) {
framebufferObjectMultisampleSupported = true;
packedDepthStencilSupported = true;
textureMaxLevelSupported = true;
unpackSubImageSupported = true;
OESDepth24 = true;
}
// OpenGL ES 3.2 and later always has texture border clamp support
if (isGLVersionOrHigher(3, 2)) {
textureBorderClampSupported = true;
}
// In GLES2, texture lookup is done using lowp (and mediump is not always available)
GLint range[2];
GLint precision = 0;
glGetShaderPrecisionFormat(GL_FRAGMENT_SHADER, GL_LOW_FLOAT, range, &precision);
textureLookupPrecision = precision;
debug(5, "OpenGL: GLES2 context initialized");
} else if (type == kContextGLES) {
// GLES doesn't support shaders natively
// ScummVM does not support multisample FBOs with GLES for now
framebufferObjectMultisampleSupported = false;
// GLES always has imaging support
imagingSupported = true;
packedPixelsSupported = true;
textureEdgeClampSupported = true;
// No border clamping in GLES
// No mirror repeat in GLES
// Precision is irrelevant (shaders) in GLES
debug(5, "OpenGL: GLES context initialized");
} else if (type == kContextGL) {
// Official support of shaders starts with version 110
// Older versions didn't support the #version directive and were only available through
// ARB extensions which we removed support for
shadersSupported = glslVersion >= 110;
// In GL mode engines need GLSL 1.20
enginesShadersSupported = glslVersion >= 120;
// Desktop GL always has unpack sub-image support
unpackSubImageSupported = true;
framebufferObjectMultisampleSupported = EXTFramebufferMultisample && EXTFramebufferBlit;
// OpenGL 1.2 and later always has packed pixels, texture edge clamp and texture max level support
if (isGLVersionOrHigher(1, 2)) {
bgraSupported = true;
packedPixelsSupported = true;
textureEdgeClampSupported = true;
textureMaxLevelSupported = true;
}
// OpenGL 1.3 adds texture border clamp support and mandatory imaging support
if (isGLVersionOrHigher(1, 3)) {
imagingSupported = true;
textureBorderClampSupported = true;
}
// OpenGL 1.4 adds texture mirror repeat support
if (isGLVersionOrHigher(1, 4)) {
textureMirrorRepeatSupported = true;
}
// In OpenGL precision is always enough
textureLookupPrecision = UINT_MAX;
debug(5, "OpenGL: GL context initialized");
} else {
warning("OpenGL: Unknown context initialized");
}
if (framebufferObjectMultisampleSupported) {
glGetIntegerv(GL_MAX_SAMPLES, (GLint *)&multisampleMaxSamples);
}
const char *glslVersionString = glslVersion ? (const char *)glGetString(GL_SHADING_LANGUAGE_VERSION) : "";
// Log features supported by GL context.
debug(5, "OpenGL version: %s", glGetString(GL_VERSION));
debug(5, "OpenGL vendor: %s", glGetString(GL_VENDOR));
debug(5, "OpenGL renderer: %s", glGetString(GL_RENDERER));
debug(5, "OpenGL: version %d.%d", majorVersion, minorVersion);
debug(5, "OpenGL: GLSL version string: %s", glslVersionString ? glslVersionString : "<none>");
debug(5, "OpenGL: GLSL version: %d", glslVersion);
debug(5, "OpenGL: Max texture size: %d", maxTextureSize);
debug(5, "OpenGL: NPOT texture support: %d", NPOTSupported);
debug(5, "OpenGL: Imaging support: %d", imagingSupported);
debug(5, "OpenGL: Shader support: %d", shadersSupported);
debug(5, "OpenGL: Shader support for engines: %d", enginesShadersSupported);
debug(5, "OpenGL: Multitexture support: %d", multitextureSupported);
debug(5, "OpenGL: FBO support: %d", framebufferObjectSupported);
debug(5, "OpenGL: Multisample FBO support: %d", framebufferObjectMultisampleSupported);
debug(5, "OpenGL: Multisample max number: %d", multisampleMaxSamples);
debug(5, "OpenGL: BGRA support: %d", bgraSupported);
debug(5, "OpenGL: Packed pixels support: %d", packedPixelsSupported);
debug(5, "OpenGL: Packed depth stencil support: %d", packedDepthStencilSupported);
debug(5, "OpenGL: Unpack subimage support: %d", unpackSubImageSupported);
debug(5, "OpenGL: OpenGL ES depth 24 support: %d", OESDepth24);
debug(5, "OpenGL: Texture edge clamping support: %d", textureEdgeClampSupported);
debug(5, "OpenGL: Texture border clamping support: %d", textureBorderClampSupported);
debug(5, "OpenGL: Texture mirror repeat support: %d", textureMirrorRepeatSupported);
debug(5, "OpenGL: Texture max level support: %d", textureMaxLevelSupported);
debug(5, "OpenGL: Texture lookup precision: %d", textureLookupPrecision);
}
int Context::getGLSLVersion() const {
#if USE_FORCED_GLES
return 0;
#else
// No shader support in GLES
if (type == kContextGLES) {
return 0;
}
// No shader support in OpenGL 1.x
if (type == kContextGL && !isGLVersionOrHigher(2, 0)) {
return 0;
}
const char *glslVersionString = (const char *)glGetString(GL_SHADING_LANGUAGE_VERSION);
if (!glslVersionString) {
warning("Could not get GLSL version");
return 0;
}
// Search for the first digit in the version string and parse from there
const char *glslVersionStringNum;
for (glslVersionStringNum = glslVersionString; *glslVersionStringNum != '\0'; glslVersionStringNum++) {
if (*glslVersionStringNum >= '0' &&
*glslVersionStringNum <= '9') {
break;
}
}
// Here *glslVersionStringNum is either a digit or a NUL character
int glslMajorVersion, glslMinorVersion;
if (sscanf(glslVersionStringNum, "%d.%d", &glslMajorVersion, &glslMinorVersion) != 2) {
warning("Could not parse GLSL version '%s' extracted from '%s'", glslVersionStringNum, glslVersionString);
return 0;
}
return glslMajorVersion * 100 + glslMinorVersion;
#endif
}
} // End of namespace OpenGL
#endif

145
graphics/opengl/context.h Normal file
View File

@@ -0,0 +1,145 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRAPHICS_OPENGL_CONTEXT_H
#define GRAPHICS_OPENGL_CONTEXT_H
#include "common/singleton.h"
namespace OpenGL {
enum ContextType {
kContextNone,
kContextGL,
kContextGLES,
kContextGLES2
};
/**
* Description structure of the OpenGL (ES) context.
*/
class Context : public Common::Singleton<Context> {
public:
Context();
/**
* Initialize the context description from currently active context.
*
* The extensions and features are marked as available according
* to the current context capabilities.
*/
void initialize(ContextType contextType);
/**
* Reset context.
*
* This marks all extensions as unavailable.
*/
void reset();
/** The type of the active context. */
ContextType type;
/** Helper function for checking the GL version supported by the context. */
inline bool isGLVersionOrHigher(int major, int minor) const {
return ((majorVersion > major) || ((majorVersion == major) && (minorVersion >= minor)));
}
/** The GL version supported by the context. */
int majorVersion, minorVersion;
/** The GLSL version supported by the context */
int glslVersion;
/** The maximum texture size supported by the context. */
int maxTextureSize;
/** Whether GL_ARB_texture_non_power_of_two is available or not. */
bool NPOTSupported;
/** Whether GL_ARB_imaging is available or not. */
bool imagingSupported;
/** Whether shader support is available or not. */
bool shadersSupported;
/** Whether shader support is good enough for engines or not. */
bool enginesShadersSupported;
/** Whether multi texture support is available or not. */
bool multitextureSupported;
/** Whether FBO support is available or not. */
bool framebufferObjectSupported;
/** Whether multisample FBO support is available or not */
bool framebufferObjectMultisampleSupported;
/**
* Contains the maximum number of supported multisample samples
* if multisample FBOs are supported. Contains -1 otherwise.
*/
int multisampleMaxSamples;
/** Whether BGRA support is available or not. */
bool bgraSupported;
/** Whether packed pixels support is available or not. */
bool packedPixelsSupported;
/** Whether packing the depth and stencil buffers is possible or not. */
bool packedDepthStencilSupported;
/** Whether specifying a pitch when uploading to textures is available or not */
bool unpackSubImageSupported;
/** Whether depth component 24 is supported or not */
bool OESDepth24;
/** Whether texture coordinate edge clamping is available or not. */
bool textureEdgeClampSupported;
/** Whether texture coordinate border clamping is available or not. */
bool textureBorderClampSupported;
/** Whether texture coordinate mirrored repeat is available or not. */
bool textureMirrorRepeatSupported;
/** Whether texture max level is available or not. */
bool textureMaxLevelSupported;
/** Texture lookup result precision. */
unsigned int textureLookupPrecision;
private:
/**
* Returns the native GLSL version supported by the driver.
* This does NOT take shaders ARB extensions into account.
*/
int getGLSLVersion() const;
};
} // End of namespace OpenGL
/** Shortcut for accessing the active OpenGL context. */
#define OpenGLContext OpenGL::Context::instance()
#endif

77
graphics/opengl/debug.cpp Normal file
View File

@@ -0,0 +1,77 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "graphics/opengl/debug.h"
#include "common/str.h"
#include "common/textconsole.h"
#include "graphics/opengl/system_headers.h"
#if defined(USE_OPENGL)
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 clearGLError() {
GLenum error;
while ((error = glGetError()) != GL_NO_ERROR)
;
}
bool checkGLError(const char *expr, const char *file, int line) {
bool ret = false;
GLenum error;
while ((error = 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);
ret = true;
}
return ret;
}
} // End of namespace OpenGL
#endif

57
graphics/opengl/debug.h Normal file
View File

@@ -0,0 +1,57 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_GRAPHICS_OPENGL_DEBUG_H
#define BACKENDS_GRAPHICS_OPENGL_DEBUG_H
#include "graphics/opengl/context.h"
#ifndef __EMSCRIPTEN__
#define OPENGL_DEBUG
#endif
namespace OpenGL {
void clearGLError();
bool checkGLError(const char *expr, const char *file, int line);
} // End of namespace OpenGL
#define GL_WRAP_CHECK(err, call, name) do { OpenGL::clearGLError(); (call); err = OpenGL::checkGLError(#name, __FILE__, __LINE__); } while (false)
#ifdef OPENGL_DEBUG
#define GL_WRAP_DEBUG(call, name) do { OpenGL::clearGLError(); (call); OpenGL::checkGLError(#name, __FILE__, __LINE__); } while (false)
#else
#define GL_WRAP_DEBUG(call, name) do { (call); } while (false)
#endif
// This macro will set boolean err to true if there was an error and display them in the console
#define GL_CALL_CHECK(err, x) GL_WRAP_CHECK(err, x, x)
// This macro will check for errors and display them in the console only when OPENGL_DEBUG is enabled
#define GL_CALL(x) GL_WRAP_DEBUG(x, x)
// This macro will execute the call only when the context is valid and will check errors only when OPENGL_DEBUG is enabled
#define GL_CALL_SAFE(func, params) \
do { \
if (OpenGLContext.type != kContextNone) { \
GL_CALL(func params); \
} \
} while (0)
// This macro will assign result of x to var and check for errors when OPENGL_DEBUG is enabled
#define GL_ASSIGN(var, x) GL_WRAP_DEBUG(var = x, x)
#endif

14196
graphics/opengl/glad.h Normal file

File diff suppressed because it is too large Load Diff

519
graphics/opengl/shader.cpp Normal file
View File

@@ -0,0 +1,519 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/scummsys.h"
#include "common/config-manager.h"
#include "graphics/opengl/system_headers.h"
#if defined(USE_OPENGL) && !USE_FORCED_GLES
#include "graphics/opengl/shader.h"
#include "graphics/opengl/context.h"
namespace OpenGL {
static const char *const compatVertex =
"#if defined(GL_ES)\n"
"#define ROUND(x) (sign(x) * floor(abs(x) + .5))\n"
"#define in attribute\n"
"#define out varying\n"
"#elif __VERSION__ < 130\n"
"#define ROUND(x) (sign(x) * floor(abs(x) + .5))\n"
"#define highp\n"
"#define in attribute\n"
"#define out varying\n"
"#else\n"
"#define ROUND(x) round(x)\n"
"#endif\n";
static const char *const compatFragment =
"#if defined(GL_ES)\n"
"#define in varying\n"
"#ifdef GL_FRAGMENT_PRECISION_HIGH\n"
"precision highp float;\n"
"#else\n"
"precision mediump float;\n"
"#endif\n"
"#define OUTPUT\n"
"#define outColor gl_FragColor\n"
"#define texture texture2D\n"
"#elif __VERSION__ < 130\n"
"#define in varying\n"
"#define OUTPUT\n"
"#define outColor gl_FragColor\n"
"#define texture texture2D\n"
"#else\n"
"#define OUTPUT out vec4 outColor;\n"
"#endif\n";
// OGLES2 on AmigaOS doesn't support uniform booleans, let's introduce some shim
#if defined(__amigaos4__)
static const char *const compatUniformBool =
"#define UBOOL mediump int\n"
"#define UBOOL_TEST(v) (v != 0)\n";
#else
static const char *const compatUniformBool =
"#define UBOOL bool\n"
"#define UBOOL_TEST(v) v\n";
#endif
static const GLchar *readFile(const Common::String &filename) {
Common::File file;
Common::Path shaderDir;
#ifndef RELEASE_BUILD
// Allow load shaders from source code directory without install them.
// It's used for development purpose.
// Additionally allow load shaders outside distribution data path,
// 'extrapath' is used temporary in SearchMan.
SearchMan.addDirectory("GRIM_SHADERS", "engines/grim", 0, 2);
SearchMan.addDirectory("MYST3_SHADERS", "engines/myst3", 0, 2);
SearchMan.addDirectory("STARK_SHADERS", "engines/stark", 0, 2);
SearchMan.addDirectory("WINTERMUTE_SHADERS", "engines/wintermute/base/gfx/opengl", 0, 2);
SearchMan.addDirectory("PLAYGROUND3D_SHADERS", "engines/playground3d", 0, 2);
SearchMan.addDirectory("FREESCAPE_SHADERS", "engines/freescape", 0, 2);
SearchMan.addDirectory("HPL1_SHADERS", "engines/hpl1/engine/impl", 0, 2);
#endif
if (ConfMan.hasKey("extrapath")) {
SearchMan.addDirectory("EXTRA_PATH", Common::FSNode(ConfMan.getPath("extrapath")), 0, 2);
}
#if !defined(IPHONE)
shaderDir = Common::Path("shaders/", '/');
#endif
file.open(shaderDir.appendComponent(filename));
if (!file.isOpen())
error("Could not open shader %s!", filename.c_str());
#ifndef RELEASE_BUILD
SearchMan.remove("GRIM_SHADERS");
SearchMan.remove("MYST3_SHADERS");
SearchMan.remove("STARK_SHADERS");
SearchMan.remove("WINTERMUTE_SHADERS");
SearchMan.remove("PLAYGROUND3D_SHADERS");
SearchMan.remove("FREESCAPE_SHADERS");
SearchMan.remove("HPL1_SHADERS");
#endif
SearchMan.remove("EXTRA_PATH");
const int32 size = file.size();
GLchar *shaderSource = new GLchar[size + 1];
file.read(shaderSource, size);
file.close();
shaderSource[size] = '\0';
return shaderSource;
}
GLuint Shader::createDirectShader(size_t shaderSourcesCount, const char *const *shaderSources, GLenum shaderType, const Common::String &name) {
GLuint shader;
GL_ASSIGN(shader, glCreateShader(shaderType));
GL_CALL(glShaderSource(shader, shaderSourcesCount, shaderSources, NULL));
GL_CALL(glCompileShader(shader));
GLint status;
GL_CALL(glGetShaderiv(shader, GL_COMPILE_STATUS, &status));
if (status != GL_TRUE) {
GLint logSize;
GL_CALL(glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logSize));
GLchar *log = new GLchar[logSize];
GL_CALL(glGetShaderInfoLog(shader, logSize, nullptr, log));
_error = Common::String::format("Could not compile shader %s: %s", name.c_str(), log);
warning("Shader::createDirectShader(): %s", _error.c_str());
delete[] log;
return 0;
}
return shader;
}
GLuint Shader::createCompatShader(const char *shaderSource, GLenum shaderType, const Common::String &name, int compatGLSLVersion) {
GLchar versionSource[20];
if (OpenGLContext.type == kContextGLES2) {
switch(compatGLSLVersion) {
case 110:
case 120:
// GLSL ES 1.00 is a subset of GLSL 1.20
compatGLSLVersion = 100;
break;
default:
_error = Common::String::format("Invalid GLSL version %d", compatGLSLVersion);
warning("Shader: createCompatShader(): %s", _error.c_str());
return 0;
}
} else {
switch(compatGLSLVersion) {
case 110:
case 120:
break;
default:
_error = Common::String::format("Invalid GLSL version %d", compatGLSLVersion);
warning("Shader: createCompatShader(): %s", _error.c_str());
return 0;
}
}
if (OpenGLContext.glslVersion < compatGLSLVersion) {
_error = Common::String::format("Required GLSL version %d is not supported (%d maximum)", compatGLSLVersion, OpenGLContext.glslVersion);
warning("Shader: createCompatShader(): %s", _error.c_str());
return 0;
}
Common::sprintf_s(versionSource, "#version %d\n", compatGLSLVersion);
const GLchar *compatSource =
shaderType == GL_VERTEX_SHADER ? compatVertex : compatFragment;
const GLchar *shaderSources[] = {
versionSource,
compatSource,
compatUniformBool,
shaderSource
};
GLuint shader;
GL_ASSIGN(shader, glCreateShader(shaderType));
GL_CALL(glShaderSource(shader, 4, shaderSources, NULL));
GL_CALL(glCompileShader(shader));
GLint status;
GL_CALL(glGetShaderiv(shader, GL_COMPILE_STATUS, &status));
if (status != GL_TRUE) {
GLint logSize;
GL_CALL(glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logSize));
GLchar *log = new GLchar[logSize];
GL_CALL(glGetShaderInfoLog(shader, logSize, nullptr, log));
_error = Common::String::format("Could not compile shader %s: %s", name.c_str(), log);
warning("Shader: createCompatShader(): %s", _error.c_str());
return 0;
}
return shader;
}
GLuint Shader::loadShaderFromFile(const char *base, const char *extension, GLenum shaderType, int compatGLSLVersion) {
const Common::String filename = Common::String(base) + "." + extension;
const GLchar *shaderSource = readFile(filename);
GLuint shader;
if (compatGLSLVersion) {
shader = createCompatShader(shaderSource, shaderType, filename, compatGLSLVersion);
} else {
shader = createDirectShader(1, &shaderSource, shaderType, filename);
}
delete[] shaderSource;
return shader;
}
/**
* A deleter for OpenGL programs pointers which can be used with Common::SharedPtr.
*/
struct SharedPtrProgramDeleter {
void operator()(GLuint *ptr) {
if (ptr) {
GL_CALL(glDeleteProgram(*ptr));
}
delete ptr;
}
};
Shader *Shader::_previousShader = nullptr;
uint32 Shader::previousNumAttributes = 0;
Shader::Shader() {
}
bool Shader::loadShader(const Common::String &name, GLuint vertexShader, GLuint fragmentShader, const char *const *attributes) {
assert(attributes);
_name = name;
GLuint shaderProgram;
GL_ASSIGN(shaderProgram, glCreateProgram());
GL_CALL(glAttachShader(shaderProgram, vertexShader));
GL_CALL(glAttachShader(shaderProgram, fragmentShader));
for (int idx = 0; attributes[idx]; ++idx) {
GL_CALL(glBindAttribLocation(shaderProgram, idx, attributes[idx]));
_attributes.push_back(VertexAttrib(idx, attributes[idx]));
}
GL_CALL(glLinkProgram(shaderProgram));
GLint status;
GL_CALL(glGetProgramiv(shaderProgram, GL_LINK_STATUS, &status));
if (status != GL_TRUE) {
GLint logSize;
GL_CALL(glGetProgramiv(shaderProgram, GL_INFO_LOG_LENGTH, &logSize));
GLchar *log = new GLchar[logSize];
GL_CALL(glGetProgramInfoLog(shaderProgram, logSize, nullptr, log));
_error = Common::String::format("Could not link shader %s: %s", name.c_str(), log);
warning("Shader:Shader(): %s", _error.c_str());
return false;
}
GL_CALL(glDetachShader(shaderProgram, vertexShader));
GL_CALL(glDetachShader(shaderProgram, fragmentShader));
GL_CALL(glDeleteShader(vertexShader));
GL_CALL(glDeleteShader(fragmentShader));
_shaderNo = Common::SharedPtr<GLuint>(new GLuint(shaderProgram), SharedPtrProgramDeleter());
_uniforms = Common::SharedPtr<UniformsMap>(new UniformsMap());
return true;
}
Shader *Shader::fromStrings(const Common::String &name, const char *vertex, const char *fragment, const char *const *attributes, int compatGLSLVersion) {
Shader *shader = new Shader;
shader->loadFromStrings(name, vertex, fragment, attributes, compatGLSLVersion);
if (shader->hasError())
error("%s", shader->getError().c_str());
return shader;
}
bool Shader::loadFromStrings(const Common::String &name, const char *vertex, const char *fragment, const char *const *attributes, int compatGLSLVersion) {
GLuint vertexShader, fragmentShader;
if (compatGLSLVersion) {
vertexShader = createCompatShader(vertex, GL_VERTEX_SHADER, name + ".vertex", compatGLSLVersion);
if (!vertexShader)
return false;
fragmentShader = createCompatShader(fragment, GL_FRAGMENT_SHADER, name + ".fragment", compatGLSLVersion);
} else {
vertexShader = createDirectShader(1, &vertex, GL_VERTEX_SHADER, name + ".vertex");
if (!vertexShader)
return false;
fragmentShader = createDirectShader(1, &fragment, GL_FRAGMENT_SHADER, name + ".fragment");
}
if (!fragmentShader)
return false;
return loadShader(name, vertexShader, fragmentShader, attributes);
}
bool Shader::loadFromStringsArray(const Common::String &name,
size_t vertexCount, const char *const *vertex,
size_t fragmentCount, const char *const *fragment,
const char *const *attributes) {
GLuint vertexShader, fragmentShader;
vertexShader = createDirectShader(vertexCount, vertex, GL_VERTEX_SHADER, name + ".vertex");
if (!vertexShader)
return false;
fragmentShader = createDirectShader(fragmentCount, fragment, GL_FRAGMENT_SHADER, name + ".fragment");
if (!fragmentShader)
return false;
return loadShader(name, vertexShader, fragmentShader, attributes);
}
Shader *Shader::fromFiles(const char *vertex, const char *fragment, const char *const *attributes, int compatGLSLVersion) {
Shader *shader = new Shader;
shader->loadFromFiles(vertex, fragment, attributes, compatGLSLVersion);
if (shader->hasError())
error("%s", shader->getError().c_str());
return shader;
}
bool Shader::loadFromFiles(const char *vertex, const char *fragment, const char *const *attributes, int compatGLSLVersion) {
GLuint vertexShader = loadShaderFromFile(vertex, "vertex", GL_VERTEX_SHADER, compatGLSLVersion);
if (!vertexShader)
return false;
GLuint fragmentShader = loadShaderFromFile(fragment, "fragment", GL_FRAGMENT_SHADER, compatGLSLVersion);
if (!fragmentShader)
return false;
Common::String name = Common::String::format("%s/%s", vertex, fragment);
return loadShader(name, vertexShader, fragmentShader, attributes);
}
void Shader::use(bool forceReload) {
if (this == _previousShader && !forceReload)
return;
// The previous shader might have had more attributes. Disable any extra ones.
if (_attributes.size() < previousNumAttributes) {
for (uint32 i = _attributes.size(); i < previousNumAttributes; ++i) {
GL_CALL(glDisableVertexAttribArray(i));
}
}
_previousShader = this;
previousNumAttributes = _attributes.size();
GL_CALL(glUseProgram(*_shaderNo));
for (uint32 i = 0; i < _attributes.size(); ++i) {
VertexAttrib &attrib = _attributes[i];
if (attrib._enabled) {
GL_CALL(glEnableVertexAttribArray(i));
GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, attrib._vbo));
GL_CALL(glVertexAttribPointer(i, attrib._size, attrib._type, attrib._normalized, attrib._stride, (const void *)attrib._pointer));
} else {
GL_CALL(glDisableVertexAttribArray(i));
switch (attrib._size) {
case 2:
GL_CALL(glVertexAttrib2fv(i, attrib._const));
break;
case 3:
GL_CALL(glVertexAttrib3fv(i, attrib._const));
break;
case 4:
GL_CALL(glVertexAttrib4fv(i, attrib._const));
break;
}
}
}
GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, 0));
}
GLuint Shader::createBuffer(GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage) {
GLuint vbo;
GL_CALL(glGenBuffers(1, &vbo));
GL_CALL(glBindBuffer(target, vbo));
GL_CALL(glBufferData(target, size, data, usage));
GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, 0));
return vbo;
}
void Shader::freeBuffer(GLuint vbo) {
GL_CALL(glDeleteBuffers(1, &vbo));
}
bool Shader::addAttribute(const char *attrib) {
// Once we are linked we can't rebind the attribute so we have to deal with its place defined by OpenGL
// As we store attribute at its OpenGL index, we will end up with empty attributes in the middle
uint32 i;
for (i = 0; i < _attributes.size(); ++i)
if (_attributes[i]._name.equals(attrib))
return true;
GLint result = -1;
GL_ASSIGN(result, glGetAttribLocation(*_shaderNo, attrib));
if (result < 0)
return false;
// Make sure we can store our new attribute
if (_attributes.size() <= (uint)result) {
for(; i < (uint)result; i++) {
_attributes.push_back(VertexAttrib(i, ""));
}
_attributes.push_back(VertexAttrib(result, attrib));
}
_attributes[result] = VertexAttrib(result, attrib);
return true;
}
VertexAttrib &Shader::getAttributeAt(uint32 idx) {
assert(idx < _attributes.size());
return _attributes[idx];
}
VertexAttrib &Shader::getAttribute(const char *attrib) {
for (uint32 i = 0; i < _attributes.size(); ++i)
if (_attributes[i]._name.equals(attrib))
return _attributes[i];
_error = Common::String::format("Could not find attribute %s in shader %s", attrib, _name.c_str());
warning("Shader: getAttribute(): %s", _error.c_str());
return _attributes[0];
}
void Shader::enableVertexAttribute(const char *attrib, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer) {
VertexAttrib &va = getAttribute(attrib);
va._enabled = true;
va._vbo = 0;
va._size = size;
va._type = type;
va._normalized = normalized;
va._stride = stride;
va._pointer = (uintptr)pointer;
}
void Shader::enableVertexAttribute(const char *attrib, GLuint vbo, GLint size, GLenum type, GLboolean normalized, GLsizei stride, uint32 offset) {
VertexAttrib &va = getAttribute(attrib);
va._enabled = true;
va._vbo = vbo;
va._size = size;
va._type = type;
va._normalized = normalized;
va._stride = stride;
va._pointer = offset;
}
void Shader::disableVertexAttribute(const char *attrib, int size, const float *data) {
VertexAttrib &va = getAttribute(attrib);
va._enabled = false;
va._size = size;
for (int i = 0; i < size; ++i)
va._const[i] = data[i];
}
void Shader::unbind() {
GL_CALL(glUseProgram(0));
_previousShader = nullptr;
// Disable all vertex attributes as well
for (uint32 i = 0; i < previousNumAttributes; ++i) {
GL_CALL(glDisableVertexAttribArray(i));
}
previousNumAttributes = 0;
}
Shader::~Shader() {
// If this is the currently active shader, unbind
if (_previousShader == this) {
unbind();
}
}
} // End of namespace OpenGL
#endif

270
graphics/opengl/shader.h Normal file
View File

@@ -0,0 +1,270 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRAPHICS_OPENGL_SHADER_H
#define GRAPHICS_OPENGL_SHADER_H
#include "common/file.h"
#include "common/array.h"
#include "common/ptr.h"
#include "math/matrix3.h"
#include "math/matrix4.h"
#include "math/vector2d.h"
#include "math/vector3d.h"
#include "math/vector4d.h"
#include "graphics/opengl/debug.h"
#include "graphics/opengl/system_headers.h"
namespace OpenGL {
struct VertexAttrib {
VertexAttrib(uint32 idx, const char *name) :
_enabled(false), _idx(idx), _name(name), _vbo(0), _size(0),
_type(GL_FLOAT), _normalized(false), _stride(0), _pointer(0) {}
bool _enabled;
uint32 _idx;
Common::String _name;
GLuint _vbo;
GLint _size;
GLenum _type;
bool _normalized;
GLsizei _stride;
uintptr _pointer;
float _const[4];
};
class Shader {
typedef Common::HashMap<Common::String, GLint> UniformsMap;
public:
Shader();
~Shader();
Shader *clone() {
return new Shader(*this);
}
void use(bool forceReload = false);
bool setUniform(const Common::String &uniform, const Math::Matrix4 &m) {
GLint pos = getUniformLocation(uniform);
if (pos != -1) {
use();
GL_CALL(glUniformMatrix4fv(pos, 1, GL_FALSE, m.getData()));
return true;
} else {
return false;
}
}
bool setUniform(const Common::String &uniform, const Math::Matrix3 &m) {
GLint pos = getUniformLocation(uniform);
if (pos != -1) {
use();
GL_CALL(glUniformMatrix3fv(pos, 1, GL_FALSE, m.getData()));
return true;
} else {
return false;
}
}
bool setUniform(const Common::String &uniform, const Math::Vector4d &v) {
GLint pos = getUniformLocation(uniform);
if (pos != -1) {
use();
GL_CALL(glUniform4fv(pos, 1, v.getData()));
return true;
} else {
return false;
}
}
bool setUniform(const Common::String &uniform, const Math::Vector3d &v) {
GLint pos = getUniformLocation(uniform);
if (pos != -1) {
use();
GL_CALL(glUniform3fv(pos, 1, v.getData()));
return true;
} else {
return false;
}
}
bool setUniform(const Common::String &uniform, const Math::Vector2d &v) {
GLint pos = getUniformLocation(uniform);
if (pos != -1) {
use();
GL_CALL(glUniform2fv(pos, 1, v.getData()));
return true;
} else {
return false;
}
}
bool setUniform(const Common::String &uniform, unsigned int x) {
GLint pos = getUniformLocation(uniform);
if (pos != -1) {
use();
GL_CALL(glUniform1i(pos, x));
return true;
} else {
return false;
}
}
bool setUniform(const Common::String &uniform, const int size, const int *array) {
GLint pos = getUniformLocation(uniform);
if (pos != -1) {
use();
GL_CALL(glUniform1iv(pos, size, array));
return true;
} else {
return false;
}
}
// Different name to avoid overload ambiguity
bool setUniform1f(const Common::String &uniform, float f) {
GLint pos = getUniformLocation(uniform);
if (pos != -1) {
use();
GL_CALL(glUniform1f(pos, f));
return true;
} else {
return false;
}
}
GLint getUniformLocation(const Common::String &uniform) const {
UniformsMap::iterator kv = _uniforms->find(uniform);
if (kv == _uniforms->end()) {
GLint ret;
GL_ASSIGN(ret, glGetUniformLocation(*_shaderNo, uniform.c_str()));
_uniforms->setVal(uniform, ret);
return ret;
} else {
return kv->_value;
}
}
void enableVertexAttribute(const char *attrib, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer);
void enableVertexAttribute(const char *attrib, GLuint vbo, GLint size, GLenum type, GLboolean normalized, GLsizei stride, uint32 offset);
void disableVertexAttribute(const char *attrib, int size, const float *data);
template <int r>
void disableVertexAttribute(const char *attrib, const Math::Matrix<r,1> &m) {
disableVertexAttribute(attrib, r, m.getData());
}
bool addAttribute(const char *attrib);
VertexAttrib & getAttributeAt(uint32 idx);
VertexAttrib & getAttribute(const char *attrib);
static GLuint createBuffer(GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage = GL_STATIC_DRAW);
static void freeBuffer(GLuint vbo);
/**
* Creates a shader object from files
*
* For shader files (used by games), we used to require GLSL 1.20, this is the default for compatGLSLVersion.
* The GLSL version is converted to GLSL ES version if needed.
*
* @param name The name of the shader for errors messages
* @param vertex The vertex shader code
* @param fragment The fragment shader code
* @param attributes The vertex attributes names for indexing
* @param compatGLSLVersion The GLSL version required: 0 for no preprocessing, 100 for GLSL 1.00 and so on
*
* @return the shader object created
*/
static Shader *fromFiles(const char *vertex, const char *fragment, const char *const *attributes, int compatGLSLVersion = 120);
static Shader *fromFiles(const char *shared, const char *const *attributes, int compatGLSLVersion = 120) {
return fromFiles(shared, shared, attributes, compatGLSLVersion);
}
/**
* Creates a shader object from strings
*
* Shader strings are usually included in backends and don't need preprocessing, this is the default for compatGLSLVersion.
* The GLSL version is converted to GLSL ES version if needed.
*
* @param name The name of the shader for errors messages
* @param vertex The vertex shader code
* @param fragment The fragment shader code
* @param attributes The vertex attributes names for indexing
* @param compatGLSLVersion The GLSL version required: 0 for no preprocessing, 100 for GLSL 1.00 and so on
*
* @return the shader object created
*/
static Shader *fromStrings(const Common::String &name, const char *vertex, const char *fragment, const char *const *attributes, int compatGLSLVersion = 0);
bool loadFromFiles(const char *vertex, const char *fragment, const char *const *attributes, int compatGLSLVersion = 120);
bool loadFromStrings(const Common::String &name, const char *vertex, const char *fragment, const char *const *attributes, int compatGLSLVersion = 0);
/**
* Creates a shader object from strings arrays
*
* Everything is loaded directly without any preprocessing.
*
* @param name The name of the shader for errors messages
* @param vertexCount The number of vertex shader code parts
* @param vertex The vertex shader code parts
* @param fragmentCount The number of fragment shader code parts
* @param fragment The fragment shader code parts
* @param attributes The vertex attributes names for indexing
*
* @return the loading status
*/
bool loadFromStringsArray(const Common::String &name,
size_t vertexCount, const char *const *vertex,
size_t fragmentCount, const char *const *fragment,
const char *const *attributes);
void unbind();
Common::String &getError() { return _error; }
bool hasError() { return !_error.empty(); }
private:
bool loadShader(const Common::String &name, GLuint vertexShader, GLuint fragmentShader, const char *const *attributes);
GLuint createCompatShader(const char *shaderSource, GLenum shaderType, const Common::String &name, int compatGLSLVersion);
GLuint createDirectShader(size_t shaderSourcesCount, const char *const *shaderSources, GLenum shaderType, const Common::String &name);
GLuint loadShaderFromFile(const char *base, const char *extension, GLenum shaderType, int compatGLSLVersion);
// Since this class is cloned using the implicit copy constructor,
// a reference counting pointer is used to ensure deletion of the OpenGL
// program upon destruction of the last clone.
Common::SharedPtr<GLuint> _shaderNo;
Common::String _name;
Common::Array<VertexAttrib> _attributes;
Common::SharedPtr<UniformsMap> _uniforms;
static Shader *_previousShader;
static uint32 previousNumAttributes;
Common::String _error;
};
} // End of namespace OpenGL
#endif

View File

@@ -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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRAPHICS_OPENGL_SYSTEM_HEADERS_H
#define GRAPHICS_OPENGL_SYSTEM_HEADERS_H
#include "common/scummsys.h"
// On macOS 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. macOS 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 macOS. 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
// Don't include any OpenGL stuff if we didn't enable it
#ifdef USE_OPENGL
#ifdef USE_GLAD
#include "graphics/opengl/glad.h"
#elif USE_FORCED_GLES2
#define GL_GLEXT_PROTOTYPES
#if defined(IPHONE)
#include <OpenGLES/ES2/gl.h>
#include <OpenGLES/ES2/glext.h>
#else
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#endif
#undef GL_GLEXT_PROTOTYPES
#elif USE_FORCED_GLES
#define GL_GLEXT_PROTOTYPES
#if defined(IPHONE)
#include <OpenGLES/ES1/gl.h>
#include <OpenGLES/ES1/glext.h>
#else
#include <GLES/gl.h>
#include <GLES/glext.h>
#endif
#undef GL_GLEXT_PROTOTYPES
#else
#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glext.h>
#undef GL_GLEXT_PROTOTYPES
#endif
#endif
#if !defined(GL_TEXTURE_MAX_LEVEL) && defined(GL_TEXTURE_MAX_LEVEL_APPLE)
#define GL_TEXTURE_MAX_LEVEL GL_TEXTURE_MAX_LEVEL_APPLE
#endif
#if !defined(GL_BGRA) && defined(GL_BGRA_EXT)
#define GL_BGRA GL_BGRA_EXT
#endif
#if !defined(GL_UNPACK_ROW_LENGTH)
// The Android SDK does not declare GL_UNPACK_ROW_LENGTH_EXT
#define GL_UNPACK_ROW_LENGTH 0x0CF2
#endif
#if !defined(GL_MAX_SAMPLES)
// The Android SDK and SDL1 don't declare GL_MAX_SAMPLES
#define GL_MAX_SAMPLES 0x8D57
#endif
#if !defined(GL_STACK_OVERFLOW)
#define GL_STACK_OVERFLOW 0x0503
#endif
#if !defined(GL_STACK_UNDERFLOW)
#define GL_STACK_UNDERFLOW 0x0504
#endif
#if !defined(GL_CLAMP_TO_BORDER)
#define GL_CLAMP_TO_BORDER 0x812D
#endif
#if !defined(GL_MIRRORED_REPEAT)
#define GL_MIRRORED_REPEAT 0x8370
#endif
#if !defined(GL_DRAW_FRAMEBUFFER_BINDING)
#define GL_DRAW_FRAMEBUFFER_BINDING 0x8CA6
#endif
#if !defined(GL_DEPTH_COMPONENT24)
// For GLES2 with GL_OES_depth24
#define GL_DEPTH_COMPONENT24 0x81A6
#endif
#if !defined(GL_DEPTH_STENCIL)
// For GLES2 with GL_OES_packed_depth_stencil
#define GL_DEPTH_STENCIL 0x84F9
#endif
#if !defined(GL_DEPTH24_STENCIL8)
// For GLES2 with GL_OES_packed_depth_stencil
#define GL_DEPTH24_STENCIL8 0x88F0
#endif
#if !defined(GL_DEPTH_STENCIL_ATTACHMENT)
// For WebGL: see https://github.com/emscripten-core/emscripten/issues/4832
#define GL_DEPTH_STENCIL_ATTACHMENT 0x821A
#endif
#endif

281
graphics/opengl/texture.cpp Normal file
View File

@@ -0,0 +1,281 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "graphics/opengl/system_headers.h"
#ifdef USE_OPENGL
#include "graphics/opengl/texture.h"
#include "graphics/opengl/debug.h"
#include "common/algorithm.h"
#include "common/endian.h"
#include "common/rect.h"
#include "common/textconsole.h"
namespace OpenGL {
Texture::Texture(GLenum glIntFormat, GLenum glFormat, GLenum glType, bool autoCreate)
: _glIntFormat(glIntFormat), _glFormat(glFormat), _glType(glType),
_width(0), _height(0), _logicalWidth(0), _logicalHeight(0),
_flip(false), _rotation(Common::kRotationNormal),
_texCoords(), _glFilter(GL_NEAREST), _glTexture(0) {
if (autoCreate)
create();
}
Texture::~Texture() {
GL_CALL_SAFE(glDeleteTextures, (1, &_glTexture));
}
void Texture::enableLinearFiltering(bool enable) {
if (enable) {
_glFilter = GL_LINEAR;
} else {
_glFilter = GL_NEAREST;
}
if (!bind()) {
return;
}
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _glFilter));
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _glFilter));
}
void Texture::setWrapMode(WrapMode wrapMode) {
GLuint glwrapMode;
switch(wrapMode) {
case kWrapModeBorder:
if (OpenGLContext.textureBorderClampSupported) {
glwrapMode = GL_CLAMP_TO_BORDER;
break;
}
// fall through
case kWrapModeEdge:
if (OpenGLContext.textureEdgeClampSupported) {
glwrapMode = GL_CLAMP_TO_EDGE;
break;
} else {
#if !USE_FORCED_GLES && !USE_FORCED_GLES2
// Fallback on clamp
glwrapMode = GL_CLAMP;
#else
// This fallback should never happen in real life (GLES/GLES2 have border/edge clamp)
glwrapMode = GL_REPEAT;
#endif
break;
}
case kWrapModeMirroredRepeat:
if (OpenGLContext.textureMirrorRepeatSupported) {
glwrapMode = GL_MIRRORED_REPEAT;
break;
}
// fall through
case kWrapModeRepeat:
default:
glwrapMode = GL_REPEAT;
}
if (!bind()) {
return;
}
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, glwrapMode));
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, glwrapMode));
}
void Texture::destroy() {
if (!_glTexture) {
return;
}
GL_CALL(glDeleteTextures(1, &_glTexture));
_glTexture = 0;
}
void Texture::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(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _glFilter));
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _glFilter));
if (OpenGLContext.textureEdgeClampSupported) {
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));
} else {
#if !USE_FORCED_GLES && !USE_FORCED_GLES2
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP));
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP));
#endif
}
// 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, nullptr));
}
}
bool Texture::bind() const {
if (!_glTexture) {
return false;
}
GL_CALL(glBindTexture(GL_TEXTURE_2D, _glTexture));
return true;
}
bool Texture::setSize(uint width, uint height) {
const uint oldWidth = _width;
const uint oldHeight = _height;
_logicalWidth = width;
_logicalHeight = height;
if (!OpenGLContext.NPOTSupported) {
_width = Common::nextHigher2(width);
_height = Common::nextHigher2(height);
} else {
_width = width;
_height = height;
}
// If a size is specified, allocate memory for it.
if (width != 0 && height != 0) {
computeTexCoords();
// Allocate storage for OpenGL texture if necessary.
if (oldWidth != _width || oldHeight != _height) {
if (!bind()) {
return false;
}
bool error;
GL_CALL_CHECK(error, glTexImage2D(GL_TEXTURE_2D, 0, _glIntFormat, _width, _height,
0, _glFormat, _glType, nullptr));
if (error) {
return false;
}
}
}
return true;
}
void Texture::computeTexCoords() {
const GLfloat texWidth = (GLfloat)_logicalWidth / _width;
const GLfloat texHeight = (GLfloat)_logicalHeight / _height;
if (_flip) {
_texCoords[0] = 0;
_texCoords[1] = texHeight;
_texCoords[2] = texWidth;
_texCoords[3] = texHeight;
_texCoords[4] = 0;
_texCoords[5] = 0;
_texCoords[6] = texWidth;
_texCoords[7] = 0;
} else {
_texCoords[0] = 0;
_texCoords[1] = 0;
_texCoords[2] = texWidth;
_texCoords[3] = 0;
_texCoords[4] = 0;
_texCoords[5] = texHeight;
_texCoords[6] = texWidth;
_texCoords[7] = texHeight;
}
switch(_rotation) {
default:
case Common::kRotationNormal:
// Nothing to do
break;
case Common::kRotation90:
// LT -> LB and RB -> RT
SWAP(_texCoords[1], _texCoords[7]);
// RT -> LT and LB -> RB
SWAP(_texCoords[2], _texCoords[4]);
break;
case Common::kRotation180:
// LT -> RT and RT -> LT
SWAP(_texCoords[0], _texCoords[2]);
// RT -> RB and LB -> LT
SWAP(_texCoords[1], _texCoords[5]);
// LT -> LB and RB -> RT
SWAP(_texCoords[3], _texCoords[7]);
// LT -> RT and RT -> LT
SWAP(_texCoords[4], _texCoords[6]);
break;
case Common::kRotation270:
// LT -> RT and RB -> LB
SWAP(_texCoords[0], _texCoords[6]);
// RT -> RB and LB -> LT
SWAP(_texCoords[3], _texCoords[5]);
break;
}
}
void Texture::updateArea(const Common::Rect &area, const Graphics::Surface &src) {
// Set the texture on the active texture unit.
if (!bind()) {
return;
}
// 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 boundaries 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(glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
GL_CALL(glTexSubImage2D(GL_TEXTURE_2D, 0, 0, area.top, src.w, area.height(),
_glFormat, _glType, src.getBasePtr(0, area.top)));
}
} // End of namespace OpenGL
#endif

198
graphics/opengl/texture.h Normal file
View File

@@ -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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRAPHICS_OPENGL_TEXTURE_H
#define GRAPHICS_OPENGL_TEXTURE_H
#include "graphics/opengl/system_headers.h"
#include "graphics/opengl/context.h"
#include "graphics/pixelformat.h"
#include "graphics/surface.h"
#include "common/rect.h"
#include "common/rotationmode.h"
namespace OpenGL {
enum WrapMode {
kWrapModeBorder,
kWrapModeEdge,
kWrapModeRepeat,
kWrapModeMirroredRepeat
};
/**
* A simple GL texture object abstraction.
*
* This is used for low-level GL texture handling.
*/
class Texture {
public:
/**
* Constrcut a new GL texture object.
*
* @param glIntFormat The internal format to use.
* @param glFormat The input format.
* @param glType The input type.
*/
Texture(GLenum glIntFormat, GLenum glFormat, GLenum glType, bool autoCreate = true);
~Texture();
/**
* 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); }
/**
* Enable or disable linear texture filtering.
*
* @param enable true to enable and false to disable.
*/
void setWrapMode(WrapMode wrapMode);
/**
* Destroy the OpenGL texture name.
*/
void destroy();
/**
* Create the OpenGL texture name.
*/
void create();
/**
* Bind the texture to the active texture unit.
*
* @return Whether a texture really exists
*/
bool 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.
* @return Whether the call was successful
*/
bool setSize(uint width, uint height);
/**
* Sets the flip and rotate parameters of the texture
*
* @param flip Whether to flip vertically the texture when displaying it.
*/
void setFlip(bool flip) { if (_flip != flip) { _flip = flip; computeTexCoords(); } }
/**
* Sets the rotate parameter of the texture
*
* @param rotation How to rotate the texture
*/
void setRotation(Common::RotationMode rotation) { if (_rotation != rotation) { _rotation = rotation; computeTexCoords(); } }
/**
* 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; }
static inline const Graphics::PixelFormat getRGBPixelFormat() {
return Graphics::PixelFormat::createFormatRGB24();
}
static inline const Graphics::PixelFormat getRGBAPixelFormat() {
return Graphics::PixelFormat::createFormatRGBA32();
}
static inline const Graphics::PixelFormat getBGRAPixelFormat() {
return Graphics::PixelFormat::createFormatBGRA32();
}
protected:
void computeTexCoords();
const GLenum _glIntFormat;
const GLenum _glFormat;
const GLenum _glType;
uint _width, _height;
uint _logicalWidth, _logicalHeight;
bool _flip;
Common::RotationMode _rotation;
GLfloat _texCoords[4*2];
GLint _glFilter;
GLuint _glTexture;
};
} // End of namespace OpenGL
#endif