Search code examples
c++openglglslglfwvao

OpenGL GLFW, triangle not rendering


I've been trying to render a triangle on macOS but it doesn't work.

Here is my code:

#include <iostream>
#include <glm/glm.hpp>
#include <GL/glew.h>
#include <GLFW/glfw3.h>

void OnError(int errorCode, const char* msg){
    throw std::runtime_error(msg);
}

int main(){
    glfwSetErrorCallback(OnError);
    if (!glfwInit()){
        throw std::runtime_error("glfwInit failed");
    }
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

    GLFWwindow* window = glfwCreateWindow(512, 512, "Title", NULL, NULL);
    if (!window){
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);

    glewExperimental = GL_TRUE;
    if (glewInit() != GLEW_OK){
        glfwTerminate();
        return -1;
    }

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);

    float points[] = {
        0.0, 0.5, 0.0,
        -0.5, -0.5, 0.0,
        0.5, -0.5, 0.0
    };

    GLuint vbo = 0;
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(points), points, GL_STATIC_DRAW);

    GLuint vao = 0;
    glGenBuffers(1, &vao);
    glBindVertexArray(vao);
    glDisableVertexAttribArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);

    const char* vertex_shader =
    "#version 410\n"
    "in vec3 vp;"
    "void main(){"
    " gl_Position = vec4(vp, 1.0);"
    " };";

    GLuint vs = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vs, 1, &vertex_shader, NULL);
    glCompileShader(vs);

    const char* fragment_shader =
    "#version 410\n"
    "out vec4 frag_colour;"
    "void main(){"
    " frag_colour = vec4(0.0, 1.0, 0.0, 1.0);"
    " };";

    GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fs, 1, &fragment_shader, NULL);
    glCompileShader(fs);

    GLuint shader_program = glCreateProgram();
    glAttachShader(shader_program, fs);
    glAttachShader(shader_program, vs);
    glLinkProgram(shader_program);

    while (!glfwWindowShouldClose(window)){
        //glViewport(0, 0, 512, 512);
        //glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glUseProgram(shader_program);
        glBindVertexArray(vao);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        glBindVertexArray(0);
        glfwSwapBuffers(window);

        glfwPollEvents();
        glClear(GL_COLOR_BUFFER_BIT);
        glEnableVertexAttribArray(0);
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        glDisableVertexAttribArray(0);
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwTerminate();
    return 0;
}

Solution

  • A Vertex array object name has to be generated by glGenVertexArrays rather than glGenBuffers:

    glGenBuffers(1, &vao);

    glGenVertexArrays(1, &vao);
    

    State vertex array specification and the enable state is stored in the vertex array object. Create the object name and bind (create) the vertex array object. Then the vertex arrays can be specified and enabled:

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

    It is sufficient to bind the vertex array object, before the geometry is drawn:

    while (!glfwWindowShouldClose(window))
    {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
        glUseProgram(shader_program);
    
        glBindVertexArray(vao);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        glBindVertexArray(0);
    
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    

    Note, in your application a core profile OpenGL Context is created (GLFW_OPENGL_CORE_PROFILE). This causes that the default vertex array object (0) is not valid.
    Instructions like glVertexAttribPointer and glEnableVertexAttribArray change the state of the vertex array object. If no named vertex array object is bound when this functions are called, this causes an INVALID_OPERATION error.


    Furthermore, you've created a 3.2 core profile OpneGL context. The GLSL version which corresponds to OpenGL 3.2 is 1.50.
    Change the version specification in the vertex and fragment shader:

    #version 410

    #version 150
    

    The final triangle:


    I recommend to check if the shader compilation succeeded and if the program object linked successfully.

    If the compiling of a shader succeeded can be checked by glGetShaderiv and the parameter GL_COMPILE_STATUS. e.g.:

    #include <iostream>
    #include <vector>
    
    bool CompileStatus( GLuint shader )
    {
        GLint status = GL_TRUE;
        glGetShaderiv( shader, GL_COMPILE_STATUS, &status );
        if (status == GL_FALSE)
        {
            GLint logLen;
            glGetShaderiv( shader, GL_INFO_LOG_LENGTH, &logLen );
            std::vector< char >log( logLen );
            GLsizei written;
            glGetShaderInfoLog( shader, logLen, &written, log.data() );
            std::cout << "compile error:" << std::endl << log.data() << std::endl;
        }
        return status != GL_FALSE;
    }
    

    If the linking of a program was successful can be checked by glGetProgramiv and the parameter GL_LINK_STATUS. e.g.:

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

    Call the functions after compiling the shaders, respectively linking the program:

    GLuint vs = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vs, 1, &vertex_shader, NULL);
    glCompileShader(vs);
    CompileStatus(vs);
    
    GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fs, 1, &fragment_shader, NULL);
    glCompileShader(fs);
    CompileStatus(fs);
    
    GLuint shader_program = glCreateProgram();
    glAttachShader(shader_program, fs);
    glAttachShader(shader_program, vs);
    glLinkProgram(shader_program);
    LinkStatus(shader_program);