Jaclyn Brockschmidt
Jaclyn Brockschmidt

Reputation: 39

SDL2 with OpenGL 4.4: Triangle Not Rendering Properly

I'm using OpenGL 4.4 with SDL2. I am trying to render a simple triangle with the vertices (-1, -1, 0), (1, -1, 0), (0, 1, 0). However, when I think I'm doing everything correctly, nothing is drawn.

I extracted and reorganized the relevant code from my project:

#include <cerrno>
#include <cstring>
#include <exception>
#include <fstream>
#include <iostream>
#include <string>
#include <GL/glew.h>
#include <GL/glu.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_opengl.h>

void init();
void cleanUp();
std::string loadShader(std::string filepath);
void checkShaderSuccess(GLuint shader);

SDL_Window* win;
SDL_GLContext glContext;
GLuint program, vertShader, fragShader, vao, vbo;

class GenError: public std::exception {
 public:
        GenError():
                exception(), msg("") {}

        GenError(const std::string& m):
                exception(), msg(m) {}

        virtual ~GenError() throw() {}

        virtual const char* what() const throw() {
                return msg.c_str();
        }
 private:
        std::string msg;
};

int main() {
        init();

        program = glCreateProgram();
        if (program == 0) {
                throw GenError("Shader creation failed: "
                                  "Could not find valid memory location in "
                                  "constructor");
        }

        vertShader = glCreateShader(GL_VERTEX_SHADER);
        fragShader = glCreateShader(GL_FRAGMENT_SHADER);
        if (vertShader == 0 || fragShader == 0) {
                std::string m;
                m += "Shader creation failed: "
                        "Could not find valid memory location when "
                        "adding shader: ";
                m += (char *)gluErrorString(glGetError());
                throw GenError(m);
        }

        std::cout << "Creating vertex shader..." << std::endl;
        std::string data = loadShader("./shaders/basicVertex.vs");
        const GLchar* data_c = data.c_str();
        glShaderSource(vertShader, 1, &data_c, NULL);
        glCompileShader(vertShader);
        checkShaderSuccess(vertShader);
        glAttachShader(program, vertShader);
        std::cout << "Vertex shader created" << std::endl;

        std::cout << "Creating fragment shader..." << std::endl;
        data = loadShader("./shaders/basicFragment.fs");
        data_c = data.c_str();
        glShaderSource(fragShader, 1, &data_c, NULL);
        glCompileShader(fragShader);
        checkShaderSuccess(fragShader);
        glAttachShader(program, fragShader);
        std::cout << "Fragment shader created" << std::endl;

        glLinkProgram(program);

        GLint success;
        glGetProgramiv(program, GL_LINK_STATUS, &success);
        if (success == GL_FALSE) {
                GLint logLen = 0;
                glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLen);
                GLchar programLog[logLen];
                glGetProgramInfoLog(program, logLen, &logLen, programLog);
                std::string m;
                m += "Failed to link program: ";
                m += (char *)gluErrorString(glGetError());
                m += ": ";
                m += (char *)programLog;
                throw GenError(m);
        }

        glValidateProgram(program);

        glGetProgramiv(program, GL_VALIDATE_STATUS, &success);
        if (success == GL_FALSE) {
                GLint logLen = 0;
                glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLen);
                GLchar programLog[logLen];
                glGetProgramInfoLog(program, logLen, &logLen, programLog);
                std::string m;
                m += "Failed to validate program: ";
                m += (char *)gluErrorString(glGetError());
                m += ": ";
                m += (char *)programLog;
                throw GenError(m);
        }

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

        const GLfloat verts[] = {
                -1.0f, -1.0f, 0.0f,
                 1.0f, -1.0f, 0.0f,
                 0.0f,  1.0f, 0.0f
        };
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferData(GL_ARRAY_BUFFER,
                     sizeof(verts),
                     verts,
                     GL_STATIC_DRAW );

        SDL_Event ev;
        bool running = true;
        while (true) {
                while (SDL_PollEvent(&ev)) {
                        if (ev.type == SDL_WINDOWEVENT &&
                            ev.window.event == SDL_WINDOWEVENT_CLOSE) {
                                std::cout << "Closing window..." << std::endl;
                                running = false;
                                break;
                        }
                }

                if (!running) break;

                glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
                glUseProgram(program);

                glEnableVertexAttribArray(0);
                glBindBuffer(GL_ARRAY_BUFFER, vbo);
                glVertexAttribPointer(0,
                                      3,
                                      GL_FLOAT,
                                      GL_FALSE,
                                      3*sizeof(GLfloat),
                                      (GLvoid*)0 );

                glDrawArrays(GL_TRIANGLES, 0, 3);
                glDisableVertexAttribArray(0);

                SDL_GL_SwapWindow(win);
        }
        std::cout << "Window closed" << std::endl;

        glDeleteBuffers(1, &vbo);
        glDeleteVertexArrays(1, &vao);
        glDeleteProgram(program);
        glDeleteShader(vertShader);
        glDeleteShader(fragShader);

        cleanUp();

        return 0;
}

void init() {
        std::cout << "Initializing..." << std::endl;

        if (SDL_Init(SDL_INIT_VIDEO) != 0) {
                std::string m;
                m.append("Error initializing SDL2: ");
                m.append(SDL_GetError());
                throw GenError(m);
        }

        SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
        SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5);
        SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
        SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
        SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 4);

        win = SDL_CreateWindow("Triangle Test",
                               SDL_WINDOWPOS_UNDEFINED,
                               SDL_WINDOWPOS_UNDEFINED,
                               800, 600,
                               SDL_WINDOW_OPENGL );
        if (win == NULL) {
                throw GenError(SDL_GetError());
        }

        glContext = SDL_GL_CreateContext(win);
        if (glContext == NULL) {
                std::string m;
                m.append("Error associating window with OpenGL: SDL Error: ");
                m.append(SDL_GetError());
                throw GenError(m);
        }

        glewExperimental = GL_TRUE;
        GLenum glewErr = glewInit();
        if (glewErr != GLEW_OK) {
                std::string m;
                m.append("Error initializing OpenGL GLEW extension: ");
                m.append((const char*)glewGetErrorString(glewErr));
                throw GenError(m);
        } else {
                /* GLEW does not play nice with OpenGL 4.4.
                 * GLEW thinks OpenGL 4.4 is "pretentious" and
                 * "entitled". GLEW likes to throw an invalid
                 * enumerant error the next time glGetError is
                 * called after GLEW's initialization.
                 * glGetError must be envoked to discard this
                 * faulty error. GLEW makes my code look sloppy.
                 * We do not like GLEW. We tolerate GLEW.
                 */
                GLenum junk = glGetError();
        }

        glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
        glFrontFace(GL_CW);
        glCullFace(GL_BACK);
        glEnable(GL_CULL_FACE);
        glEnable(GL_DEPTH_TEST);

        glEnable(GL_FRAMEBUFFER_SRGB);

        if(SDL_GL_SetSwapInterval(1) < 0) {
                std::cerr << "Warning: Unable to set VSync! "
                          << "SDL Error: "
                          << SDL_GetError() << std::endl;
        }

        GLenum error = glGetError();
        if (error != GL_NO_ERROR) {
                std::string m;
                m.append("Error initializing OpenGL: OpenGL Error: ");
                m.append(reinterpret_cast<const char*>(gluErrorString(error)));
                throw GenError(m);
        }

        std::cout << "Initialized" << std::endl;
}

void cleanUp() {
        std::cout << "Cleaning up..." << std::endl;
        SDL_GL_DeleteContext(glContext);
        SDL_DestroyWindow(win);
        SDL_Quit();
        std::cout << "Cleaned" << std::endl;
}

std::string loadShader(std::string filepath) {
        std::ifstream shaderFile(filepath.c_str());
        if (!shaderFile.is_open()) {
                std::cerr << "Could not load shader: "
                          << "Error opening "
                          << filepath
                          << ": " << std::strerror(errno)
                          << std::endl;
                return std::string("");
        }

        std::string content, line;
        while (std::getline(shaderFile, line)) {
                content += line + '\n';
        }

        shaderFile.close();

        return content;
}

void checkShaderSuccess(GLuint shader) {
        GLint success;
        glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
        if (success == GL_FALSE) {
                GLint logLen = 0;
                glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLen);
                GLchar shaderLog[logLen];
                glGetShaderInfoLog(shader, logLen, &logLen, shaderLog);
                std::string m;
                m += "Shader compilation failed: ";
                m += (char *)gluErrorString(glGetError());
                m += ": ";
                m += (char *)shaderLog;
                glDeleteShader(shader);
                throw GenError(m);
        }
}

...without error catching (for faster skimming):

#include <cerrno>
#include <cstring>
#include <exception>
#include <fstream>
#include <iostream>
#include <string>
#include <GL/glew.h>
#include <GL/glu.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_opengl.h>

void init();
void cleanUp();
std::string loadShader(std::string filepath);

SDL_Window* win;
SDL_GLContext glContext;
GLuint program, vertShader, fragShader, vao, vbo;

int main() {
        init();

        program = glCreateProgram();

        vertShader = glCreateShader(GL_VERTEX_SHADER);
        fragShader = glCreateShader(GL_FRAGMENT_SHADER);

        std::cout << "Creating vertex shader..." << std::endl;
        std::string data = loadShader("./shaders/basicVertex.vs");
        const GLchar* data_c = data.c_str();
        glShaderSource(vertShader, 1, &data_c, NULL);
        glCompileShader(vertShader);
        glAttachShader(program, vertShader);
        std::cout << "Vertex shader created" << std::endl;

        std::cout << "Creating fragment shader..." << std::endl;
        data = loadShader("./shaders/basicFragment.fs");
        data_c = data.c_str();
        glShaderSource(fragShader, 1, &data_c, NULL);
        glCompileShader(fragShader);
        glAttachShader(program, fragShader);
        std::cout << "Fragment shader created" << std::endl;

        glLinkProgram(program);

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

        const GLfloat verts[] = {
                -1.0f, -1.0f, 0.0f,
                 1.0f, -1.0f, 0.0f,
                 0.0f,  1.0f, 0.0f
        };
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferData(GL_ARRAY_BUFFER,
                     sizeof(verts),
                     verts,
                     GL_STATIC_DRAW );

        SDL_Event ev;
        bool running = true;
        while (true) {
                while (SDL_PollEvent(&ev)) {
                        if (ev.type == SDL_WINDOWEVENT &&
                            ev.window.event == SDL_WINDOWEVENT_CLOSE) {
                                std::cout << "Closing window..." << std::endl;
                                running = false;
                                break;
                        }
                }

                if (!running) break;

                glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
                glUseProgram(program);

                glEnableVertexAttribArray(0);
                glBindBuffer(GL_ARRAY_BUFFER, vbo);
                glVertexAttribPointer(0,
                                      3,
                                      GL_FLOAT,
                                      GL_FALSE,
                                      3*sizeof(GLfloat),
                                      (GLvoid*)0 );

                glDrawArrays(GL_TRIANGLES, 0, 3);
                glDisableVertexAttribArray(0);

                SDL_GL_SwapWindow(win);
        }
        std::cout << "Window closed" << std::endl;

        glDeleteBuffers(1, &vbo);
        glDeleteVertexArrays(1, &vao);
        glDeleteProgram(program);
        glDeleteShader(vertShader);
        glDeleteShader(fragShader);

        cleanUp();

        return 0;
}

void init() {
        std::cout << "Initializing..." << std::endl;

        SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
        SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5);
        SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
        SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
        SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 4);

        win = SDL_CreateWindow("Triangle Test",
                               SDL_WINDOWPOS_UNDEFINED,
                               SDL_WINDOWPOS_UNDEFINED,
                               800, 600,
                               SDL_WINDOW_OPENGL );

        glContext = SDL_GL_CreateContext(win);

        glewExperimental = GL_TRUE;
        GLenum glewErr = glewInit();

        glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
        glFrontFace(GL_CW);
        glCullFace(GL_BACK);
        glEnable(GL_CULL_FACE);
        glEnable(GL_DEPTH_TEST);

        glEnable(GL_FRAMEBUFFER_SRGB);

        std::cout << "Initialized" << std::endl;
}

void cleanUp() {
        std::cout << "Cleaning up..." << std::endl;
        SDL_GL_DeleteContext(glContext);
        SDL_DestroyWindow(win);
        SDL_Quit();
        std::cout << "Cleaned" << std::endl;
}

std::string loadShader(std::string filepath) {
        std::ifstream shaderFile(filepath.c_str());

        std::string content, line;
        while (std::getline(shaderFile, line)) {
                content += line + '\n';
        }

        shaderFile.close();

        return content;
}

...my vertex shader (GLSL):

#version 440

layout (location = 0) in vec3 position;

void main() {
     gl_Position = vec4(0.5 * position, 1.0);
}

...and my fragment shader:

#version 440

out vec4 fragColor;

void main() {
     fragColor = vec4(0.0, 1.0, 1.0, 1.0);
}

Now oddly enough, when I change line 148 in my C++ code (with error catching) from this...

3*sizeof(GLfloat),

...to this (in other words, changing the stride)...

3*sizeof(GLdouble),

...compiling and running produces a triangle with the vertices (-1, -1, 0), (0, 0, 0), (0, 1, 0). The second vertex is apparently getting obscured. Instead of an isosceles triangle, I get a scalene triangle.

I would like to 1) figure out how to fix my program so that it displays a triangle with the specified vertices, and 2) understand what I did wrong initially that caused my such an odd result when modifying the aforementioned line of code.

I have been tinkering with this for almost a week. Any insight is appreciated. Thanks!

Upvotes: 0

Views: 1677

Answers (1)

Reto Koradi
Reto Koradi

Reputation: 54592

Your code has a problem with the winding order of the polygons. You specify clockwise winding for the front faces, and enable culling of the back faces:

glFrontFace(GL_CW);
glCullFace(GL_BACK);

But the triangle has counter-clockwise winding order:

const GLfloat verts[] = {
    -1.0f, -1.0f, 0.0f,
     1.0f, -1.0f, 0.0f,
     0.0f,  1.0f, 0.0f
};

This means that the triangle will be eliminated by culling.

Using counter-clockwise winding is mostly standard in OpenGL, and is also the default. So the best option is that you simply remove this line of code:

glFrontFace(GL_CW);

This will leave the value at GL_CCW, which matches your geometry.

Disabling backface culling is always one of the first things you should do when polygons don't show up. Having the wrong winding order is one of the most common causes of things not rendering, and it's very easy to triage by simply disabling culling, and checking if that makes the geometry show up.

Upvotes: 6

Related Questions