SDL2, Shader Class, Blended Triangle

Notes:

I was worried about converting my first assignment in this book. I am still learning C++ so there was a lot of extra time taken to learn what a map was and about references. The class for loading shaders is a neat piece of kit. The conversion to SDL2 was straight forward having to do it so many times during the last homework assignments on the OGL SuperBible. I'm glad it all worked out and hopefully having one more example of how to setup a modern OpenGL program with SDL2 will help someone.

Makefile:

NAME = main
SRCPATH = ./
LIBDIRS = -L/usr/local/lib
INCDIRS = -I/usr/include -I/usr/local/include -I/usr/include/GL

CC = g++
CFLAGS = -g $(INCDIRS)
LIBS = -lGL -lGLU -lSDL2main -lSDL2 -lGLEW

all: 
	$(CC) $(CFLAGS) $(LIBS) $(SRCPATH)$(NAME).cpp GLSLShader.cpp -o 00

Vertex Shader:

// blend vertex shader
# version 330 core

layout(location = 0) in vec3 vVertex;
layout(location = 1) in vec3 vColor;

smooth out vec4 vSmoothColor;

uniform mat4 MVP;

void main()
{
    // assign the per-vertex color to vSmoothColor
    vSmoothColor = vec4(vColor, 1);

    // get the clip space position by multiplying the combined MVP with the object space
    // vertex position
    gl_Position = MVP * vec4(vVertex, 1);
}

Fragment Shader:

// blend fragment shader
# version 330 core

layout(location = 0) out vec4 vFragColor;

smooth in vec4 vSmoothColor;

void main()
{
    vFragColor = vSmoothColor;
}

GLSLShader.h:

#pragma once
#include <GL/glew.h>
#include <map>
#include <string>

using namespace std;

class GLSLShader
{
public:
    GLSLShader(void);
    ~GLSLShader(void);

    void LoadFromString(GLenum whichShader, const string& source);
    void LoadFromFile(GLenum whichShader, const string& filename);
    void CreateAndLinkProgram();
    void Use();
    void UnUse();
    void AddAttribute(const string& attribute);
    void AddUniform(const string& uniform);
    
    // An indexer that returns the location of the attribute / uniform
    GLuint operator[] (const string& attribute);
    GLuint operator() (const string& uniform);
    void DeleteShaderProgram();

private:
    enum ShaderType{VERTEX_SHADER, FRAGMENT_SHADER, GEOMETRY_SHADER};
    GLuint _program;
    int _totalShaders;
    GLuint _shaders[3]; // 0: vertex shader | 1: fragment shader | 2: geometry shader
    map<string, GLuint> _attributeList;
    map<string, GLuint> _uniformLocationList;
};

GLSLShader.cpp:

#include "GLSLShader.h"

GLSLShader::GLSLShader(void)
{
    _totalShaders = 0;
    _shaders[VERTEX_SHADER] = 0;
    _shaders[FRAGMENT_SHADER] = 0;
    _shaders[GEOMETRY_SHADER] = 0;
    _attributeList.clear();
    _uniformLocationList.clear();
}

GLSLShader::~GLSLShader(void)
{
    _attributeList.clear();
    _uniformLocationList.clear();
}

void GLSLShader::DeleteShaderProgram()
{
    glDeleteProgram(_program);
}

void GLSLShader::LoadFromString(GLenum type, const string& source)
{
    GLuint shader = glCreateShader(type);

    const char *ptmp = source.c_str();
    glShaderSource(shader, 1, &ptmp, NULL);

    // check whether the shader loads fine
    GLint status;
    glCompileShader(shader);
    glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
    if (status == GL_FALSE)
    {
        GLint infoLogLength;
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength);
        GLchar *infoLog = new  GLchar[infoLogLength];
        glGetShaderInfoLog(shader, infoLogLength, NULL, infoLog);
        printf("Compile Log: %s\n", infoLog);
        delete[] infoLog;
    }
    _shaders[_totalShaders++] = shader;
}

void GLSLShader::CreateAndLinkProgram()
{
    _program = glCreateProgram();
    if (_shaders[VERTEX_SHADER] != 0)
    {
        glAttachShader(_program, _shaders[VERTEX_SHADER]);
    }
    if (_shaders[FRAGMENT_SHADER] != 0)
    {
        glAttachShader(_program, _shaders[FRAGMENT_SHADER]);
    }
    if (_shaders[GEOMETRY_SHADER] != 0)
    {
        glAttachShader(_program, _shaders[GEOMETRY_SHADER]);
    }

    // link and check whether the program links fine
    GLint status;
    glLinkProgram(_program);
    glGetProgramiv(_program, GL_LINK_STATUS, &status);
    if (status == GL_FALSE)
    {
        GLint infoLogLength;
        glGetProgramiv(_program, GL_INFO_LOG_LENGTH, &infoLogLength);
        GLchar *infoLog = new GLchar[infoLogLength];
        glGetProgramInfoLog(_program, infoLogLength, NULL, infoLog);
        printf("Link Log: %s\n", infoLog);
        delete[] infoLog;
    }
    glDeleteShader(_shaders[VERTEX_SHADER]);
    glDeleteShader(_shaders[FRAGMENT_SHADER]);
    glDeleteShader(_shaders[GEOMETRY_SHADER]);
}

void GLSLShader::Use()
{
    glUseProgram(_program);
}

void GLSLShader::UnUse()
{
    glUseProgram(0);
}

void GLSLShader::AddAttribute(const string& attribute)
{
    _attributeList[attribute] = glGetAttribLocation(_program, attribute.c_str());
}

// An indexer that returns the location of the attribute
GLuint GLSLShader::operator[](const string& attribute)
{
    return _attributeList[attribute];
}

void GLSLShader::AddUniform(const string& uniform)
{
    _uniformLocationList[uniform] = glGetUniformLocation(_program, uniform.c_str());
}

GLuint GLSLShader::operator()(const string& uniform)
{
    return _uniformLocationList[uniform];
}

#include <fstream>
void GLSLShader::LoadFromFile(GLenum whichShader, const string& filename)
{
    ifstream fp;
    fp.open(filename.c_str(), ios_base::in);
    if (fp)
    {
        string line, buffer;
        while (getline(fp, line))
        {
            buffer.append(line);
            buffer.append("\n");
        }
        // copy to source
        LoadFromString(whichShader, buffer);
    }
    else
    {
        printf("Error loading shader: %s\n", filename);
    }
}

main.cpp:

/*
 * 00 Create our first GLSL shader class
 *
 * We will also be adding to this lesson:
 * - Full SDL2 support
 * - A vertex buffer object
 * - A index buffer object
 */

#include <stdio.h>

// graphics libraries
#include <GL/glew.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_opengl.h>
#define GL_CHECK_ERRORS assert(glGetError() == GL_NO_ERROR);

// math libraries
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

// our files
#include "GLSLShader.h"

// our shader reference
GLSLShader shader;

// buffer objects
GLuint  vaoID;
GLuint  vboVerticesID;
GLuint  vboIndicesID;

// vertex structure for interleaved attributes
struct vertex {
    glm::vec3 position;
    glm::vec3 color;
};

// triangle vertices
vertex      vertices[3];
GLushort    indices[3];

// projection and model view matrices
glm::mat4   P   =   glm::mat4(1);
glm::mat4   MV  =   glm::mat4(1);

// Setup OpenGL
void SetupGL()
{
    // Set background color
    glClearColor(0.8f, 0.8f, 0.9f, 1.0f);

    GL_CHECK_ERRORS

    // Load the shader
    shader.LoadFromFile(GL_VERTEX_SHADER, "shaders/blend.vs");
    shader.LoadFromFile(GL_FRAGMENT_SHADER, "shaders/blend.fs");
    // compile and link
    shader.CreateAndLinkProgram();
    shader.Use();
        // Add attrubites
        shader.AddAttribute("vVertex");
        shader.AddAttribute("vColor");
        shader.AddAttribute("MVP");
    shader.UnUse();

    GL_CHECK_ERRORS

    // setup tringle geometry and vertices
    vertices[0].color = glm::vec3(1, 0, 0);
    vertices[1].color = glm::vec3(0, 1, 0);
    vertices[2].color = glm::vec3(0, 0, 1);

    vertices[0].position = glm::vec3(-1, -1, 0);
    vertices[1].position = glm::vec3( 0,  1, 0);
    vertices[2].position = glm::vec3( 1, -1, 0);

    // setup triangle indices
    indices[0] = 0;
    indices[1] = 1;
    indices[2] = 2;

    GL_CHECK_ERRORS

    // setup triangle vao and vbo and ibo
    glGenVertexArrays(1, &vaoID);
    glGenBuffers(1, &vboVerticesID);
    glGenBuffers(1, &vboIndicesID);
    GLsizei stride = sizeof(vertex);

    glBindVertexArray(vaoID);
        glBindBuffer(GL_ARRAY_BUFFER, vboVerticesID);
        // pass triangle vertices to buffer object
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices[0], GL_STATIC_DRAW);
        GL_CHECK_ERRORS
        // enable vertex attribute array for position
        glEnableVertexAttribArray(shader["vVertex"]);
        glVertexAttribPointer(shader["vVertex"], 3, GL_FLOAT, GL_FALSE, stride, 0);
        GL_CHECK_ERRORS
        // enable vertex attribute array for color
        glEnableVertexAttribArray(shader["vColor"]);
        glVertexAttribPointer(shader["vColor"], 3, GL_FLOAT, GL_FALSE, stride, (const GLvoid*)offsetof(vertex, color));
        GL_CHECK_ERRORS
        // pass indices to element array buffer
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIndicesID);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), &indices[0], GL_STATIC_DRAW);
        GL_CHECK_ERRORS
    printf("GL Init successful!\n");
}


// Print SDL's error
void PrintSDLError()
{
    printf("SDL Error: %s\n", SDL_GetError());
}

// Setup SDL
SDL_Window *mainWindow = NULL;
SDL_GLContext glContext;
void SetupSDL()
{
    // Initialize
    if (SDL_Init(SDL_INIT_VIDEO) < 0)
    {
        PrintSDLError();
    }
    else
    {
        // Use OpenGL 3.3 core
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);

        // Create window
        mainWindow = SDL_CreateWindow("00",
                                      SDL_WINDOWPOS_UNDEFINED,
                                      SDL_WINDOWPOS_UNDEFINED,
                                      640,
                                      480,
                                      SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN);
        if (mainWindow == NULL)
        {
            PrintSDLError();
        }
        else
        {
            // Create Context
            glContext = SDL_GL_CreateContext(mainWindow);
            if (glContext == NULL)
            {
                PrintSDLError();
            }
            else
            {
                // Initialize GLEW
                glewExperimental = GL_TRUE;
                GLenum glewError = glewInit();
                if (glewError != GLEW_OK)
                {
                    printf("%s\n", glewGetErrorString(glewError));
                }

                // vsync
                if (SDL_GL_SetSwapInterval(1) < 0)
                {
                    PrintSDLError();
                }
            }
        }
    }
}

// Render our content
void Render()
{
	// Clear the window with current clearing color
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // bind the shader
    shader.Use();
        // pass the shader uniform
        glUniformMatrix4fv(shader("MVP"), 1, GL_FALSE, glm::value_ptr(P * MV));
        // draw triangle
        glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, 0);
    // unbind the shader
    shader.UnUse();

    SDL_GL_SwapWindow(mainWindow);
}

// Cleanly close the program
void Shutdown()
{
    // Destroy shader
    shader.DeleteShaderProgram();
    // Destroy vao and vbo
    glDeleteBuffers(1, &vboVerticesID);
    glDeleteBuffers(1, &vboIndicesID);
    glDeleteVertexArrays(1, &vaoID);
    // Cleanly quit SDL
    SDL_DestroyWindow(mainWindow);
    mainWindow = NULL;
    SDL_Quit();
}

// Main
int main()
{
    SetupSDL();
    SetupGL();
    bool running = true;
    SDL_Event event;
    while (running)
    {
        //Handle Events on queue
        while(SDL_PollEvent(&event) != 0)
        {
            //User requests quit
            if(event.type == SDL_QUIT)
            {
                running = false;
            }
            if (event.type == SDL_KEYDOWN)
            {
                switch (event.key.keysym.sym)
                {
                    case SDLK_q:
                        running = false;
                        break;
                    default:
                        break;
                }
            }
        }
        Render();
        //Update screen
        SDL_GL_SwapWindow(mainWindow);
    }
    return 0;
}

Answer:


Back to index