scummvm/backends/graphics/opengl/shader.cpp
Filippos Karapetis d524d36a6a OPENGL: Specify a GLSL version tag, and rename reserved keywords
The GLSL version code has been taken from ResidualVM. The variable
'texture' is now a reserved keyword in GLSL 3.00, so it has been
renamed. This fixes compilation issues in AmigaOS4 (PR 1554).
2019-07-14 16:10:55 +03:00

338 lines
8.6 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 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<ShaderUniformValue>(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