GLider v0.1.0-2-g32ec02b
GLider - OpenGL C++ Class Abstraction

GitHub release (latest by date) GitHub

Author
Swarnava Ghosh

Introduction

GLider is an OpenGL class abstraction library. It uses glad to load the OpenGL functions, which can be customized using CMake.

Usage

To get the best idea on how to use this library, look at the code for rendering a triangle step by step:

Window Creation

Since window management is not part of the OpenGL specifications, this step is left to the user to accomplish. Good window managemenent libraries include GLFW and SDL

Acquire OpenGL Context

An OpenGL context must be acquired before initializing GLider. Since context creation is not part of OpenGL specifications, it is not part GLider. Normally, your window managerment library will provide functions to get this context.

 // GLFW
 glfwMakeContextCurrent(window);

 // SDL
 SDL_GL_CreateContext(window);

Initialize GLider

Once you have the OpenGL context, we can initiate GLider. If you have a function that retrives OpenGL functions, you can pass this as an argument. Again, that OpenGL function getter may be provided to you by your window managment library.

// If no OpenGL function getter present
// OR if using SDL
gli::initialize(SDL_GL_GetProcAddress);
// OR if using GLFW
gli::initialize(glfwGetProcAddress);
void initialize()

Shader Sources

const char* vertexShader = R"CODE(
#version 140
in vec4 vertexPos;
in vec3 vertexColor;
out vec3 fragmentColor;
void main(){
gl_Position = vertexPos;
fragmentColor = vertexColor;
}
)CODE";
const char* fragmentShader = R"CODE(
#version 140
in vec3 fragmentColor;
void main(){
gl_FragColor = vec4(fragmentColor, 1.f);
}
)CODE";

This is the OpenGL shader source code. You can define a string, or you can write it in a seperate file.

Glider Variable Declarations

Definition: Buffer.hpp:63
Definition: ShaderProgram.hpp:59
Definition: VertexArray.hpp:30

Declares variables using GLider classes

Vertex Data

std::array<float, 5*3> vertecies = {
//---Positions---||-----Colors-----||
-.75, -.75, 1.f, 0.f, 0.f,
0, .75, 0.f, 1.f, 0.f,
.75, -.75, 0.f, 0.f, 1.f
};

Vertex data can be specified using any C++ standard container, as long as the containers have data() and size() member functions.

Storing Vertex Data in VRAM

vb.feedData(vertecies, gli::UseDynamicDraw);
{
gli::Layout layout;
layout.push<float>(gli::D2, false);
layout.push<float>(gli::D3, false);
vb,
layout
);
}
void feedData(const T *data, unsigned int dataCount, BufferUsage usage)
Definition: VertexArray.hpp:14
void push(Dimensions count, bool normalized)
void readBufferData(const Buffer< VertexBuffer > &vb, const Layout &layout, unsigned int startingAttribIndex=0)
@ D3
Definition: VertexArray.hpp:11
@ D2
Definition: VertexArray.hpp:11
@ UseDynamicDraw
Definition: Buffer.hpp:48

These lines are responsible for feeding the vertex data to OpenGL and telling it how to interpret the data. The first thing that needs to be done is to feed the data into a vertex buffer (through a gli::Buffer<gli::VertexBuffer> object). Once that is done, a vertex array is supposed to read and make sense of the data. For this, we need to specify the structure of the data using an array (or vector) of gli::LayoutElement objects.

Vertex Attribute Locations

shaders.bindAttribLocation(0, "vertexPos");
shaders.bindAttribLocation(1, "vertexColor");
void bindAttribLocation(unsigned int index, const char *name) const noexcept

We need to specify the appropriate vertex attribute locations so that the shader can find those attributes. This step is not necessary if you decide to use the layout qualifier within your shader source code.

Shader Compilation

shaders.compileString(gli::VertexShader, vertexShader); // shaders.compileFile() can also be used if you
shaders.compileString(gli::FragmentShader, fragmentShader); // wrote your shader source in a separate file
shaders.link();
shaders.validate();
void validate() const
void link() const
void compileString(ShaderType shaderType, const char *sourceCode)

A lot of the OpenGL shader compilation procedures are automated through GLider. If you wrote your source code in a seperate file, you can use gli::ShaderProgram::compileFile(ShaderType, const char*) instead of gli::ShaderProgram::compileString(ShaderType, const char*).

Binding

va.bind();
shaders.bind();

These lines just ensure that the correct objects are bound for OpenGl to read from.

Render Loop

void draw(DrawType mode, int first, int count) const noexcept
void clear(BufferBit mask) noexcept
@ DepthBufferBit
Definition: OpenGLBase.hpp:117
@ ColorBufferBit
Definition: OpenGLBase.hpp:116
@ DrawTriangles
Definition: OpenGLBase.hpp:103

Once all the variables are set, we can loop through this piece of code to render triangle. It clears the color and depth buffers, and draws the shape described by the data we spent so long organizing for OpenGL.

Swap

To actually update the contents of the screen, we must update the view buffer. This is not part of OpenGL specifications, and thus is left out of GLider. Your window management library should have some feature to swap the buffers.

// GLFW
glfwSwapBuffers(window);
// SDL
SDL_GL_SwapWindow(window);

Event Handling

In the main render loop, after the drawing and swapping, comes your event handling code, which is again not the concern of OpenGL.

Everything Together

The following code does exactly what the above section does, with SDL as the window management library. Note that there is a seperate piece of helper code outside of view that makes managing SDL easier.

#include <cstdio>
#include <cstdlib>
#include <chrono>
#include "util.hpp"
const char* vertexShader = R"CODE(
#version 140
in vec4 vertexPos;
in vec3 vertexColor;
out vec3 fragmentColor;
void main(){
gl_Position = vertexPos;
fragmentColor = vertexColor;
}
)CODE";
const char* fragmentShader = R"CODE(
#version 140
in vec3 fragmentColor;
void main(){
gl_FragColor = vec4(fragmentColor, 1.f);
}
)CODE";
int main(int argc, char* argv[]){
// if no arguments are passed, keep window open until terminated
// If valid float has been passed, keep window open for that many milliseconds
float openDuration_ms = 0.f;
auto start = std::chrono::steady_clock::now();
if(argc >= 2) openDuration_ms = std::atof(argv[1]);
try{
SDL sdl(3,0);
SDL_DisplayMode dm;
if (SDL_GetDesktopDisplayMode(0, &dm) != 0)
throw std::runtime_error(SDL_GetError());
SDL::OpenGLWindow win{"Triangle", (dm.w*3)/4, (dm.h*3)/4};
gli::initialize(SDL_GL_GetProcAddress);
std::array<float, 5*3> vertecies = {
//---Positions---||-----Colors-----||
-.75, -.75, 1.f, 0.f, 0.f,
0, .75, 0.f, 1.f, 0.f,
.75, -.75, 0.f, 0.f, 1.f
};
vb.feedData(vertecies, gli::UseDynamicDraw);
{
gli::Layout layout;
layout.push<float>(gli::D2, false);
layout.push<float>(gli::D3, false);
vb,
layout
);
}
shaders.bindAttribLocation(0, "vertexPos");
shaders.bindAttribLocation(1, "vertexColor");
shaders.compileString(gli::VertexShader, vertexShader); // shaders.compileFile() can also be used if you
shaders.compileString(gli::FragmentShader, fragmentShader); // wrote your shader source in a separate file
shaders.link();
shaders.validate();
va.bind();
shaders.bind();
bool keepRunning = true;
SDL_Event e;
while(keepRunning){
win.swap();
while(SDL_PollEvent(&e)){
switch(e.type){
case SDL_QUIT:
keepRunning = false;
break;
case SDL_KEYDOWN:
switch(e.key.keysym.sym){
case SDLK_ESCAPE:
if(e.key.keysym.mod & KMOD_SHIFT)
keepRunning = false;
break;
}
break;
}
}
if(openDuration_ms > 0.f){
if(
std::chrono::duration_cast<std::chrono::duration<float, std::milli>>
(std::chrono::steady_clock::now() - start).count() >= openDuration_ms
)
keepRunning = false;
}
}
}catch(std::exception& e){
std::printf("Error Occured: %s\n", typeid(e).name());
std::printf("%s", e.what());
}
return 0;
}
Main header file for GLider which includes all other header files.
int main()
Definition: init.dox:3