//  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

*/

#define SHADER_NAMES

#include "graphics/callbacks.hpp"
#include "graphics/central_settings.hpp"
#include "graphics/irr_driver.hpp"
#include "graphics/gpuparticles.hpp"
#include "graphics/shaders.hpp"
#include "io/file_manager.hpp"
#include "utils/log.hpp"
#include "graphics/glwrap.hpp"
#include <assert.h>
#include <IGPUProgrammingServices.h>

using namespace video;

std::vector<void(*)()> CleanTable;

Shaders::Shaders()
{
    // 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();
}

// Shader loading  related hook

static std::string LoadHeader()
{
    std::string result;
    std::ifstream Stream(file_manager->getAsset("shaders/header.txt").c_str(), std::ios::in);

    if (Stream.is_open())
    {
        std::string Line = "";
        while (getline(Stream, Line))
            result += "\n" + Line;
        Stream.close();
    }

    return result;
}

// Mostly from shader tutorial
GLuint LoadShader(const char * file, unsigned type)
{
    GLuint Id = glCreateShader(type);
    char versionString[20];
    sprintf(versionString, "#version %d\n", CVS->getGLSLVersion());
    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;
}

void setAttribute(AttributeType Tp, GLuint ProgramID)
{
    switch (Tp)
    {
    case OBJECT:
        glBindAttribLocation(ProgramID, 0, "Position");
        glBindAttribLocation(ProgramID, 1, "Normal");
        glBindAttribLocation(ProgramID, 2, "Color");
        glBindAttribLocation(ProgramID, 3, "Texcoord");
        glBindAttribLocation(ProgramID, 4, "SecondTexcoord");
        glBindAttribLocation(ProgramID, 5, "Tangent");
        glBindAttribLocation(ProgramID, 6, "Bitangent");
        glBindAttribLocation(ProgramID, 7, "Origin");
        glBindAttribLocation(ProgramID, 8, "Orientation");
        glBindAttribLocation(ProgramID, 9, "Scale");
        break;
    case PARTICLES_SIM:
        glBindAttribLocation(ProgramID, 0, "particle_position");
        glBindAttribLocation(ProgramID, 1, "lifetime");
        glBindAttribLocation(ProgramID, 2, "particle_velocity");
        glBindAttribLocation(ProgramID, 3, "size");
        glBindAttribLocation(ProgramID, 4, "particle_position_initial");
        glBindAttribLocation(ProgramID, 5, "lifetime_initial");
        glBindAttribLocation(ProgramID, 6, "particle_velocity_initial");
        glBindAttribLocation(ProgramID, 7, "size_initial");
        break;
    case PARTICLES_RENDERING:
        glBindAttribLocation(ProgramID, 1, "lifetime");
        glBindAttribLocation(ProgramID, 2, "size");
        glBindAttribLocation(ProgramID, 4, "quadcorner");
        glBindAttribLocation(ProgramID, 5, "rotationvec");
        glBindAttribLocation(ProgramID, 6, "anglespeed");
        break;
    }
}

GLuint LoadTFBProgram(const char * vertex_file_path, const char **varyings, unsigned varyingscount)
{
    GLuint Program = glCreateProgram();
    loadAndAttach(Program, GL_VERTEX_SHADER, vertex_file_path);
    if (CVS->getGLSLVersion() < 330)
        setAttribute(PARTICLES_SIM, Program);
    glTransformFeedbackVaryings(Program, varyingscount, varyings, GL_INTERLEAVED_ATTRIBS);
    glLinkProgram(Program);

    GLint Result = GL_FALSE;
    int InfoLogLength;
    glGetProgramiv(Program, GL_LINK_STATUS, &Result);
    if (Result == GL_FALSE)
    {
        glGetProgramiv(Program, GL_INFO_LOG_LENGTH, &InfoLogLength);
        char *ErrorMessage = new char[InfoLogLength];
        glGetProgramInfoLog(Program, InfoLogLength, NULL, ErrorMessage);
        Log::error("GLWrap", ErrorMessage);
        delete[] ErrorMessage;
    }

    glGetError();

    return Program;
}

GLuint quad_vbo, tri_vbo;

GLuint SharedObject::FullScreenQuadVAO = 0;
GLuint SharedObject::UIVAO = 0;

static void initQuadVBO()
{
    const float quad_vertex[] = {
        -1., -1., 0., 0., // UpperLeft
        -1., 1., 0., 1., // LowerLeft
        1., -1., 1., 0., // UpperRight
        1., 1., 1., 1., // LowerRight
    };
    glGenBuffers(1, &quad_vbo);
    glBindBuffer(GL_ARRAY_BUFFER, quad_vbo);
    glBufferData(GL_ARRAY_BUFFER, 16 * sizeof(float), quad_vertex, GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    const float tri_vertex[] = {
        -1., -1.,
        -1., 3.,
        3., -1.,
    };
    glGenBuffers(1, &tri_vbo);
    glBindBuffer(GL_ARRAY_BUFFER, tri_vbo);
    glBufferData(GL_ARRAY_BUFFER, 6 * sizeof(float), tri_vertex, GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    glGenVertexArrays(1, &SharedObject::FullScreenQuadVAO);
    glBindVertexArray(SharedObject::FullScreenQuadVAO);
    glBindBuffer(GL_ARRAY_BUFFER, tri_vbo);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), 0);
    glBindVertexArray(0);
}

// It should be possible to merge it with previous one...
GLuint quad_buffer;

static void initQuadBuffer()
{
    const float quad_vertex[] = {
        -1., -1., -1., 1., // UpperLeft
        -1., 1., -1., -1., // LowerLeft
        1., -1., 1., 1., // UpperRight
        1., 1., 1., -1., // LowerRight
    };
    glGenBuffers(1, &quad_buffer);
    glBindBuffer(GL_ARRAY_BUFFER, quad_buffer);
    glBufferData(GL_ARRAY_BUFFER, 16 * sizeof(float), quad_vertex, GL_STATIC_DRAW);

    glGenVertexArrays(1, &SharedObject::UIVAO);
    glBindVertexArray(SharedObject::UIVAO);
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(3);
    glBindBuffer(GL_ARRAY_BUFFER, quad_buffer);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
    glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (GLvoid *)(2 * sizeof(float)));
    glBindVertexArray(0);
}

GLuint SharedObject::billboardvbo = 0;

static void initBillboardVBO()
{
    float quad[] = {
        -.5, -.5, 0., 1.,
        -.5, .5, 0., 0.,
        .5, -.5, 1., 1.,
        .5, .5, 1., 0.,
    };
    glGenBuffers(1, &(SharedObject::billboardvbo));
    glBindBuffer(GL_ARRAY_BUFFER, SharedObject::billboardvbo);
    glBufferData(GL_ARRAY_BUFFER, 16 * sizeof(float), quad, GL_STATIC_DRAW);
}

GLuint SharedObject::skytrivbo = 0;

static void initSkyTriVBO()
{
  const float tri_vertex[] = {
      -1., -1., 1.,
      -1., 3., 1.,
      3., -1., 1.,
  };

    glGenBuffers(1, &SharedObject::skytrivbo);
    glBindBuffer(GL_ARRAY_BUFFER, SharedObject::skytrivbo);
    glBufferData(GL_ARRAY_BUFFER, 3 * 3 * sizeof(float), tri_vertex, GL_STATIC_DRAW);
}

GLuint SharedObject::frustrumvbo = 0;
GLuint SharedObject::frustrumindexes = 0;

static void initFrustrumVBO()
{
    glGenBuffers(1, &SharedObject::frustrumvbo);
    glBindBuffer(GL_ARRAY_BUFFER, SharedObject::frustrumvbo);
    glBufferData(GL_ARRAY_BUFFER, 8 * 3 * sizeof(float), 0, GL_DYNAMIC_DRAW);

    int indices[24] = {
        0, 1, 1, 3, 3, 2, 2, 0,
        4, 5, 5, 7, 7, 6, 6, 4,
        0, 4, 1, 5, 2, 6, 3, 7,
    };

    glGenBuffers(1, &SharedObject::frustrumindexes);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedObject::frustrumindexes);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, 12 * 2 * sizeof(int), indices, GL_STATIC_DRAW);
}

GLuint SharedObject::ViewProjectionMatrixesUBO;

static void initShadowVPMUBO()
{
    glGenBuffers(1, &SharedObject::ViewProjectionMatrixesUBO);
    glBindBuffer(GL_UNIFORM_BUFFER, SharedObject::ViewProjectionMatrixesUBO);
    glBufferData(GL_UNIFORM_BUFFER, (16 * 9 + 2) * sizeof(float), 0, GL_STREAM_DRAW);
    glBindBuffer(GL_UNIFORM_BUFFER, 0);
}

GLuint SharedObject::LightingDataUBO;

static void initLightingDataUBO()
{
    glGenBuffers(1, &SharedObject::LightingDataUBO);
    glBindBuffer(GL_UNIFORM_BUFFER, SharedObject::LightingDataUBO);
    glBufferData(GL_UNIFORM_BUFFER, 36 * sizeof(float), 0, GL_STREAM_DRAW);
    glBindBuffer(GL_UNIFORM_BUFFER, 0);
}

GLuint SharedObject::ParticleQuadVBO = 0;

static void initParticleQuadVBO()
{
    static const GLfloat quad_vertex[] = {
        -.5, -.5, 0., 0.,
        .5, -.5, 1., 0.,
        -.5, .5, 0., 1.,
        .5, .5, 1., 1.,
    };
    glGenBuffers(1, &SharedObject::ParticleQuadVBO);
    glBindBuffer(GL_ARRAY_BUFFER, SharedObject::ParticleQuadVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(quad_vertex), quad_vertex, GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
}

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));

    // Ok, go
    m_shaders[ES_NORMAL_MAP] = glsl_noinput(dir + "pass.vert", dir + "pass.frag");
    m_shaders[ES_NORMAL_MAP_LIGHTMAP] = glsl_noinput(dir + "pass.vert", dir + "pass.frag");

    m_shaders[ES_SKYBOX] = glslmat(dir + "pass.vert", dir + "pass.frag",
                                   m_callbacks[ES_SKYBOX], EMT_TRANSPARENT_ALPHA_CHANNEL);

    m_shaders[ES_SPLATTING] = glsl_noinput(dir + "pass.vert", dir + "pass.frag");

    m_shaders[ES_WATER] = glslmat(dir + "pass.vert", dir + "pass.frag",
                                  m_callbacks[ES_WATER], EMT_TRANSPARENT_ALPHA_CHANNEL);
    m_shaders[ES_WATER_SURFACE] = glsl(dir + "pass.vert", dir + "pass.frag",
                                       m_callbacks[ES_WATER]);

    m_shaders[ES_SPHERE_MAP] = glsl_noinput(dir + "pass.vert", dir + "pass.frag");

    m_shaders[ES_GRASS] = glslmat(dir + "pass.vert", dir + "pass.frag",
                                  m_callbacks[ES_GRASS], EMT_TRANSPARENT_ALPHA_CHANNEL);
    m_shaders[ES_GRASS_REF] = glslmat(dir + "pass.vert", dir + "pass.frag",
                                      m_callbacks[ES_GRASS], EMT_TRANSPARENT_ALPHA_CHANNEL_REF);

    m_shaders[ES_MOTIONBLUR] = glsl(dir + "pass.vert", dir + "pass.frag",
                                    m_callbacks[ES_MOTIONBLUR]);

    m_shaders[ES_GAUSSIAN3H] = glslmat(dir + "pass.vert", dir + "pass.frag",
                                       m_callbacks[ES_GAUSSIAN3H], EMT_SOLID);
    m_shaders[ES_GAUSSIAN3V] = glslmat(dir + "pass.vert", dir + "pass.frag",
                                       m_callbacks[ES_GAUSSIAN3V], EMT_SOLID);

    m_shaders[ES_MIPVIZ] = glslmat(dir + "pass.vert", dir + "pass.frag",
                                   m_callbacks[ES_MIPVIZ], EMT_SOLID);

    m_shaders[ES_OBJECTPASS] = glsl_noinput(dir + "pass.vert", dir + "pass.frag");
    m_shaders[ES_OBJECT_UNLIT] = glsl_noinput(dir + "pass.vert", dir + "pass.frag");
    m_shaders[ES_OBJECTPASS_REF] = glsl_noinput(dir + "pass.vert", dir + "pass.frag");
    m_shaders[ES_OBJECTPASS_RIMLIT] = glsl_noinput(dir + "pass.vert", dir + "pass.frag");

    m_shaders[ES_DISPLACE] = glslmat(dir + "pass.vert", dir + "pass.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();
    CleanTable.clear();
    initQuadVBO();
    initQuadBuffer();
    initBillboardVBO();
    initSkyTriVBO();
    initFrustrumVBO();
    initShadowVPMUBO();
    initLightingDataUBO();
    initParticleQuadVBO();
}

void Shaders::killShaders()
{
    for (unsigned i = 0; i < CleanTable.size(); i++)
        CleanTable[i]();
}

Shaders::~Shaders()
{
    u32 i;
    for (i = 0; i < ES_COUNT; i++)
    {
        if (i == ES_GAUSSIAN3V || !m_callbacks[i]) continue;
        delete m_callbacks[i];
    }
}

E_MATERIAL_TYPE Shaders::getShader(const ShaderType num) const
{
    assert(num < ES_COUNT);

    return (E_MATERIAL_TYPE)m_shaders[num];
}

void Shaders::check(const int num) const
{
    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);
    }
}

void bypassUBO(GLuint Program)
{
    GLint VM = glGetUniformLocation(Program, "ViewMatrix");
    glUniformMatrix4fv(VM, 1, GL_FALSE, irr_driver->getViewMatrix().pointer());
    GLint PM = glGetUniformLocation(Program, "ProjectionMatrix");
    glUniformMatrix4fv(PM, 1, GL_FALSE, irr_driver->getProjMatrix().pointer());
    GLint IVM = glGetUniformLocation(Program, "InverseViewMatrix");
    glUniformMatrix4fv(IVM, 1, GL_FALSE, irr_driver->getInvViewMatrix().pointer());
    GLint IPM = glGetUniformLocation(Program, "InverseProjectionMatrix");
    glUniformMatrix4fv(IPM, 1, GL_FALSE, irr_driver->getInvProjMatrix().pointer());
    GLint Screen = glGetUniformLocation(Program, "screen");
    glUniform2f(Screen, irr_driver->getCurrentScreenSize().X, irr_driver->getCurrentScreenSize().Y);
    GLint bLmn = glGetUniformLocation(Program, "blueLmn[0]");
    glUniform1fv(bLmn, 9, irr_driver->blueSHCoeff);
    GLint gLmn = glGetUniformLocation(Program, "greenLmn[0]");
    glUniform1fv(gLmn, 9, irr_driver->greenSHCoeff);
    GLint rLmn = glGetUniformLocation(Program, "redLmn[0]");
    glUniform1fv(rLmn, 9, irr_driver->redSHCoeff);
    GLint sundir = glGetUniformLocation(Program, "sun_direction");
    glUniform3f(sundir, irr_driver->getSunDirection().X, irr_driver->getSunDirection().Y, irr_driver->getSunDirection().Z);
    GLint suncol = glGetUniformLocation(Program, "sun_col");
    glUniform3f(suncol, irr_driver->getSunColor().getRed(), irr_driver->getSunColor().getGreen(), irr_driver->getSunColor().getBlue());
    GLint sunangle = glGetUniformLocation(Program, "sun_angle");
    glUniform1f(sunangle, 0.54f);
}

namespace UtilShader
{
    ColoredLine::ColoredLine()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/object_pass.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/coloredquad.frag").c_str());

        AssignUniforms("color");

        glGenVertexArrays(1, &vao);
        glBindVertexArray(vao);
        glGenBuffers(1, &vbo);
        glBindBuffer(GL_ARRAY_BUFFER, 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);
    }

    struct TexUnit
    {
        GLuint m_index;
        const char* m_uniform;

        TexUnit(GLuint index, const char* uniform)
        {
            m_index = index;
            m_uniform = uniform;
        }
    };

    template <typename T>
    std::vector<TexUnit> TexUnits(T curr) // required on older clang versions
    {
        std::vector<TexUnit> v;
        v.push_back(curr);
        return v;
    }

    template <typename T, typename... R>
    std::vector<TexUnit> TexUnits(T curr, R... rest) // required on older clang versions
    {
        std::vector<TexUnit> v;
        v.push_back(curr);
        VTexUnits(v, rest...);
        return v;
    }

    template <typename T, typename... R>
    void VTexUnits(std::vector<TexUnit>& v, T curr, R... rest) // required on older clang versions
    {
        v.push_back(curr);
        VTexUnits(v, rest...);
    }

    template <typename T>
    void VTexUnits(std::vector<TexUnit>& v, T curr)
    {
        v.push_back(curr);
    }

    static void
    AssignTextureUnit(GLuint Program, TexUnit texUnit)
    {
        glUseProgram(Program);
        GLuint uniform = glGetUniformLocation(Program, texUnit.m_uniform);
        glUniform1i(uniform, texUnit.m_index);
        glUseProgram(0);
    }

    template<typename... T>
    static void AssignTextureUnit(GLuint Program, TexUnit texUnit, T... rest)
    {
        glUseProgram(Program);
        GLuint uniform = glGetUniformLocation(Program, texUnit.m_uniform);
        glUniform1i(uniform, texUnit.m_index);
        AssignTextureUnit_Sub(Program, rest...);
        glUseProgram(0);
    }

    static void AssignTextureUnit_Sub(GLuint Program) {}

    template<typename... T>
    static void AssignTextureUnit_Sub(GLuint Program, TexUnit texUnit, T... rest)
    {
        GLuint uniform = glGetUniformLocation(Program, texUnit.m_uniform);
        glUniform1i(uniform, texUnit.m_index);
        AssignTextureUnit_Sub(Program, rest...);
    }
}
using namespace UtilShader;

bool needsUBO()
{
    return !CVS->isARBUniformBufferObjectUsable();
}

void setTextureSampler(GLenum tp, GLuint texunit, GLuint tid, GLuint sid)
{
#ifdef GL_VERSION_3_3
    glActiveTexture(GL_TEXTURE0 + texunit);
    glBindTexture(tp, tid);
    glBindSampler(texunit, sid);
#endif
}


GLuint createNearestSampler()
{
#ifdef GL_VERSION_3_3
    unsigned id;
    glGenSamplers(1, &id);
    glSamplerParameteri(id, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glSamplerParameteri(id, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glSamplerParameteri(id, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glSamplerParameteri(id, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glSamplerParameterf(id, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.);
    return id;
#endif
}

void BindTextureNearest(GLuint TU, GLuint tex)
{
    glActiveTexture(GL_TEXTURE0 + TU);
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.);
}

GLuint createBilinearSampler()
{
#ifdef GL_VERSION_3_3
    unsigned id;
    glGenSamplers(1, &id);
    glSamplerParameteri(id, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glSamplerParameteri(id, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glSamplerParameteri(id, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glSamplerParameteri(id, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glSamplerParameterf(id, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.);
    return id;
#endif
}

void BindTextureBilinear(GLuint TU, GLuint tex)
{
    glActiveTexture(GL_TEXTURE0 + TU);
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.);
}

GLuint createBilinearClampedSampler()
{
#ifdef GL_VERSION_3_3
    unsigned id;
    glGenSamplers(1, &id);
    glSamplerParameteri(id, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glSamplerParameteri(id, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glSamplerParameteri(id, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glSamplerParameteri(id, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glSamplerParameterf(id, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.);
    return id;
#endif
}

void BindTextureBilinearClamped(GLuint TU, GLuint tex)
{
    glActiveTexture(GL_TEXTURE0 + TU);
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.);
}

GLuint createSemiTrilinearSampler()
{
#ifdef GL_VERSION_3_3
    unsigned id;
    glGenSamplers(1, &id);
    glSamplerParameteri(id, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glSamplerParameteri(id, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
    glSamplerParameteri(id, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glSamplerParameteri(id, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glSamplerParameterf(id, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.);
    return id;
#endif
}

void BindTextureSemiTrilinear(GLuint TU, GLuint tex)
{
    glActiveTexture(GL_TEXTURE0 + TU);
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.);
}

GLuint createTrilinearSampler()
{
#ifdef GL_VERSION_3_3
    unsigned id;
    glGenSamplers(1, &id);
    glSamplerParameteri(id, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glSamplerParameteri(id, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glSamplerParameteri(id, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glSamplerParameteri(id, GL_TEXTURE_WRAP_T, GL_REPEAT);

    int aniso = UserConfigParams::m_anisotropic;
    if (aniso == 0) aniso = 1;
    glSamplerParameterf(id, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)aniso);
    return id;
#endif
}

void BindTextureTrilinearAnisotropic(GLuint TU, GLuint tex)
{
    glActiveTexture(GL_TEXTURE0 + TU);
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    int aniso = UserConfigParams::m_anisotropic;
    if (aniso == 0) aniso = 1;
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)aniso);
}

void BindCubemapTrilinear(unsigned TU, unsigned tex)
{
    glActiveTexture(GL_TEXTURE0 + TU);
    glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_REPEAT);

    int aniso = UserConfigParams::m_anisotropic;
    if (aniso == 0) aniso = 1;
    glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)aniso);
}

GLuint createShadowSampler()
{
#ifdef GL_VERSION_3_3
    unsigned id;
    glGenSamplers(1, &id);
    glSamplerParameteri(id, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glSamplerParameteri(id, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glSamplerParameteri(id, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glSamplerParameteri(id, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glSamplerParameterf(id, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
    glSamplerParameterf(id, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
    return id;
#endif
}

void BindTextureShadow(GLuint TU, GLuint tex)
{
    glActiveTexture(GL_TEXTURE0 + TU);
    glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
    glTexParameterf(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
}


GLuint createTrilinearClampedArray()
{
#ifdef GL_VERSION_3_3
    unsigned id;
    glGenSamplers(1, &id);
    glSamplerParameteri(id, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glSamplerParameteri(id, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glSamplerParameteri(id, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glSamplerParameteri(id, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    int aniso = UserConfigParams::m_anisotropic;
    if (aniso == 0) aniso = 1;
    glSamplerParameterf(id, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)aniso);
    return id;
#endif
}

void BindTrilinearClampedArrayTexture(unsigned TU, unsigned tex)
{
    glActiveTexture(GL_TEXTURE0 + TU);
    glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    int aniso = UserConfigParams::m_anisotropic;
    if (aniso == 0) aniso = 1;
    glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)aniso);
}

void BindTextureVolume(GLuint TU, GLuint tex)
{
    glActiveTexture(GL_TEXTURE0 + TU);
    glBindTexture(GL_TEXTURE_3D, tex);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.);
}

unsigned getGLSLVersion()
{
    return CVS->getGLSLVersion();
}

namespace UtilShader
{
    SpecularIBLGenerator::SpecularIBLGenerator()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/importance_sampling_specular.frag").c_str());
        AssignUniforms("PermutationMatrix", "ViewportSize");
        TU_Samples = 1;
        AssignSamplerNames(Program, 0, "tex");
        AssignTextureUnit(Program, TexUnit(TU_Samples, "samples"));
    }
}

namespace MeshShader
{
    // Solid Normal and depth pass shaders
    ObjectPass1Shader::ObjectPass1Shader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/object_pass.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/encode_normal.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/object_pass1.frag").c_str());
        AssignUniforms("ModelMatrix", "InverseModelMatrix");
        AssignSamplerNames(Program, 0, "tex");
    }

    ObjectRefPass1Shader::ObjectRefPass1Shader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/object_pass.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/encode_normal.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/objectref_pass1.frag").c_str());
        AssignUniforms("ModelMatrix", "InverseModelMatrix", "TextureMatrix");
        AssignSamplerNames(Program, 0, "tex", 1, "glosstex");
    }

    GrassPass1Shader::GrassPass1Shader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/grass_pass.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/encode_normal.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/objectref_pass1.frag").c_str());
        AssignUniforms("ModelMatrix", "InverseModelMatrix", "windDir");
        AssignSamplerNames(Program, 0, "tex", 1, "glosstex");
    }

    NormalMapShader::NormalMapShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/object_pass.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/encode_normal.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/normalmap.frag").c_str());
        AssignUniforms("ModelMatrix", "InverseModelMatrix");
        AssignSamplerNames(Program, 1, "normalMap", 0, "DiffuseForAlpha");
    }

    InstancedObjectPass1Shader::InstancedObjectPass1Shader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/utils/getworldmatrix.vert").c_str(),
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/instanced_object_pass.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/encode_normal.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/instanced_object_pass1.frag").c_str());

        AssignUniforms();
        AssignSamplerNames(Program, 0, "glosstex");
    }

    InstancedObjectRefPass1Shader::InstancedObjectRefPass1Shader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/utils/getworldmatrix.vert").c_str(),
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/instanced_object_pass.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/encode_normal.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/instanced_objectref_pass1.frag").c_str());

        AssignUniforms();
        AssignSamplerNames(Program, 0, "tex", 1, "glosstex");
    }

    InstancedGrassPass1Shader::InstancedGrassPass1Shader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/utils/getworldmatrix.vert").c_str(),
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/instanced_grass.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/encode_normal.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/instanced_objectref_pass1.frag").c_str());
        AssignUniforms("windDir");
        AssignSamplerNames(Program, 0, "tex", 1, "glosstex");
    }

    InstancedNormalMapShader::InstancedNormalMapShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/utils/getworldmatrix.vert").c_str(),
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/instanced_object_pass.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/encode_normal.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/instanced_normalmap.frag").c_str());
        AssignUniforms();
        AssignSamplerNames(Program, 0, "normalMap", 1, "glossMap");
    }

    // Solid Lit pass shaders
    ObjectPass2Shader::ObjectPass2Shader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/object_pass.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getLightFactor.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/object_pass2.frag").c_str());
        AssignUniforms("ModelMatrix", "TextureMatrix");
        AssignSamplerNames(Program, 0, "DiffuseMap", 1, "SpecularMap", 2, "SSAO", 3, "Albedo", 4, "SpecMap");
    }

    InstancedObjectPass2Shader::InstancedObjectPass2Shader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/utils/getworldmatrix.vert").c_str(),
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/instanced_object_pass.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getLightFactor.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/instanced_object_pass2.frag").c_str());
        AssignUniforms();
        AssignSamplerNames(Program, 0, "DiffuseMap", 1, "SpecularMap", 2, "SSAO", 3, "Albedo", 4, "SpecMap");
    }

    InstancedObjectRefPass2Shader::InstancedObjectRefPass2Shader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/utils/getworldmatrix.vert").c_str(),
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/instanced_object_pass.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getLightFactor.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/instanced_objectref_pass2.frag").c_str());
        AssignUniforms();
        AssignSamplerNames(Program, 0, "DiffuseMap", 1, "SpecularMap", 2, "SSAO", 3, "Albedo", 4, "SpecMap");
    }

    DetailledObjectPass2Shader::DetailledObjectPass2Shader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/object_pass.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getLightFactor.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/detailledobject_pass2.frag").c_str());
        AssignUniforms("ModelMatrix");
        AssignSamplerNames(Program, 0, "DiffuseMap", 1, "SpecularMap", 2, "SSAO", 3, "Albedo", 4, "Detail", 5, "SpecMap");
    }

    InstancedDetailledObjectPass2Shader::InstancedDetailledObjectPass2Shader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/utils/getworldmatrix.vert").c_str(),
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/instanced_object_pass.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getLightFactor.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/instanced_detailledobject_pass2.frag").c_str());
        AssignUniforms();
        AssignSamplerNames(Program, 0, "DiffuseMap", 1, "SpecularMap", 2, "SSAO", 3, "Albedo", 4, "Detail", 5, "SpecMap");
    }

    ObjectUnlitShader::ObjectUnlitShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/object_pass.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/object_unlit.frag").c_str());
        AssignUniforms("ModelMatrix", "TextureMatrix");
        AssignSamplerNames(Program, 0, "DiffuseMap", 1, "SpecularMap", 2, "SSAO", 3, "tex");
    }

    InstancedObjectUnlitShader::InstancedObjectUnlitShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/utils/getworldmatrix.vert").c_str(),
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/instanced_object_pass.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/instanced_object_unlit.frag").c_str());
        AssignUniforms();
        AssignSamplerNames(Program, 0, "DiffuseMap", 1, "SpecularMap", 2, "SSAO", 3, "tex");
    }

    ObjectRefPass2Shader::ObjectRefPass2Shader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/object_pass.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getLightFactor.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/objectref_pass2.frag").c_str());
        AssignUniforms("ModelMatrix", "TextureMatrix");
        AssignSamplerNames(Program, 0, "DiffuseMap", 1, "SpecularMap", 2, "SSAO", 3, "Albedo", 4, "SpecMap");
    }

    GrassPass2Shader::GrassPass2Shader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/grass_pass.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getLightFactor.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/grass_pass2.frag").c_str());
        AssignUniforms("ModelMatrix", "windDir");
        AssignSamplerNames(Program, 0, "DiffuseMap", 1, "SpecularMap", 2, "SSAO", 3, "Albedo", 4, "SpecMap");
    }

    InstancedGrassPass2Shader::InstancedGrassPass2Shader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/utils/getworldmatrix.vert").c_str(),
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/instanced_grass.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getLightFactor.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/instanced_grass_pass2.frag").c_str());
        AssignUniforms("windDir", "SunDir");
        AssignSamplerNames(Program, 0, "DiffuseMap", 1, "SpecularMap", 2, "SSAO", 3, "dtex", 4, "Albedo", 5, "SpecMap");
    }

    SphereMapShader::SphereMapShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/object_pass.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getLightFactor.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getPosFromUVDepth.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/objectpass_spheremap.frag").c_str());
        AssignUniforms("ModelMatrix", "InverseModelMatrix");
        AssignSamplerNames(Program, 0, "DiffuseMap", 1, "SpecularMap", 2, "SSAO", 3, "tex");
    }

    InstancedSphereMapShader::InstancedSphereMapShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/utils/getworldmatrix.vert").c_str(),
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/instanced_object_pass.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getLightFactor.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getPosFromUVDepth.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/instanced_objectpass_spheremap.frag").c_str());
        AssignUniforms();
        AssignSamplerNames(Program, 0, "DiffuseMap", 1, "SpecularMap", 2, "SSAO", 3, "tex");
    }

    SplattingShader::SplattingShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/object_pass.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getLightFactor.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/splatting.frag").c_str());
        AssignUniforms("ModelMatrix");

        AssignSamplerNames(Program,
            0, "DiffuseMap",
            1, "SpecularMap",
            2, "SSAO",
            3, "tex_layout",
            4, "tex_detail0",
            5, "tex_detail1",
            6, "tex_detail2",
            7, "tex_detail3");
    }

    TransparentShader::TransparentShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/object_pass.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/transparent.frag").c_str());
        AssignUniforms("ModelMatrix", "TextureMatrix");
        AssignSamplerNames(Program, 0, "tex");
    }

    TransparentFogShader::TransparentFogShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/object_pass.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/transparentfog.frag").c_str());
        AssignUniforms("ModelMatrix", "TextureMatrix", "fogmax", "startH", "endH", "start", "end", "col");
        AssignSamplerNames(Program, 0, "tex");
    }

    BillboardShader::BillboardShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/billboard.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/billboard.frag").c_str());

        AssignUniforms("ModelViewMatrix", "ProjectionMatrix", "Position", "Size");
        AssignSamplerNames(Program, 0, "tex");
    }

    ColorizeShader::ColorizeShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/object_pass.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/colorize.frag").c_str());
        AssignUniforms("ModelMatrix", "col");
    }

    InstancedColorizeShader::InstancedColorizeShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/utils/getworldmatrix.vert").c_str(),
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/glow_object.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/glow_object.frag").c_str());
        AssignUniforms();
    }

    ShadowShader::ShadowShader()
    {
        // Geometry shader needed
        if (CVS->getGLSLVersion() < 150)
            return;
        if (CVS->isAMDVertexShaderLayerUsable())
        {
            Program = LoadProgram(OBJECT,
                GL_VERTEX_SHADER, file_manager->getAsset("shaders/shadow.vert").c_str(),
                GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/shadow.frag").c_str());
        }
        else
        {
            Program = LoadProgram(OBJECT,
                GL_VERTEX_SHADER, file_manager->getAsset("shaders/shadow.vert").c_str(),
                GL_GEOMETRY_SHADER, file_manager->getAsset("shaders/shadow.geom").c_str(),
                GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/shadow.frag").c_str());
        }
        AssignUniforms("layer", "ModelMatrix");
    }

    RSMShader::RSMShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/rsm.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/rsm.frag").c_str());

        AssignUniforms("RSMMatrix", "ModelMatrix", "TextureMatrix");
        AssignSamplerNames(Program, 0, "tex");
    }

    InstancedRSMShader::InstancedRSMShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/utils/getworldmatrix.vert").c_str(),
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/instanced_rsm.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/instanced_rsm.frag").c_str());

        AssignUniforms("RSMMatrix");
        AssignSamplerNames(Program, 0, "tex");
    }

    SplattingRSMShader::SplattingRSMShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/rsm.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/splatting_rsm.frag").c_str());

        AssignUniforms("RSMMatrix", "ModelMatrix");
        AssignSamplerNames(Program, 0, "tex_layout", 1, "tex_detail0", 2, "tex_detail1", 3, "tex_detail2", 4, "tex_detail3");
    }

    InstancedShadowShader::InstancedShadowShader()
    {
        // Geometry shader needed
        if (CVS->getGLSLVersion() < 150)
            return;
        if (CVS->isAMDVertexShaderLayerUsable())
        {
            Program = LoadProgram(OBJECT,
                GL_VERTEX_SHADER, file_manager->getAsset("shaders/utils/getworldmatrix.vert").c_str(),
                GL_VERTEX_SHADER, file_manager->getAsset("shaders/instanciedshadow.vert").c_str(),
                GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/shadow.frag").c_str());
        }
        else
        {
            Program = LoadProgram(OBJECT,
                GL_VERTEX_SHADER, file_manager->getAsset("shaders/utils/getworldmatrix.vert").c_str(),
                GL_VERTEX_SHADER, file_manager->getAsset("shaders/instanciedshadow.vert").c_str(),
                GL_GEOMETRY_SHADER, file_manager->getAsset("shaders/instanced_shadow.geom").c_str(),
                GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/shadow.frag").c_str());
        }
        AssignUniforms("layer");
    }

    RefShadowShader::RefShadowShader()
    {
        // Geometry shader needed
        if (CVS->getGLSLVersion() < 150)
            return;
        if (CVS->isAMDVertexShaderLayerUsable())
        {
            Program = LoadProgram(OBJECT,
                GL_VERTEX_SHADER, file_manager->getAsset("shaders/shadow.vert").c_str(),
                GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/shadowref.frag").c_str());
        }
        else
        {
            Program = LoadProgram(OBJECT,
                GL_VERTEX_SHADER, file_manager->getAsset("shaders/shadow.vert").c_str(),
                GL_GEOMETRY_SHADER, file_manager->getAsset("shaders/shadow.geom").c_str(),
                GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/shadowref.frag").c_str());
        }
        AssignUniforms("layer", "ModelMatrix");
        AssignSamplerNames(Program, 0, "tex");
    }

    InstancedRefShadowShader::InstancedRefShadowShader()
    {
        // Geometry shader needed
        if (CVS->getGLSLVersion() < 150)
            return;
        if (CVS->isAMDVertexShaderLayerUsable())
        {
            Program = LoadProgram(OBJECT,
                GL_VERTEX_SHADER, file_manager->getAsset("shaders/utils/getworldmatrix.vert").c_str(),
                GL_VERTEX_SHADER, file_manager->getAsset("shaders/instanciedshadow.vert").c_str(),
                GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/instanced_shadowref.frag").c_str());
        }
        else
        {
            Program = LoadProgram(OBJECT,
                GL_VERTEX_SHADER, file_manager->getAsset("shaders/utils/getworldmatrix.vert").c_str(),
                GL_VERTEX_SHADER, file_manager->getAsset("shaders/instanciedshadow.vert").c_str(),
                GL_GEOMETRY_SHADER, file_manager->getAsset("shaders/instanced_shadow.geom").c_str(),
                GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/instanced_shadowref.frag").c_str());
        }
        AssignUniforms("layer");
        AssignSamplerNames(Program, 0, "tex");
    }

    GrassShadowShader::GrassShadowShader()
    {
        // Geometry shader needed
        if (CVS->getGLSLVersion() < 150)
            return;
        if (CVS->isAMDVertexShaderLayerUsable())
        {
            Program = LoadProgram(OBJECT,
                GL_VERTEX_SHADER, file_manager->getAsset("shaders/shadow_grass.vert").c_str(),
                GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/instanced_shadowref.frag").c_str());
        }
        else
        {
            Program = LoadProgram(OBJECT,
                GL_VERTEX_SHADER, file_manager->getAsset("shaders/shadow_grass.vert").c_str(),
                GL_GEOMETRY_SHADER, file_manager->getAsset("shaders/shadow.geom").c_str(),
                GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/instanced_shadowref.frag").c_str());
        }
        AssignUniforms("layer", "ModelMatrix", "windDir");
        AssignSamplerNames(Program, 0, "tex");
    }

    InstancedGrassShadowShader::InstancedGrassShadowShader()
    {
        // Geometry shader needed
        if (CVS->getGLSLVersion() < 150)
            return;
        if (CVS->isAMDVertexShaderLayerUsable())
        {
            Program = LoadProgram(OBJECT,
                GL_VERTEX_SHADER, file_manager->getAsset("shaders/utils/getworldmatrix.vert").c_str(),
                GL_VERTEX_SHADER, file_manager->getAsset("shaders/instanciedgrassshadow.vert").c_str(),
                GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/instanced_shadowref.frag").c_str());
        }
        else
        {
            Program = LoadProgram(OBJECT,
                GL_VERTEX_SHADER, file_manager->getAsset("shaders/utils/getworldmatrix.vert").c_str(),
                GL_VERTEX_SHADER, file_manager->getAsset("shaders/instanciedgrassshadow.vert").c_str(),
                GL_GEOMETRY_SHADER, file_manager->getAsset("shaders/instanced_shadow.geom").c_str(),
                GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/instanced_shadowref.frag").c_str());
        }

        AssignSamplerNames(Program, 0, "tex");
        AssignUniforms("layer", "windDir");
    }

    DisplaceMaskShader::DisplaceMaskShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/displace.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/white.frag").c_str());
        AssignUniforms("ModelMatrix");
    }


    DisplaceShader::DisplaceShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/displace.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/displace.frag").c_str());
        AssignUniforms("ModelMatrix", "dir", "dir2");

        AssignSamplerNames(Program,
            0, "displacement_tex",
            1, "color_tex",
            2, "mask_tex",
            3, "tex");
    }

    SkyboxShader::SkyboxShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/sky.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/sky.frag").c_str());
        AssignUniforms();
        AssignSamplerNames(Program, 0, "tex");

        glGenVertexArrays(1, &vao);
        glBindVertexArray(vao);
        glBindBuffer(GL_ARRAY_BUFFER, SharedObject::skytrivbo);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);
        glBindVertexArray(0);
    }

    NormalVisualizer::NormalVisualizer()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/utils/getworldmatrix.vert").c_str(),
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/instanced_object_pass.vert").c_str(),
            GL_GEOMETRY_SHADER, file_manager->getAsset("shaders/normal_visualizer.geom").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/coloredquad.frag").c_str());
        AssignUniforms("color");
    }

    ViewFrustrumShader::ViewFrustrumShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/frustrum.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/coloredquad.frag").c_str());

        AssignUniforms("color", "idx");

        glGenVertexArrays(1, &frustrumvao);
        glBindVertexArray(frustrumvao);
        glBindBuffer(GL_ARRAY_BUFFER, SharedObject::frustrumvbo);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedObject::frustrumindexes);
        glBindVertexArray(0);
    }
}

namespace LightShader
{
    PointLightShader::PointLightShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/pointlight.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/decodeNormal.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/SpecularBRDF.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/DiffuseBRDF.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getPosFromUVDepth.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/pointlight.frag").c_str());

        AssignUniforms();
        AssignSamplerNames(Program, 0, "ntex", 1, "dtex");

        glGenVertexArrays(1, &vao);
        glBindVertexArray(vao);

        glGenBuffers(1, &vbo);
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferData(GL_ARRAY_BUFFER, MAXLIGHT * sizeof(PointLightInfo), 0, GL_DYNAMIC_DRAW);

        GLuint attrib_Position = glGetAttribLocation(Program, "Position");
        GLuint attrib_Color = glGetAttribLocation(Program, "Color");
        GLuint attrib_Energy = glGetAttribLocation(Program, "Energy");
        GLuint attrib_Radius = glGetAttribLocation(Program, "Radius");

        glEnableVertexAttribArray(attrib_Position);
        glVertexAttribPointer(attrib_Position, 3, GL_FLOAT, GL_FALSE, sizeof(PointLightInfo), 0);
        glEnableVertexAttribArray(attrib_Energy);
        glVertexAttribPointer(attrib_Energy, 1, GL_FLOAT, GL_FALSE, sizeof(PointLightInfo), (GLvoid*)(3 * sizeof(float)));
        glEnableVertexAttribArray(attrib_Color);
        glVertexAttribPointer(attrib_Color, 3, GL_FLOAT, GL_FALSE, sizeof(PointLightInfo), (GLvoid*)(4 * sizeof(float)));
        glEnableVertexAttribArray(attrib_Radius);
        glVertexAttribPointer(attrib_Radius, 1, GL_FLOAT, GL_FALSE, sizeof(PointLightInfo), (GLvoid*)(7 * sizeof(float)));

        glVertexAttribDivisorARB(attrib_Position, 1);
        glVertexAttribDivisorARB(attrib_Energy, 1);
        glVertexAttribDivisorARB(attrib_Color, 1);
        glVertexAttribDivisorARB(attrib_Radius, 1);
    }

    PointLightScatterShader::PointLightScatterShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/pointlight.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getPosFromUVDepth.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/pointlightscatter.frag").c_str());

        AssignUniforms("density", "fogcol");
        AssignSamplerNames(Program, 0, "dtex");

        glGenVertexArrays(1, &vao);
        glBindVertexArray(vao);

        glBindBuffer(GL_ARRAY_BUFFER, PointLightShader::getInstance()->vbo);

        GLuint attrib_Position = glGetAttribLocation(Program, "Position");
        GLuint attrib_Color = glGetAttribLocation(Program, "Color");
        GLuint attrib_Energy = glGetAttribLocation(Program, "Energy");
        GLuint attrib_Radius = glGetAttribLocation(Program, "Radius");

        glEnableVertexAttribArray(attrib_Position);
        glVertexAttribPointer(attrib_Position, 3, GL_FLOAT, GL_FALSE, sizeof(PointLightInfo), 0);
        glEnableVertexAttribArray(attrib_Energy);
        glVertexAttribPointer(attrib_Energy, 1, GL_FLOAT, GL_FALSE, sizeof(PointLightInfo), (GLvoid*)(3 * sizeof(float)));
        glEnableVertexAttribArray(attrib_Color);
        glVertexAttribPointer(attrib_Color, 3, GL_FLOAT, GL_FALSE, sizeof(PointLightInfo), (GLvoid*)(4 * sizeof(float)));
        glEnableVertexAttribArray(attrib_Radius);
        glVertexAttribPointer(attrib_Radius, 1, GL_FLOAT, GL_FALSE, sizeof(PointLightInfo), (GLvoid*)(7 * sizeof(float)));

        glVertexAttribDivisorARB(attrib_Position, 1);
        glVertexAttribDivisorARB(attrib_Energy, 1);
        glVertexAttribDivisorARB(attrib_Color, 1);
        glVertexAttribDivisorARB(attrib_Radius, 1);
    }
}


namespace ParticleShader
{
    SimpleSimulationShader::SimpleSimulationShader()
    {
        const char *varyings[] = {
            "new_particle_position",
            "new_lifetime",
            "new_particle_velocity",
            "new_size",
        };
        Program = LoadTFBProgram(file_manager->getAsset("shaders/pointemitter.vert").c_str(), varyings, 4);
        AssignUniforms("sourcematrix", "dt", "level", "size_increase_factor");
    }

    HeightmapSimulationShader::HeightmapSimulationShader()
    {
        const char *varyings[] = {
            "new_particle_position",
            "new_lifetime",
            "new_particle_velocity",
            "new_size",
        };
        Program = LoadTFBProgram(file_manager->getAsset("shaders/particlesimheightmap.vert").c_str(), varyings, 4);
        TU_heightmap = 2;
        AssignTextureUnit(Program, TexUnit(TU_heightmap, "heightmap"));
        AssignUniforms("sourcematrix", "dt", "level", "size_increase_factor", "track_x", "track_x_len", "track_z", "track_z_len");
    }

    SimpleParticleRender::SimpleParticleRender()
    {
        Program = LoadProgram(PARTICLES_RENDERING,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/particle.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getPosFromUVDepth.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/particle.frag").c_str());
        AssignUniforms("color_from", "color_to");

        AssignSamplerNames(Program, 0, "tex", 1, "dtex");
    }

    FlipParticleRender::FlipParticleRender()
    {
        Program = LoadProgram(PARTICLES_RENDERING,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/flipparticle.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getPosFromUVDepth.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/particle.frag").c_str());
        AssignUniforms();

        AssignSamplerNames(Program, 0, "tex", 1, "dtex");
    }
}

static GLuint createVAO(GLuint Program)
{
    GLuint vao;
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);
    GLuint attrib_position = glGetAttribLocation(Program, "Position");
    GLuint attrib_texcoord = glGetAttribLocation(Program, "Texcoord");
    glBindBuffer(GL_ARRAY_BUFFER, quad_vbo);
    glEnableVertexAttribArray(attrib_position);
    glEnableVertexAttribArray(attrib_texcoord);
    glVertexAttribPointer(attrib_position, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
    glVertexAttribPointer(attrib_texcoord, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (GLvoid*)(2 * sizeof(float)));
    glBindVertexArray(0);
    return vao;
}

namespace FullScreenShader
{
    BloomShader::BloomShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getCIEXYZ.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getRGBfromCIEXxy.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/bloom.frag").c_str());
        AssignUniforms();

        AssignSamplerNames(Program, 0, "tex");
    }

    BloomBlendShader::BloomBlendShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/bloomblend.frag").c_str());
        AssignUniforms();

        AssignSamplerNames(Program, 0, "tex_128", 1, "tex_256", 2, "tex_512");
    }
	
	LensBlendShader::LensBlendShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/lensblend.frag").c_str());
        AssignUniforms();

        AssignSamplerNames(Program, 0, "tex_128", 1, "tex_256", 2, "tex_512");
    }

    ToneMapShader::ToneMapShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getRGBfromCIEXxy.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getCIEXYZ.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/tonemap.frag").c_str());
        AssignUniforms("vignette_weight");

        AssignSamplerNames(Program, 0, "text");
    }

    DepthOfFieldShader::DepthOfFieldShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/dof.frag").c_str());

        AssignUniforms();
        AssignSamplerNames(Program, 0, "tex", 1, "dtex");
    }

    SunLightShader::SunLightShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/decodeNormal.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/SpecularBRDF.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/DiffuseBRDF.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getPosFromUVDepth.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/SunMRP.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/sunlight.frag").c_str());

        AssignSamplerNames(Program, 0, "ntex", 1, "dtex");
        AssignUniforms("direction", "col");
    }

    IBLShader::IBLShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/decodeNormal.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getPosFromUVDepth.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/DiffuseIBL.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/SpecularIBL.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/IBL.frag").c_str());
        AssignUniforms();
        AssignSamplerNames(Program, 0, "ntex", 1, "dtex", 2, "probe");
    }

    DegradedIBLShader::DegradedIBLShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/decodeNormal.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getPosFromUVDepth.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/DiffuseIBL.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/SpecularIBL.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/degraded_ibl.frag").c_str());
        AssignUniforms();
        AssignSamplerNames(Program, 0, "ntex");
    }

    ShadowedSunLightShaderPCF::ShadowedSunLightShaderPCF()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/decodeNormal.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/SpecularBRDF.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/DiffuseBRDF.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getPosFromUVDepth.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/SunMRP.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/sunlightshadow.frag").c_str());

        // Use 8 to circumvent a catalyst bug when binding sampler
        AssignSamplerNames(Program, 0, "ntex", 1, "dtex", 8, "shadowtex");
        AssignUniforms("split0", "split1", "split2", "splitmax", "shadow_res");
    }

    ShadowedSunLightShaderESM::ShadowedSunLightShaderESM()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/decodeNormal.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/SpecularBRDF.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/DiffuseBRDF.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getPosFromUVDepth.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/SunMRP.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/sunlightshadowesm.frag").c_str());

        // Use 8 to circumvent a catalyst bug when binding sampler
        AssignSamplerNames(Program, 0, "ntex", 1, "dtex", 8, "shadowtex");
        AssignUniforms("split0", "split1", "split2", "splitmax");
    }

    RadianceHintsConstructionShader::RadianceHintsConstructionShader()
    {
        if (CVS->isAMDVertexShaderLayerUsable())
        {
            Program = LoadProgram(OBJECT,
                GL_VERTEX_SHADER, file_manager->getAsset("shaders/slicedscreenquad.vert").c_str(),
                GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/rh.frag").c_str());
        }
        else
        {
            Program = LoadProgram(OBJECT,
                GL_VERTEX_SHADER, file_manager->getAsset("shaders/slicedscreenquad.vert").c_str(),
                GL_GEOMETRY_SHADER, file_manager->getAsset("shaders/rhpassthrough.geom").c_str(),
                GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/rh.frag").c_str());
        }

        AssignUniforms("RSMMatrix", "RHMatrix", "extents", "suncol");
        AssignSamplerNames(Program, 0, "ctex", 1, "ntex", 2, "dtex");
    }

    NVWorkaroundRadianceHintsConstructionShader::NVWorkaroundRadianceHintsConstructionShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/slicedscreenquad_nvworkaround.vert").c_str(),
            GL_GEOMETRY_SHADER, file_manager->getAsset("shaders/rhpassthrough.geom").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/rh.frag").c_str());

        AssignUniforms("RSMMatrix", "RHMatrix", "extents", "slice", "suncol");

        AssignSamplerNames(Program, 0, "ctex", 1, "ntex", 2, "dtex");
    }

    RHDebug::RHDebug()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/rhdebug.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/rhdebug.frag").c_str());
        AssignUniforms("RHMatrix", "extents");
        TU_SHR = 0;
        TU_SHG = 1;
        TU_SHB = 2;
        AssignTextureUnit(Program, TexUnit(TU_SHR, "SHR"), TexUnit(TU_SHG, "SHG"), TexUnit(TU_SHB, "SHB"));
    }

    GlobalIlluminationReconstructionShader::GlobalIlluminationReconstructionShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/decodeNormal.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getPosFromUVDepth.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/gi.frag").c_str());

        AssignUniforms("RHMatrix", "InvRHMatrix", "extents");
        AssignSamplerNames(Program, 0, "ntex", 1, "dtex", 2, "SHR", 3, "SHG", 4, "SHB");
    }

    Gaussian17TapHShader::Gaussian17TapHShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/bilateralH.frag").c_str());
        AssignUniforms("pixel");
        AssignSamplerNames(Program, 0, "tex", 1, "depth");
    }

    ComputeGaussian17TapHShader::ComputeGaussian17TapHShader()
    {
        Program = LoadProgram(OBJECT,
            GL_COMPUTE_SHADER, file_manager->getAsset("shaders/bilateralH.comp").c_str());
        TU_dest = 2;
        AssignUniforms("pixel");
        AssignSamplerNames(Program, 0, "source", 1, "depth");
        AssignTextureUnit(Program, TexUnit(TU_dest, "dest"));
    }

    ComputeGaussian6HBlurShader::ComputeGaussian6HBlurShader()
    {
        Program = LoadProgram(OBJECT,
            GL_COMPUTE_SHADER, file_manager->getAsset("shaders/gaussian6h.comp").c_str());
        TU_dest = 1;
        AssignUniforms("pixel", "weights");
        AssignSamplerNames(Program, 0, "source");
        AssignTextureUnit(Program, TexUnit(TU_dest, "dest"));
    }

    ComputeShadowBlurHShader::ComputeShadowBlurHShader()
    {
        Program = LoadProgram(OBJECT,
            GL_COMPUTE_SHADER, file_manager->getAsset("shaders/blurshadowH.comp").c_str());
        TU_dest = 1;
        AssignUniforms("pixel", "weights");
        AssignSamplerNames(Program, 0, "source");
        AssignTextureUnit(Program, TexUnit(TU_dest, "dest"));
    }

    Gaussian6HBlurShader::Gaussian6HBlurShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/gaussian6h.frag").c_str());
        AssignUniforms("pixel", "sigma");

        AssignSamplerNames(Program, 0, "tex");
    }
	
	HorizontalBlurShader::HorizontalBlurShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/gaussian6h.frag").c_str());
        AssignUniforms("pixel");

        AssignSamplerNames(Program, 0, "tex");
    }

    Gaussian3HBlurShader::Gaussian3HBlurShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/gaussian3h.frag").c_str());
        AssignUniforms("pixel");

        AssignSamplerNames(Program, 0, "tex");
    }

    Gaussian17TapVShader::Gaussian17TapVShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/bilateralV.frag").c_str());
        AssignUniforms("pixel");

        AssignSamplerNames(Program, 0, "tex", 1, "depth");
    }

    ComputeGaussian17TapVShader::ComputeGaussian17TapVShader()
    {
        Program = LoadProgram(OBJECT,
            GL_COMPUTE_SHADER, file_manager->getAsset("shaders/bilateralV.comp").c_str());
        TU_dest = 2;
        AssignUniforms("pixel");
        AssignSamplerNames(Program, 0, "source", 1, "depth");
        AssignTextureUnit(Program, TexUnit(TU_dest, "dest"));
    }

    ComputeGaussian6VBlurShader::ComputeGaussian6VBlurShader()
    {
        Program = LoadProgram(OBJECT,
            GL_COMPUTE_SHADER, file_manager->getAsset("shaders/gaussian6v.comp").c_str());
        TU_dest = 1;
        AssignUniforms("pixel", "weights");
        AssignSamplerNames(Program, 0, "source");
        AssignTextureUnit(Program, TexUnit(TU_dest, "dest"));
    }

    ComputeShadowBlurVShader::ComputeShadowBlurVShader()
    {
        Program = LoadProgram(OBJECT,
            GL_COMPUTE_SHADER, file_manager->getAsset("shaders/blurshadowV.comp").c_str());
        TU_dest = 1;
        AssignUniforms("pixel", "weights");
        AssignSamplerNames(Program, 0, "source");
        AssignTextureUnit(Program, TexUnit(TU_dest, "dest"));
    }

    Gaussian6VBlurShader::Gaussian6VBlurShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/gaussian6v.frag").c_str());
        AssignUniforms("pixel", "sigma");

        AssignSamplerNames(Program, 0, "tex");
    }

    Gaussian3VBlurShader::Gaussian3VBlurShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/gaussian3v.frag").c_str());
        AssignUniforms("pixel");

        AssignSamplerNames(Program, 0, "tex");
    }

    PassThroughShader::PassThroughShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/passthrough.frag").c_str());

        AssignUniforms("width", "height");
        AssignSamplerNames(Program, 0, "tex");
    }

    LayerPassThroughShader::LayerPassThroughShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/layertexturequad.frag").c_str());
        TU_texture = 0;
        AssignUniforms("layer");
        AssignTextureUnit(Program, TexUnit(TU_texture, "tex"));
        vao = createVAO(Program);
    }

    LinearizeDepthShader::LinearizeDepthShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/linearizedepth.frag").c_str());
        AssignUniforms("zn", "zf");

        AssignSamplerNames(Program, 0, "texture");
    }


    LightspaceBoundingBoxShader::LightspaceBoundingBoxShader()
    {
        Program = LoadProgram(OBJECT,
            GL_COMPUTE_SHADER, file_manager->getAsset("shaders/Lightspaceboundingbox.comp").c_str(),
            GL_COMPUTE_SHADER, file_manager->getAsset("shaders/utils/getPosFromUVDepth.frag").c_str());
        AssignSamplerNames(Program, 0, "depth");
        AssignUniforms("SunCamMatrix", "split0", "split1", "split2", "splitmax");
        GLuint block_idx = glGetProgramResourceIndex(Program, GL_SHADER_STORAGE_BLOCK, "BoundingBoxes");
        glShaderStorageBlockBinding(Program, block_idx, 2);
    }

    ShadowMatrixesGenerationShader::ShadowMatrixesGenerationShader()
    {
        Program = LoadProgram(OBJECT,
            GL_COMPUTE_SHADER, file_manager->getAsset("shaders/shadowmatrixgeneration.comp").c_str());
        AssignUniforms("SunCamMatrix");
        GLuint block_idx = glGetProgramResourceIndex(Program, GL_SHADER_STORAGE_BLOCK, "BoundingBoxes");
        glShaderStorageBlockBinding(Program, block_idx, 2);
        block_idx = glGetProgramResourceIndex(Program, GL_SHADER_STORAGE_BLOCK, "NewMatrixData");
        glShaderStorageBlockBinding(Program, block_idx, 1);
    }

    DepthHistogramShader::DepthHistogramShader()
    {
        Program = LoadProgram(OBJECT,
            GL_COMPUTE_SHADER, file_manager->getAsset("shaders/depthhistogram.comp").c_str(),
            GL_COMPUTE_SHADER, file_manager->getAsset("shaders/utils/getPosFromUVDepth.frag").c_str());
        AssignSamplerNames(Program, 0, "depth");

        GLuint block_idx = glGetProgramResourceIndex(Program, GL_SHADER_STORAGE_BLOCK, "Histogram");
        glShaderStorageBlockBinding(Program, block_idx, 1);
    }

    GlowShader::GlowShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/glow.frag").c_str());
        AssignUniforms();

        AssignSamplerNames(Program, 0, "tex");
        vao = createVAO(Program);
    }

    SSAOShader::SSAOShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/decodeNormal.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getPosFromUVDepth.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/ssao.frag").c_str());

        AssignSamplerNames(Program, 0, "dtex");
        AssignUniforms("radius", "k", "sigma");
    }

    FogShader::FogShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getPosFromUVDepth.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/fog.frag").c_str());

        AssignUniforms("density", "col");
        AssignSamplerNames(Program, 0, "tex");
    }

    MotionBlurShader::MotionBlurShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/utils/getPosFromUVDepth.frag").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/motion_blur.frag").c_str());
        AssignUniforms("previous_viewproj", "center", "boost_amount", "mask_radius");
        AssignSamplerNames(Program, 0, "color_buffer", 1, "dtex");
    }

    GodFadeShader::GodFadeShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/godfade.frag").c_str());
        AssignUniforms("col");

        AssignSamplerNames(Program, 0, "tex");
    }

    GodRayShader::GodRayShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/godray.frag").c_str());

        AssignUniforms("sunpos");
        AssignSamplerNames(Program, 0, "tex");
    }

    MLAAColorEdgeDetectionSHader::MLAAColorEdgeDetectionSHader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/mlaa_color1.frag").c_str());
        AssignUniforms("PIXEL_SIZE");

        AssignSamplerNames(Program, 0, "colorMapG");
    }

    MLAABlendWeightSHader::MLAABlendWeightSHader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/mlaa_blend2.frag").c_str());
        AssignUniforms("PIXEL_SIZE");

        AssignSamplerNames(Program, 0, "edgesMap", 1, "areaMap");
    }

    MLAAGatherSHader::MLAAGatherSHader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/screenquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/mlaa_neigh3.frag").c_str());
        AssignUniforms("PIXEL_SIZE");

        AssignSamplerNames(Program, 0, "blendMap", 1, "colorMap");
    }
}

namespace UIShader
{

    Primitive2DList::Primitive2DList()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/primitive2dlist.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/transparent.frag").c_str());
        AssignUniforms();
        AssignSamplerNames(Program, 0, "tex");
    }

    TextureRectShader::TextureRectShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/texturedquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/texturedquad.frag").c_str());
        AssignUniforms("center", "size", "texcenter", "texsize");

        AssignSamplerNames(Program, 0, "tex");
    }

    UniformColoredTextureRectShader::UniformColoredTextureRectShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/texturedquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/uniformcolortexturedquad.frag").c_str());

        AssignUniforms("center", "size", "texcenter", "texsize", "color");

        AssignSamplerNames(Program, 0, "tex");
    }

    ColoredTextureRectShader::ColoredTextureRectShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/colortexturedquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/colortexturedquad.frag").c_str());
        AssignUniforms("center", "size", "texcenter", "texsize");

        AssignSamplerNames(Program, 0, "tex");

        glGenVertexArrays(1, &vao);
        glBindVertexArray(vao);
        glEnableVertexAttribArray(0);
        glEnableVertexAttribArray(3);
        glEnableVertexAttribArray(2);
        glBindBuffer(GL_ARRAY_BUFFER, quad_buffer);
        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
        glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (GLvoid *)(2 * sizeof(float)));
        const unsigned quad_color[] = {
            0, 0, 0, 255,
            255, 0, 0, 255,
            0, 255, 0, 255,
            0, 0, 255, 255,
        };
        glGenBuffers(1, &colorvbo);
        glBindBuffer(GL_ARRAY_BUFFER, colorvbo);
        glBufferData(GL_ARRAY_BUFFER, 16 * sizeof(unsigned), quad_color, GL_DYNAMIC_DRAW);
        glVertexAttribIPointer(2, 4, GL_UNSIGNED_INT, 4 * sizeof(unsigned), 0);
        glBindVertexArray(0);
    }

    ColoredRectShader::ColoredRectShader()
    {
        Program = LoadProgram(OBJECT,
            GL_VERTEX_SHADER, file_manager->getAsset("shaders/coloredquad.vert").c_str(),
            GL_FRAGMENT_SHADER, file_manager->getAsset("shaders/coloredquad.frag").c_str());
        AssignUniforms("center", "size", "color");
    }
}
