296 lines
9.8 KiB
C++
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
|