// SuperTuxKart - a fun racing game with go-kart // Copyright (C) 2014-2015 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. /** \page shaders_overview Shaders Overview \section shader_declaration Shader declaration You need to create a class for each shader in shaders.cpp This class should inherit from the template ShaderHelper<>. The template first parameter is the shader class being declared and the following ones are the types of every uniform (except samplers) required by the shaders. The template inheritance will provide the shader with a setUniforms() variadic function which calls the glUniform*() that pass uniforms value to the shader according to the type given as parameter to the template. The class constructor is used to \li \ref shader_declaration_compile \li \ref shader_declaration_uniform_names \li \ref shader_declaration_bind_texture_unit Of course it's possible to use the constructor to declare others things if needed. \subsection shader_declaration_compile Compile the shader The LoadProgram() function is provided to ease shader compilation and link. It takes a flat sequence of SHADER_TYPE, filename pairs that will be linked together. This way you can add any shader stage you want (geometry, domain/hull shader) It is highly recommended to use explicit attribute location for a program input. However as not all hardware support this extension, default location are provided for input whose name is either Position (location 0) or Normal (location 1) or Texcoord (location 3) or Color (location 2) or SecondTexcoord (location 4). You can use these predefined name and location in your vao for shader that needs GL pre 3.3 support. \subsection shader_declaration_uniform_names Declare uniforms Use the assignUniforms() function to pass name of the uniforms in the program. The order of name declaration is the same as the argument passed to setUniforms function. \subsection shader_declaration_bind_texture_unit Bind texture unit and name Texture are optional but if you have one, you must give them determined texture unit (up to 32). You can do this using the assignTextureUnit function that takes pair of texture unit and sampler name as argument. \section shader_usage Shader's class are singleton that can be retrieved using ShaderClassName::getInstance() which automatically creates an instance the first time it is called. As the program id of a shader instance is public it can be used to bind a program : \code glUseProgram(MyShaderClass::getInstance()->Program); \endcode To set uniforms use the automatically generated function setUniforms: \code MyShaderClass::getInstance()->setUniforms(Args...) \endcode A Vertex Array must be bound (VAO creation process is currently left to the reader) : \code glBindVertexAttrib(vao); \endcode To actually perform the rendering you also need to call a glDraw* function (left to the reader as well) : \code glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_SHORT); \endcode */ #include "graphics/shaders.hpp" #include "graphics/callbacks.hpp" #include "graphics/central_settings.hpp" #include "graphics/glwrap.hpp" #include "graphics/gpu_particles.hpp" #include "graphics/irr_driver.hpp" #include "graphics/shared_gpu_objects.hpp" #include "io/file_manager.hpp" #include "utils/log.hpp" #include #include bool Shaders::m_has_been_initialised = false; video::IShaderConstantSetCallBack *Shaders::m_callbacks[ES_COUNT]; int Shaders::m_shaders[ES_COUNT]; // Use macro FOREACH_SHADER from shaders.hpp to create an array // with all shader names. #define STR(a) #a, const char *Shaders::shader_names[] = { FOREACH_SHADER(STR) }; #undef STR using namespace video; void Shaders::init() { assert(!m_has_been_initialised); // Callbacks memset(m_callbacks, 0, sizeof(m_callbacks)); m_callbacks[ES_WATER] = new WaterShaderProvider(); m_callbacks[ES_GRASS] = new GrassShaderProvider(); m_callbacks[ES_MOTIONBLUR] = new MotionBlurProvider(); m_callbacks[ES_MIPVIZ] = new MipVizProvider(); m_callbacks[ES_DISPLACE] = new DisplaceProvider(); for (s32 i = 0; i < ES_COUNT; i++) m_shaders[i] = -1; loadShaders(); m_has_been_initialised = true; } // init // ---------------------------------------------------------------------------- /** Frees all memory used by the shader manager. */ void Shaders::destroy() { assert(m_has_been_initialised); u32 i; for (i = 0; i < ES_COUNT; i++) { if (i == ES_GAUSSIAN3V || !m_callbacks[i]) continue; m_callbacks[i]->drop(); m_callbacks[i] = NULL; } m_has_been_initialised = false; SharedGPUObjects::reset(); } // destroy // ---------------------------------------------------------------------------- // Shader loading related hook static std::string loadHeader() { std::string result; std::ifstream Stream("header.txt", std::ios::in); if (Stream.is_open()) { std::string Line = ""; while (getline(Stream, Line)) result += "\n" + Line; Stream.close(); } return result; } // loadHeader // ---------------------------------------------------------------------------- // Mostly from shader tutorial GLuint loadShader(const char * file, unsigned type) { GLuint Id = glCreateShader(type); char versionString[20]; #if !defined(USE_GLES2) sprintf(versionString, "#version %d\n", CVS->getGLSLVersion()); #else if (CVS->isGLSL()) sprintf(versionString, "#version 300 es\n"); #endif std::string Code = versionString; if (CVS->isAMDVertexShaderLayerUsable()) Code += "#extension GL_AMD_vertex_shader_layer : enable\n"; if (CVS->isAZDOEnabled()) { Code += "#extension GL_ARB_bindless_texture : enable\n"; Code += "#define Use_Bindless_Texture\n"; } std::ifstream Stream(file, std::ios::in); Code += "//" + std::string(file) + "\n"; if (!CVS->isARBUniformBufferObjectUsable()) Code += "#define UBO_DISABLED\n"; if (CVS->isAMDVertexShaderLayerUsable()) Code += "#define VSLayer\n"; if (CVS->needsRGBBindlessWorkaround()) Code += "#define SRGBBindlessFix\n"; Code += loadHeader(); if (Stream.is_open()) { std::string Line = ""; while (getline(Stream, Line)) Code += "\n" + Line; Stream.close(); } GLint Result = GL_FALSE; int InfoLogLength; Log::info("GLWrap", "Compiling shader : %s", file); char const * SourcePointer = Code.c_str(); int length = (int)strlen(SourcePointer); glShaderSource(Id, 1, &SourcePointer, &length); glCompileShader(Id); glGetShaderiv(Id, GL_COMPILE_STATUS, &Result); if (Result == GL_FALSE) { Log::error("GLWrap", "Error in shader %s", file); glGetShaderiv(Id, GL_INFO_LOG_LENGTH, &InfoLogLength); if (InfoLogLength<0) InfoLogLength = 1024; char *ErrorMessage = new char[InfoLogLength]; ErrorMessage[0] = 0; glGetShaderInfoLog(Id, InfoLogLength, NULL, ErrorMessage); Log::error("GLWrap", ErrorMessage); delete[] ErrorMessage; } glGetError(); return Id; } // loadShader // ---------------------------------------------------------------------------- void Shaders::loadShaders() { const std::string &dir = file_manager->getAsset(FileManager::SHADER, ""); IGPUProgrammingServices * const gpu = irr_driver->getVideoDriver() ->getGPUProgrammingServices(); #define glsl(a, b, c) gpu->addHighLevelShaderMaterialFromFiles((a).c_str(), (b).c_str(), (IShaderConstantSetCallBack*) c) #define glslmat(a, b, c, d) gpu->addHighLevelShaderMaterialFromFiles((a).c_str(), (b).c_str(), (IShaderConstantSetCallBack*) c, d) #define glsl_noinput(a, b) gpu->addHighLevelShaderMaterialFromFiles((a).c_str(), (b).c_str(), (IShaderConstantSetCallBack*) 0) // Save previous shaders (used in case some shaders don't compile) int saved_shaders[ES_COUNT]; memcpy(saved_shaders, m_shaders, sizeof(m_shaders)); #if !defined(USE_GLES2) std::string name = "pass"; #else std::string name = "pass_gles"; #endif // Ok, go m_shaders[ES_NORMAL_MAP] = glsl_noinput(dir + name + ".vert", dir + name + ".frag"); m_shaders[ES_NORMAL_MAP_LIGHTMAP] = glsl_noinput(dir + name + ".vert", dir + name + ".frag"); m_shaders[ES_SKYBOX] = glslmat(dir + name + ".vert", dir + name + ".frag", m_callbacks[ES_SKYBOX], EMT_TRANSPARENT_ALPHA_CHANNEL); m_shaders[ES_SPLATTING] = glsl_noinput(dir + name + ".vert", dir + name + ".frag"); m_shaders[ES_WATER] = glslmat(dir + name + ".vert", dir + name + ".frag", m_callbacks[ES_WATER], EMT_TRANSPARENT_ALPHA_CHANNEL); m_shaders[ES_WATER_SURFACE] = glsl(dir + name + ".vert", dir + name + ".frag", m_callbacks[ES_WATER]); m_shaders[ES_SPHERE_MAP] = glsl_noinput(dir + name + ".vert", dir + name + ".frag"); m_shaders[ES_GRASS] = glslmat(dir + name + ".vert", dir + name + ".frag", m_callbacks[ES_GRASS], EMT_TRANSPARENT_ALPHA_CHANNEL); m_shaders[ES_GRASS_REF] = glslmat(dir + name + ".vert", dir + name + ".frag", m_callbacks[ES_GRASS], EMT_TRANSPARENT_ALPHA_CHANNEL_REF); m_shaders[ES_MOTIONBLUR] = glsl(dir + name + ".vert", dir + name + ".frag", m_callbacks[ES_MOTIONBLUR]); m_shaders[ES_GAUSSIAN3H] = glslmat(dir + name + ".vert", dir + name + ".frag", m_callbacks[ES_GAUSSIAN3H], EMT_SOLID); m_shaders[ES_GAUSSIAN3V] = glslmat(dir + name + ".vert", dir + name + ".frag", m_callbacks[ES_GAUSSIAN3V], EMT_SOLID); m_shaders[ES_MIPVIZ] = glslmat(dir + name + ".vert", dir + name + ".frag", m_callbacks[ES_MIPVIZ], EMT_SOLID); m_shaders[ES_OBJECTPASS] = glsl_noinput(dir + name + ".vert", dir + name + ".frag"); m_shaders[ES_OBJECT_UNLIT] = glsl_noinput(dir + name + ".vert", dir + name + ".frag"); m_shaders[ES_OBJECTPASS_REF] = glsl_noinput(dir + name + ".vert", dir + name + ".frag"); m_shaders[ES_OBJECTPASS_RIMLIT] = glsl_noinput(dir + name + ".vert", dir + name + ".frag"); m_shaders[ES_DISPLACE] = glslmat(dir + name + ".vert", dir + name + ".frag", m_callbacks[ES_DISPLACE], EMT_TRANSPARENT_ALPHA_CHANNEL); // Check that all successfully loaded for (s32 i = 0; i < ES_COUNT; i++) { // Old Intel Windows drivers fail here. // It's an artist option, so not necessary to play. if (i == ES_MIPVIZ) continue; check(i); } #undef glsl #undef glslmat #undef glsl_noinput // In case we're reloading and a shader didn't compile: keep the previous, working one for (s32 i = 0; i < ES_COUNT; i++) { if (m_shaders[i] == -1) m_shaders[i] = saved_shaders[i]; } initGL(); SharedGPUObjects::init(); } // loadShaders // ---------------------------------------------------------------------------- void Shaders::check(const int num) { if (m_shaders[num] == -1) { Log::error("shaders", "Shader %s failed to load. Update your drivers, if the issue " "persists, report a bug to us.", shader_names[num] + 3); } } // check // ============================================================================ // Solid Normal and depth pass shaders Shaders::ObjectPass1Shader::ObjectPass1Shader() { loadProgram(OBJECT, GL_VERTEX_SHADER, "object_pass.vert", GL_FRAGMENT_SHADER, "object_pass1.frag"); assignUniforms("ModelMatrix", "InverseModelMatrix"); assignSamplerNames(0, "tex", ST_TRILINEAR_ANISOTROPIC_FILTERED); } // ObjectPass1Shader // ============================================================================ // Solid Lit pass shaders Shaders::ObjectPass2Shader::ObjectPass2Shader() { loadProgram(OBJECT, GL_VERTEX_SHADER, "object_pass.vert", GL_FRAGMENT_SHADER, "object_pass2.frag"); assignUniforms("ModelMatrix", "texture_trans", "color_change"); assignSamplerNames(0, "DiffuseMap", ST_NEAREST_FILTERED, 1, "SpecularMap", ST_NEAREST_FILTERED, 2, "SSAO", ST_BILINEAR_FILTERED, 3, "Albedo", ST_TRILINEAR_ANISOTROPIC_FILTERED, 4, "SpecMap", ST_TRILINEAR_ANISOTROPIC_FILTERED, 5, "colorization_mask", ST_TRILINEAR_ANISOTROPIC_FILTERED); } // ObjectPass2Shader // ============================================================================ Shaders::SkinnedMeshPass1Shader::SkinnedMeshPass1Shader() { loadProgram(OBJECT, GL_VERTEX_SHADER, "skinning.vert", GL_FRAGMENT_SHADER, "object_pass1.frag"); assignUniforms("ModelMatrix", "InverseModelMatrix", "skinning_offset"); assignSamplerNames(0, "tex", ST_TRILINEAR_ANISOTROPIC_FILTERED); } // SkinnedMeshPass1Shader // ============================================================================ Shaders::SkinnedMeshPass2Shader::SkinnedMeshPass2Shader() { loadProgram(OBJECT, GL_VERTEX_SHADER, "skinning.vert", GL_FRAGMENT_SHADER, "object_pass2.frag"); assignUniforms("ModelMatrix", "texture_trans", "color_change", "skinning_offset"); assignSamplerNames(0, "DiffuseMap", ST_NEAREST_FILTERED, 1, "SpecularMap", ST_NEAREST_FILTERED, 2, "SSAO", ST_BILINEAR_FILTERED, 3, "Albedo", ST_TRILINEAR_ANISOTROPIC_FILTERED, 4, "SpecMap", ST_TRILINEAR_ANISOTROPIC_FILTERED, 5, "colorization_mask", ST_TRILINEAR_ANISOTROPIC_FILTERED); } // SkinnedMeshPass2Shader // ============================================================================ Shaders::TransparentShader::TransparentShader() { loadProgram(OBJECT, GL_VERTEX_SHADER, "object_pass.vert", GL_FRAGMENT_SHADER, "transparent.frag"); assignUniforms("ModelMatrix", "texture_trans", "custom_alpha"); assignSamplerNames(0, "tex", ST_TRILINEAR_ANISOTROPIC_FILTERED); } // TransparentShader // ============================================================================ Shaders::TransparentFogShader::TransparentFogShader() { loadProgram(OBJECT, GL_VERTEX_SHADER, "object_pass.vert", GL_FRAGMENT_SHADER, "transparentfog.frag"); assignUniforms("ModelMatrix", "texture_trans", "fogmax", "startH", "endH", "start", "end", "col"); assignSamplerNames(0, "tex", ST_TRILINEAR_ANISOTROPIC_FILTERED); } // TransparentFogShader // ============================================================================ Shaders::ColoredLine::ColoredLine() { loadProgram(OBJECT, GL_VERTEX_SHADER, "object_pass.vert", GL_FRAGMENT_SHADER, "coloredquad.frag"); assignUniforms("color"); glGenVertexArrays(1, &m_vao); glBindVertexArray(m_vao); glGenBuffers(1, &m_vbo); glBindBuffer(GL_ARRAY_BUFFER, m_vbo); glBufferData(GL_ARRAY_BUFFER, 6 * 1024 * sizeof(float), 0, GL_DYNAMIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0); glBindVertexArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); } // Shaders::ColoredLine