Search code examples
c++openglglslshaderglew

Is dynamically load shaders in OpenGL senseless?


I just begin my journey in graphical computing, so I started by understanding this tutorial: https://learnopengl.com/Getting-started/Hello-Triangle.

Before adventuring myself to do some "dynamic" drawing I thought transform this into a "Renderer" class would be interesting this was the result:

#pragma once

#include <fstream>
#include <vector>

#include "pch.h"

class Renderer {
public:
    Renderer(GLFWwindow*);

    void initVertexShaders(std::vector<std::string>&);
    void initFragmentShaders(std::vector<std::string>&);
    void load(float*, size_t);
    void draw();

    const GLubyte* renderer;
    const GLubyte* version;

private:
    GLFWwindow* window = nullptr;

    GLuint vbo;
    GLuint vao;

    GLuint shaderProgram = 0; //= glCreateProgram();
    GLuint vs[100];
    GLuint fs[100];
};

pch.h is a precompiled header in order to not compile glm, glfw and glew everytime y build the project.

The idea was to isolate different utilities, I would use load(float*, size_t) to send information to the gpu, and draw() during the main window loop. The idea is to make a first approximation to a 2d, cameraless render engine.

Anyway, source code is:

#include "Renderer.hpp"

Renderer::Renderer(GLFWwindow* window) {
    this->window = window;
    glfwMakeContextCurrent(window);

    glewExperimental = GL_TRUE;
    glewInit();

    this->renderer  =   glGetString(GL_RENDERER);
    this->version   =   glGetString(GL_VERSION);

    this->vao       =   0;
    this->vbo       =   0;
}

void Renderer::load(float* points, size_t memory) {
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, memory, points, GL_STATIC_DRAW);

    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);
    glEnableVertexAttribArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
}

void Renderer::draw() {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glUseProgram(shaderProgram);
    glBindVertexArray(vao);

    glDrawArrays(GL_TRIANGLES, 0, 6);
}

void Renderer::initVertexShaders(std::vector<std::string>& paths) {
    int i = 0;
    std::ifstream ifs;

    if(this->shaderProgram == 0) shaderProgram = glCreateProgram();

    for (const auto& path : paths){
        ifs.open(path);    
        std::string program_str((  std::istreambuf_iterator<char>(ifs)),
                                (  std::istreambuf_iterator<char>()  ));
        const char* program_src = program_str.c_str();
        vs[i] = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vs[i], 1, &program_src, NULL);
        glCompileShader(vs[i]);
        glAttachShader(shaderProgram, vs[i]);
        i++;
    }
    glLinkProgram(shaderProgram);
}

void Renderer::initFragmentShaders(std::vector<std::string>& paths) {
    int i = 0;
    std::ifstream ifs;

    if(this->shaderProgram == 0) shaderProgram = glCreateProgram();

    for (const auto& path : paths){

        ifs.open();
        std::string program_str((  std::istreambuf_iterator<char>(ifs)),
                                (  std::istreambuf_iterator<char>()  ));
        const char* program_src = program_str.c_str();

        fs[i] = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fs[i], 1, &program_src, NULL);
        glCompileShader(fs[i]);
        glAttachShader(shaderProgram, fs[i]);
        i++;
    }
    glLinkProgram(shaderProgram);
}

The problem comes at shader time, I can read the files without problem, but they do nothing.

The questions are: Does store all the shaders in an array make sense? Can a shader program consist in more than one vertex shader/fragment shader? Is the error in another spot? Does the whole class make sense in any way?

Thank you.

edit: Code from main.cpp

#include "Renderer.hpp"

    float points[] = {
        0.5f,  0.5f,  0.0f,
        0.5f, -0.5f,  0.0f,
        -0.5f, -0.5f, 0.0f,
        //
         0.5f, 0.5f,  0.0f,
        -0.5f, 0.5f,  0.0f,
        -0.5f, -0.5f, 0.0f

    };

int main() {

    std::vector<std::string> vertexShaders;
    std::vector<std::string> fragmentShaders;

    vertexShaders.push_back("shader/vs.glsl");
    fragmentShaders.push_back("shader/fs.glsl");

    glfwInit();                                                         //glfwGetPrimaryMonitor()
    GLFWwindow *window = glfwCreateWindow(960, 540, "Hello Triangle",   NULL, NULL);
    Renderer Ren(window);

    Ren.load(points, sizeof(points));
    Ren.initVertexShaders(vertexShaders);
    Ren.initFragmentShaders(fragmentShaders);

    while (!glfwWindowShouldClose(window)) {
        Ren.draw();
        glfwPollEvents();
        glfwSwapBuffers(window);
    }

    glfwTerminate();
    return 0;
}

Solution

  • It is sufficient to link the shader program object once, after all attached shader objects have been compiled.


    OpenGL 4.6 API Core Profile Specification; 7.3. PROGRAM OBJECTS; page 94:

    Shader objects may be attached to program objects before source code has been loaded into the shader object, or before the shader object has been compiled or specialized.

    Note, it is sufficient that the shader object is successfully compiled, before the shader program object, where it is attached to, gets linked.


    OpenGL 4.6 API Core Profile Specification; 7.3. PROGRAM OBJECTS; page 94:

    Multiple shader objects of the same type may be attached to a single program object, and a single shader object may be attached to more than one program object.

    e.g.

    One shader object contains a function (FragColor)

    #version 460
    
    uniform sampler2D u_texture;
    
    vec4 FragColor(vec2 uv)
    {
        return texture(u_texture, uv);
    }
    

    A second shader object of the same type contains the function signature (but not the implementation) and the usage of the function.

    #version 460
    
    in vec2 vUV;
    out vec4 fragColor;
    
    vec4 FragColor(vec2 uv);
    
    void main()
    {
        fragColor = FragColor(vUV);
    }
    

    Both of the above code snippets can be placed to 2 separate shader objects of type GL_FRAGMENT_SHADER. Each of the 2 shader objects can be successfully compiled. And if they are attached to the same shader program object, then the shader program object can be successfully liked.

    See also Attaching multiple shaders of the same type in a single OpenGL program?


    Further, I recommend to check if a shader object was successfully compiled:

    GLuint shaderObj = .... ;
    glCompileShader( shaderObj );
    
    GLint status = GL_TRUE;
    glGetShaderiv( shaderObj, GL_COMPILE_STATUS, &status );
    if ( status == GL_FALSE )
    {
        GLint logLen;
        glGetShaderiv( shaderObj, GL_INFO_LOG_LENGTH, &logLen );
        std::vector< char >log( logLen );
        GLsizei written;
        glGetShaderInfoLog( shaderObj, logLen, &written, log.data() );
        std::cout << "compile error:" << std::endl << log.data() << std::endl;
    }
    

    and a shader program object was successfully linked:

    GLuint progObj = ....;
    glLinkProgram( progObj );
    
    GLint status = GL_TRUE;
    glGetProgramiv( progObj, GL_LINK_STATUS, &status );
    if ( status == GL_FALSE )
    {
        GLint logLen;
        glGetProgramiv( progObj, GL_INFO_LOG_LENGTH, &logLen );
        std::vector< char >log( logLen );
        GLsizei written;
        glGetProgramInfoLog( progObj, logLen, &written, log.data() );
        std::cout  << "link error:" << std::endl << log.data() << std::endl;
    }