diff options
Diffstat (limited to 'backends/graphics/opengl/shader.cpp')
-rw-r--r-- | backends/graphics/opengl/shader.cpp | 335 |
1 files changed, 335 insertions, 0 deletions
diff --git a/backends/graphics/opengl/shader.cpp b/backends/graphics/opengl/shader.cpp new file mode 100644 index 0000000000..0b4c677d70 --- /dev/null +++ b/backends/graphics/opengl/shader.cpp @@ -0,0 +1,335 @@ +/* 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 texture;\n" + "\n" + "void main(void) {\n" + "\tgl_FragColor = blendColor * texture2D(texture, texCoord);\n" + "}\n"; + +const char *const g_lookUpFragmentShader = + "varying vec2 texCoord;\n" + "varying vec4 blendColor;\n" + "\n" + "uniform sampler2D texture;\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(texture, 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) { + GLshader handle; + GL_ASSIGN(handle, glCreateShader(shaderType)); + if (!handle) { + return 0; + } + + const char *const sources[2] = { + g_precisionDefines, + source + }; + + GL_CALL(glShaderSource(handle, ARRAYSIZE(sources), sources, 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("texture", 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 |