2016-12-09 18:41:13 +08:00

422 lines
16 KiB
C++

// 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 <assert.h>
#include <IGPUProgrammingServices.h>
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