// SuperTuxKart - a fun racing game with go-kart // Copyright (C) 2016 SuperTuxKart-Team // // 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, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #ifndef SERVER_ONLY #include "graphics/shader_files_manager.hpp" #include "config/user_config.hpp" #include "config/stk_config.hpp" #include "graphics/central_settings.hpp" #include "graphics/graphics_restrictions.hpp" #include "guiengine/message_queue.hpp" #include "io/file_manager.hpp" #include "utils/file_utils.hpp" #include "utils/log.hpp" #include "utils/string_utils.hpp" #include #include // ---------------------------------------------------------------------------- /** Returns a string with the content of header.txt (which contains basic * shader defines). */ const std::string& ShaderFilesManager::getHeader() { // Stores the content of header.txt, to avoid reading this file repeatedly. static std::string shader_header; // Only read file first time if (shader_header.empty()) { std::ifstream stream(FileUtils::getPortableReadingPath( file_manager->getShader("header.txt")), std::ios::in); if (stream.is_open()) { std::string line = ""; while (std::getline(stream, line)) shader_header += "\n" + line; stream.close(); } } // if shader_header.empty() return shader_header; } // getHeader // ---------------------------------------------------------------------------- void ShaderFilesManager::readFile(const std::string& file, std::ostringstream& code, bool not_header) { std::string path = FileUtils::getPortableReadingPath( ((file.find('/') != std::string::npos || file.find('\\') != std::string::npos) && not_header) ? file : file_manager->getShader(file)); std::ifstream stream(path, std::ios::in); if (!stream.is_open()) { Log::error("ShaderFilesManager", "Can not open '%s'.", file.c_str()); return; } const std::string stk_include = "#stk_include"; std::string line; while (std::getline(stream, line)) { const std::size_t pos = line.find(stk_include); // load the custom file pointed by the #stk_include directive // we only look for #stk_include in official shader directory if (pos != std::string::npos) { // find the start " std::size_t pos = line.find("\""); if (pos == std::string::npos) { Log::error("ShaderFilesManager", "Invalid #stk_include" " line: '%s'.", line.c_str()); continue; } std::string filename = line.substr(pos + 1); // find the end " pos = filename.find("\""); if (pos == std::string::npos) { Log::error("ShaderFilesManager", "Invalid #stk_include" " line: '%s'.", line.c_str()); continue; } filename = filename.substr(0, pos); // read the whole include file readFile(filename, code, false/*not_header*/); } else { code << "\n" << line; } } stream.close(); } // ---------------------------------------------------------------------------- /** Loads a single shader. This is NOT cached, use addShaderFile for that. * \param file Filename of the shader to load. * \param type Type of the shader. */ ShaderFilesManager::SharedShader ShaderFilesManager::loadShader (const std::string& full_path, unsigned type) { GLuint* ss_ptr = new GLuint; *ss_ptr = glCreateShader(type); SharedShader ss(ss_ptr, [](GLuint* ss) { glDeleteShader(*ss); delete ss; }); std::ostringstream code; #if !defined(USE_GLES2) code << "#version " << CVS->getGLSLVersion()<<"\n"; #else if (CVS->isGLSL()) code << "#version 300 es\n"; #endif #if !defined(USE_GLES2) // Some drivers report that the compute shaders extension is available, // but they report only OpenGL 3.x version, and thus these extensions // must be enabled manually. Otherwise the shaders compilation will fail // because STK tries to use extensions which are available, but disabled // by default. if (type == GL_COMPUTE_SHADER) { if (CVS->isARBComputeShaderUsable()) code << "#extension GL_ARB_compute_shader : enable\n"; if (CVS->isARBImageLoadStoreUsable()) code << "#extension GL_ARB_shader_image_load_store : enable\n"; if (CVS->isARBArraysOfArraysUsable()) code << "#extension GL_ARB_arrays_of_arrays : enable\n"; } #endif if (CVS->isARBExplicitAttribLocationUsable()) { #if !defined(USE_GLES2) code << "#extension GL_ARB_explicit_attrib_location : enable\n"; #endif code << "#define Explicit_Attrib_Location_Usable\n"; } if (GraphicsRestrictions::isDisabled (GraphicsRestrictions::GR_CORRECT_10BIT_NORMALIZATION)) { code << "#define Converts_10bit_Vector\n"; } code << "//" << full_path << "\n"; if (!CVS->isARBUniformBufferObjectUsable()) code << "#define UBO_DISABLED\n"; if (!CVS->isARBTextureBufferObjectUsable()) code << "#define TBO_DISABLED\n"; if (CVS->needsVertexIdWorkaround()) code << "#define Needs_Vertex_Id_Workaround\n"; if (CVS->isDeferredEnabled()) code << "#define Advanced_Lighting_Enabled\n"; #if !defined(USE_GLES2) // shader compilation fails with some drivers if there is no precision // qualifier if (type == GL_FRAGMENT_SHADER) code << "precision highp float;\n"; #else int range[2], precision; glGetShaderPrecisionFormat(GL_FRAGMENT_SHADER, GL_HIGH_FLOAT, range, &precision); if (precision > 0) { code << "precision highp float;\n"; code << "precision highp sampler2DArrayShadow;\n"; code << "precision highp sampler2DArray;\n"; } else { code << "precision mediump float;\n"; code << "precision mediump sampler2DArrayShadow;\n"; code << "precision mediump sampler2DArray;\n"; } #endif code << "#define MAX_BONES " << stk_config->m_max_skinning_bones << "\n"; code << getHeader(); readFile(full_path, code); Log::info("ShaderFilesManager", "Compiling shader: %s", full_path.c_str()); const std::string &source = code.str(); char const *source_pointer = source.c_str(); int len = (int)source.size(); glShaderSource(*ss, 1, &source_pointer, &len); glCompileShader(*ss); GLint result = GL_FALSE; glGetShaderiv(*ss, GL_COMPILE_STATUS, &result); if (result == GL_FALSE) { // failed to compile int info_length; Log::error("ShaderFilesManager", "Error in shader %s", full_path.c_str()); glGetShaderiv(*ss, GL_INFO_LOG_LENGTH, &info_length); if (info_length < 0) info_length = 1024; char *error_message = new char[info_length]; error_message[0] = 0; glGetShaderInfoLog(*ss, info_length, NULL, error_message); Log::error("ShaderFilesManager", error_message); delete[] error_message; if (UserConfigParams::m_artist_debug_mode) { core::stringw err = StringUtils::insertValues(L"Shader file %s" " failed to compile, check stdout.log or console for details", full_path.c_str()); MessageQueue::add(MessageQueue::MT_ERROR, err); } return NULL; } glGetError(); return ss; } // loadShader // ---------------------------------------------------------------------------- /** Loads a single shader file, and add it to the loaded (cached) list * \param file Filename of the shader to load. * \param type Type of the shader. */ ShaderFilesManager::SharedShader ShaderFilesManager::addShaderFile (const std::string& full_path, unsigned type) { #ifdef DEBUG // Make sure no duplicated shader is added somewhere else auto i = m_shader_files_loaded.find(full_path); assert(i == m_shader_files_loaded.end()); #endif SharedShader ss = loadShader(full_path, type); m_shader_files_loaded[full_path] = ss; return ss; } // addShaderFile // ---------------------------------------------------------------------------- /** Get a shader file. If the shader is not already in the cache it will be * loaded and cached. * \param file Filename of the shader to load. * \param type Type of the shader. */ ShaderFilesManager::SharedShader ShaderFilesManager::getShaderFile (const std::string &file, unsigned type) { const std::string full_path = (file.find('/') != std::string::npos || file.find('\\') != std::string::npos) ? file : std::string(file_manager->getFileSystem()->getAbsolutePath (file_manager->getShadersDir().c_str()).c_str()) + file; // found in cache auto it = m_shader_files_loaded.find(full_path); if (it != m_shader_files_loaded.end()) return it->second; // add to the cache now return addShaderFile(full_path, type); } // getShaderFile #endif // !SERVER_ONLY