stk-code_catmod/src/graphics/shader_files_manager.cpp

296 lines
9.8 KiB
C++

// 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 <fstream>
#include <sstream>
// ----------------------------------------------------------------------------
/** 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