, dev
Getting Started with OpenGL in C++
Learning OpenGL has been on my to-do list for as long as I can remember, but before getting too crazy on it, I wanted to start slowly by focusing on a simple project.
I hence began by laying out the structure of the project. I wanted to have the core to deal only with OpenGL, while having hooks with GUI toolkits—such as Qt and GLFW—being built on top of it in separate modules.
For testing purposes, I figured that the core OpenGL library could be as simple as a single class containing the required methods to setup and cleanup the OpenGL context, with a draw()
method for the main rendering loop.
// core/openglrenderer.h
#include <GLXW/glxw.h>
class OpenGLRenderer
{
public:
OpenGLRenderer();
void setup();
void draw();
void cleanup();
private:
bool _setup;
GLuint _program;
};
Note that GLXW is one of the many OpenGL loader available. There is no special reason why I picked this one over another one, they are all similar enough.
The Qt 5 Struggle
With this class prototype in place and a simple call to glClearBufferfv()
within the draw()
method, I was ready to give a go at hooking up Qt 5.
// qt5/openglsurface.h
#include <QtGui/QWindow>
class QOpenGLContext;
class OpenGLRenderer;
class OpenGLSurface :
public QWindow
{
Q_OBJECT
public:
OpenGLSurface(QScreen *screen = 0);
OpenGLSurface(QWindow *parent);
~OpenGLSurface();
void render();
private:
QOpenGLContext *_context;
OpenGLRenderer *_renderer;
};
This is a simplification of the actual class but that’s basically the idea behind it. The render()
method gets triggered by the event system of Qt and its role is to ensure that an OpenGL context exists before calling the appropriate methods from the OpenGLRenderer
class.
I’ve implemented the full class by thoroughly following an official example from Qt but it decided not to compile.
That’s not due to the tutorial itself but more with the way of how Qt deals with OpenGL.
The C++ source file implementing the OpenGLSurface
’s Qt class includes the <core/openglrenderer.h>
header, which implicity also includes the GLXW header—that’s when Qt starts screaming.
Without going into the details, the QtOpenGL
module (from Qt 5.2) doesn’t like mixing with external OpenGL loading libraries such as GLXW, GLEW and others.
I came to realize anyways that exposing the <GLXW/glxw.h>
file into a public header from the OpenGL core I was writing meant that any project linking to that library also had an extra compile-time dependency towards GLXW to take care of. That’s not only annoying but it also isn’t required—that dependency should be kept internal to the core library.
Pimpling It Up
The idea was to break this extra dependency by making any reference to GLXW and OpenGL invisible to the public. A true abstraction.
The easy solutions is to replace any reference to OpenGL typdefs such as GLuint
for their actual standard types. But even though it would be fine in most cases, it could potentially break the portability of OpenGL and is largely not recommended by the Internet.
That’s where the pimpl idiom—also called opaque or d pointer, and widely used in Qt—comes to the rescue.
The public interface of the OpenGLRenderer
was already good to go as it was, but the GLuint _program
private member was at fault—as well as the others to come.
The pimpl idiom is all about replacing the current private members of a class by pointing to a new class where the members have been moved to.
// core/openglrenderer.h
//#include "openglrenderer.h" < Not required anymore!
class OpenGLRenderer
{
public:
OpenGLRenderer();
void setup();
void draw();
void cleanup();
private:
class Impl;
Impl *_d;
};
// core/openglrenderer_impl.h
#include <GLXW/glxw.h>
#include "openglrenderer.h"
class OpenGLRenderer::Impl
{
public:
bool setup;
GLuint_program;
};
On top of removing dependencies, the pimpl idiom also provides the convenience of having a cleaner public API, faster compilation times, and helps preserving binary compatibility.
A more advanced definition and usage is available in 2 parts on Sutter’s Mill website: Compilation Firewalls, part 2.
GLSL Shaders
Happy with the implementation of my OpenGL core with Qt 5 and even GLFW, I coud move onto going through the great OpenGL SuperBible to learn some basics.
After writing a couple of GLSL shaders the way they show it in the first chapters, I already couldn’t take it anymore.
How could I possibly go through the entire book by wrapping each line of the shaders within double quotes while making sure they all end with a newline \n
character?
static const GLchar *vertexShaderSource[] = {
"#version 430 core\n"
"void main(void)\n"
"{\n"
" const vec4 vertices[3] = vec4[3](\n"
" vec4( 0.25, -0.25, 0.5, 1.0),\n"
" vec4(-0.25, -0.25, 0.5, 1.0),\n"
" vec4( 0.25, 0.25, 0.5, 1.0)\n"
" );\n"
" \n"
" gl_Position = vertices[gl_VertexID];\n"
"}\n"
};
Frankly I couldn’t imagine the pros writing their shaders like this either. I had to find a solution, one involving having each of my GLSL shaders in a separate file and loading them somehow in OpenGL.
The Dynamic Approach
The only OpenGL function available to set the source code of a shader is glShaderSource()
and it expects the source to be given as a const GLchar **
.
The obvious first move was to open a shader file and convert its content into a character buffer at runtime.
It’s an easy task and the web crawls with examples to do just that.
void setShaderSourceFromFile(GLuint shader, const GLchar *filename)
{
std::ifstream file(filename);
if (!file.is_open()) {
std::cerr << "Unable to open file " << filename << std::endl;
return;
}
std::stringstream stream;
stream << file.rdbuf();
file.close();
std::string string = stream.str();
const char *code = string.c_str();
glShaderSource(shader, 1, (GLchar **)&code, 0);
}
One thing to keep in mind is that if a relative filename is passed to the std::ifstream()
constructor—which will most likely be the case—and that the application is started from a shell, then this can lead to problems.
Paths are defined relatively to the shell’s current directory rather than to the location of the binary, so it’s important that the shaders can be found from where the application is being started.
That’s probably one of the reasons of why there’s so many launchers out there that initialize a working environment before firing the actual application—finding resources at runtime.
The Static Approach
How about using an #include
statement to make the shader available into the C++ code at compile-time?
The idea here is to automagically convert the content of each shader file into a multiline string literal, like the ones showcased in the OpenGL SuperBible.
That’s a parsing job simple enough for CMake and it can be wrapped in a custom target.
add_custom_target(
shaders
COMMAND ${CMAKE_COMMAND}
-DSOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}
-DDESTINATION_DIR=/path/to/formatted/shaders
-DFILES="${shaders_files}"
-P /path/to/shaders.cmake
COMMENT "Creating the files to include the GLSL shaders"
)
The command defined in shaders.cmake
is where the main logic happens:
separate_arguments(FILES)
foreach (file ${FILES})
set(filename ${SOURCE_DIR}/${file})
get_filename_component(name ${filename} NAME_WE)
set(source "")
file(STRINGS ${filename} lines)
foreach (line ${lines})
set(source "${source} \"${line}\\n\"\n")
endforeach()
file(
WRITE ${DESTINATION_DIR}/${file}
"static const GLchar *${name}ShaderSource[] = {\n"
"${source}"
"};\n"
)
endforeach()
For each shader file found, CMake will create a new C++ file in the destination directory containing a single GLchar *
variable that can be directly passed to the glShaderSource()
function.
Bootstrapping It All
At this stage everything was ready for me to get started on OpenGL for good—I could write my OpenGL code and GLSL shaders independently from anything and then preview the result within the GUI toolkit of my choice.
I’ve decided to save a checkpoint of this progression by wrapping everything in a bootstrap that I could reuse later on as a starting point for any project. It can be found on GitHub.
Since I couldn’t decide which method to use for loading the GLSL shaders, and that it after all depends on the needs of each, I’ve added in the bootstrap an option for the user to switch between the dynamic and the static approach.
Support for both GLFW and Qt 5 (with or without the QtWidgets module) is also provided out of the box.
I’ve tested it only on Mac OS X so far but porting it to either Linux or Windows shouldn’t be a problem, if it’s not working already.
Time to get those pixels moving!