RSS link icon

OpenGL : Chargement et compilation des shaders

Publication : le 06 sept. 2016

Lorsque l’on développe une application OpenGL 3 (ou plus récent), une des premières fonctions à développer est celle permettant de charger et compiler des shaders.

Personnellement, j’ai tendance à placer tout le code relatif aux shaders dans une classe en C++. Mais pour couvrir le C et C++, je vais présenter de manière itératif la procédure.

Pour le moment, le code présenté ne gère que les vertex shaders et les fragment shaders. Pour les impatients ou les partisans du « tout, tout de suite », vous trouverez l’intégralité du code en fin d’article.

La fonction principale

La fonction est déclarée ainsi :

Gluint loadShader(GLchar* vs, Glchar* fs) ;

Nous supposons que les deux shaders sont déjà stockés en RAM et accessibles par les deux pointeurs vs et fs.

Compilation

Comme il faut compiler le vertex shader et le fragment shader, autant mettre le procédé de compilation dans une seconde fonction, telle que :

bool compile(GLuint &shaderId, GLenum type, GLchar* src)
{
    shaderId = glCreateShader(type);
    if (shaderId == 0)
    {
        std::cout << "ERROR: shader type (" << type << ") does not exist" << std::endl;
        return false;
    }
    glShaderSource(shaderId, 1, &src, 0);
    glCompileShader(shaderId);

    GLint errorCp(0);
    glGetShaderiv(shaderId, GL_COMPILE_STATUS, &errorCp);
    if (errorCp != GL_TRUE)
    {
        std::cout << "ERROR: while compiling Shader :" << std::endl;
        GLint errorSize(0);
        glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &errorSize);

        char *error = new char[errorSize + 1];
        glGetShaderInfoLog(shaderId, errorSize, &errorSize, error);
        error[errorSize] = ' 0';
        std::cout << error << std::endl;

        delete[] error;
        glDeleteShader(shaderId);
        return false;
    }
    return true;
}

On appelle cette fonction deux fois depuis la fonction loadShader() :

Gluint vertexId, fragmentId ;
if (!compile(verterxId, GL_VERTEX_SHADER, vs))
{
    std::cout << "ERROR: while compiling vertex shader" << std::endl;
    return 0 ; // 0 means null in OpenGL programming
}
if (!compile(fragmentId, GL_FRAGMENT_SHADER, fs))
{
    std::cout << "ERROR: while compiling fragment shader" << std::endl;
    glDeleteShader(verterxId); 
    return 0 ; // 0 means null in OpenGL programming
}

Fusion des shaders en un seul programme

Maintenant, il faut dire à OpenGL que nos deux shaders sont liés. Pour cela, il faut créer un programme (ce qu’on appelle communément shader également).

Gluint programId = glCreateProgram();

glAttachShader(programId, verterxId);
glAttachShader(programId, fragmentId);

glLinkProgram(programId);

Contrôle des erreurs

Cette phase n’est pas obligatoire mais fortement recommandée. Il s’agit de vérifier que la fusion s’est correctement déroulée.

GLint errorlk(0);
glGetProgramiv(programId, GL_LINK_STATUS, &errorlk);
if (errorlk != GL_TRUE)
{
    std::cout << "ERROR: while linking Shader :" << std::endl;
    GLint errorSize(0);
    glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &errorSize);

    char *error = new char[errorSize + 1];
    glGetShaderInfoLog(programId, errorSize, &errorSize, error);
    error[errorSize] = ' 0';
    std::cout << error << std::endl;

    delete[] error;
    glDeleteProgram(programId);
    return 0 ; // 0 means null in OpenGL programming
}

Retourner l’ID du shader (/programme)

Enfin, si tout va bien, on retourne l’ID du shader.

return programID ;

Code complet

bool compile(GLuint &shaderId, GLenum type, GLchar* src)
{
    shaderId = glCreateShader(type);
    if (shaderId == 0)
    {
        std::cout << "ERROR: shader type (" << type << ") does not exist" << std::endl;
        return false;
    }
    glShaderSource(shaderId, 1, &src, 0);
    glCompileShader(shaderId);

    GLint errorCp(0);
    glGetShaderiv(shaderId, GL_COMPILE_STATUS, &errorCp);
    if (errorCp != GL_TRUE)
    {
        std::cout << "ERROR: while compiling Shader :" << std::endl;
        GLint errorSize(0);
        glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &errorSize);

        char *error = new char[errorSize + 1];
        glGetShaderInfoLog(shaderId, errorSize, &errorSize, error);
        error[errorSize] = ' 0';
        std::cout << error << std::endl;

        delete[] error;
        glDeleteShader(shaderId);
        return false;
    }
    return true;
}

Gluint loadShader(GLchar* vs, Glchar* fs)
{
    Gluint vertexId, fragmentId ;
    if (!compile(verterxId, GL_VERTEX_SHADER, vs))
    {
        std::cout << "ERROR: while compiling vertex shader" << std::endl;
        return 0 ; // 0 means null in OpenGL programming
    }
    if (!compile(fragmentId, GL_FRAGMENT_SHADER, fs))
    {
        std::cout << "ERROR: while compiling fragment shader" << std::endl;
        glDeleteShader(verterxId); 
        return 0 ; // 0 means null in OpenGL programming
    }

    Gluint programId = glCreateProgram();

    glAttachShader(programId, verterxId);
    glAttachShader(programId, fragmentId);

    glLinkProgram(programId);

    GLint errorlk(0);
    glGetProgramiv(programId, GL_LINK_STATUS, &errorlk);
    if (errorlk != GL_TRUE)
    {
        std::cout << "ERROR: while linking Shader :" << std::endl;
        GLint errorSize(0);
        glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &errorSize);

        char *error = new char[errorSize + 1];
        glGetShaderInfoLog(programId, errorSize, &errorSize, error);
        error[errorSize] = ' 0';
        std::cout << error << std::endl;

        delete[] error;
        glDeleteProgram(programId);
        return 0 ; // 0 means null in OpenGL programming
    }
    return programID ;
}