Creating an OpenGL 4.5 context using SDL2 and Glad

In this post I’ll describe how to set up an OpenGL 4.5 context. In my previous post, I decided that I’m going to use GLM, SDL2 and Glad for that. Glad is a loader generator, so head over there and choose API: gl version 4.5, Profile: core, Extensions: GL_ARB_bindless_texture, GL_ARB_sparse_buffer, GL_ARB_sparse_texture, Options: uncheck ‘generate a loader’. Click generate; download the zip-file and unpack glad.h and glad.c into a folder named glad in your source directory. The package also contains a file khrplatform.h, which you don’t need if you remove the line #include <KHR/khrplatform.h> from glad.h. Furthermore, because you put glad.c and glad.h in the same folder, you should change glad.c to replace #include <glad/glad.h> with #include "glad.h".

Yes, I said I wanted to avoid extensions, however, those three are part of AZDO (approaching zero driver overhead) OpenGL, so I want to be able to explain their use as well. I also plan on using the sparse buffers in my voxel-engine.

main.cpp

In this section we will create the c++ file main.cpp, which contains the main() entry point. First we include the headers:

#include <cstdio>
#include <cstdlib>

#define GLM_FORCE_RADIANS 1
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <SDL2/SDL.h>

#include "glad/glad.h"

We need cstdio and cstdlib for fprintf() and exit(). Legacy OpenGL used to accept angles in degrees. Therefore the developers of GLM decided that they would also accept angles in degrees by default. However, at some point this functionality has been deprecated in favour of GLM accepting angles in radians. The #define GLM_FORCE_RADIANS 1 is a left-over from that, otherwise GLM will print a deprecation warning during compilation. Inclusion of the SDL header should be obvious. Lastly, glad is included using quotes, rather than angle brackets, as it is part of our source tree.

Note that if you’re using kdevelop 4, the auto-completion doesn’t work and if you have the OpenGL man pages installed, those don’t work in kdevelop either. A workaround is to replace #include "glad/glad.h" in main.cpp by:

#ifdef IN_IDE_PARSER
# define GL_GLEXT_PROTOTYPES
# include "GL/gl.h"
#else
# include "glad/glad.h"
#endif

Now we will define some global variables and methods. (Remember, this is a tutorial about using AZDO OpenGL, not about proper software design. Though I think static (file-scoped) methods are kind of neat.)

static const int SCREEN_FULLSCREEN = 1;
static const int SCREEN_WIDTH  = 960;
static const int SCREEN_HEIGHT = 540;
static SDL_Window *window = NULL;
static SDL_GLContext maincontext;

static void sdl_die(const char * message) {
  fprintf(stderr, "%s: %s\n", message, SDL_GetError());
  exit(2);
}

void init_screen(const char * caption) {
  // To be filled in the next section
}

The type SDL_GLContext is a typedef’ed void pointer (void pointers are ugly, it would’ve been cleaner to typedef a forward declared struct). We use sdl_die to deal with any errors from SDL. The remainder should be self-explanatory.

Now we can define our program’s entry point, which will include the SDL event loop.

int main() {
  init_screen("OpenGL 4.5");
  SDL_Event event;
  bool quit = false;
  while (!quit) {
    SDL_GL_SwapWindow(window);
    while (SDL_PollEvent(&event)) {
      if (event.type == SDL_QUIT) {
        quit = true;
      }
    }
  }
}

It calls init_screen to initialize our window and OpenGL context. The call to SDL_GL_SwapWindow updates the window with the contents rendered by OpenGL. We will be using v-sync, so rendering is limited to 50 or 60 FPS (and thus calls to SDL_GL_SwapWindow are also limited to 50/60 per second). The event loop keeps running and ‘rendering’ until the window is closed. Closing can often be done using alt+f4.

init_screen()

In this section we write the body of the init_screen() function.

We are using SDL2, so first we need to initialize its video module (note that with sdl_die the error handling is almost a one-liner). Subsequently we load the default OpenGL library through SDL’s SDL_GL_LoadLibrary, which lets SDL handle the OS dependent finding and loading of the OpenGL library.

  // Initialize SDL 
  if (SDL_Init(SDL_INIT_VIDEO) < 0)
    sdl_die("Couldn't initialize SDL");
  atexit (SDL_Quit);
  SDL_GL_LoadLibrary(NULL); // Default OpenGL is fine.

Before creating the window, we tell SDL what kind of context we want, using SDL_GL_SetAttribute. In our case that would be an OpenGL 4.5 context. Eventually I want to be able to mix rasterizing with the output of my voxel engine, so I also request a depth buffer. If you don’t need a depth buffer, you can change the numbers in the last two lines to 0.

  
  // Request an OpenGL 4.5 context (should be core)
  SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 5);
  // Also request a depth buffer
  SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
  SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);

Now that we’ve told SDL what we want, it is time to create the window, which is done using SDL_CreateWindow. For fullscreen we use the current desktop resolution. The flag SDL_WINDOW_OPENGL is used to tell SDL that we want to use the window for an OpenGL context.

  // Create the window
  if (SCREEN_FULLSCREEN) {
    window = SDL_CreateWindow(
      caption, 
      SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 
      0, 0, SDL_WINDOW_FULLSCREEN_DESKTOP | SDL_WINDOW_OPENGL
    );
  } else {
    window = SDL_CreateWindow(
      caption, 
      SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 
      SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_OPENGL
    );
  }
  if (window == NULL) sdl_die("Couldn't set video mode");

We’re not done yet. We still have to call SDL_GL_CreateContext to actually create the context.

  maincontext = SDL_GL_CreateContext(window);
  if (maincontext == NULL) 
    sdl_die("Failed to create OpenGL context");

Great, now we have an OpenGL context, but no functions through which we can access it. That’s where glad comes in (see the top of this post). The SDL_GL_GetProcAddress should be used to obtain the API function pointers for OpenGL, which is nice as it again abstracts away all OS dependent stuff for dealing with OpenGL libraries. Fortunately it also has the correct signature to be able to pass it to the glad loader function gladLoadGLLoader. Calling that function makes all OpenGL functions available, including glGetString, which we use to print information about our context and to see whether everything went right.

  
  // Check OpenGL properties
  printf("OpenGL loaded\n");
  gladLoadGLLoader(SDL_GL_GetProcAddress);
  printf("Vendor:   %s\n", glGetString(GL_VENDOR));
  printf("Renderer: %s\n", glGetString(GL_RENDERER));
  printf("Version:  %s\n", glGetString(GL_VERSION));

As stated earlier in the post, we want to sync rendering to our monitor’s refresh rate. This is done using SDL_GL_SetSwapInterval.

  // Use v-sync
  SDL_GL_SetSwapInterval(1);

We’re not yet using depth testing and face culling, so for now we disable those here (using glDisable).

  
  // Disable depth test and face culling.
  glDisable(GL_DEPTH_TEST);
  glDisable(GL_CULL_FACE);

If we are in fullscreen mode, we don’t know the window’s resolution, so we request the current resolution from SDL using SDL_GetWindowSize. Then we pass this information on to OpenGL, using glViewport. Furthermore we tell OpenGL that a nice blue colour should be used when clearing the screen (glClearColor).

  
  int w,h;
  SDL_GetWindowSize(window, &w, &h);
  glViewport(0, 0, w, h);
  glClearColor(0.0f, 0.5f, 1.0f, 0.0f);

Now we’re done filling the body of the init_screen() function. Note that we did not do any error handling for OpenGL. The way to do error handling in OpenGL has changed a lot in 4.3, so I will focus on that in my next post.

CMake

In this section we will build the program using CMake, a rather common and relatively cross-platform build tool. If you don’t want to use CMake, you can skip this section.

First download the files FindGLM.cmake and FindSDL2.cmake and put them in a subdirectory called cmake in your source directory.

Create a file called CMakeLists.txt in your source directory with the following contents (if you don’t use gcc, skip the line containing -std=gnu++11):

cmake_minimum_required (VERSION 2.6)
project(my-opengl4-program)

set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11 -Wall -Wextra -march=native")

find_package(GLM REQUIRED)
find_package(SDL2 REQUIRED)

add_executable(my-opengl4-program
  glad/glad.c
  main.cpp
)

target_include_directories(my-opengl4-program PUBLIC
  ${GLM_INCLUDE_DIRS} ${SDL2_INCLUDE_DIRS}
)

target_link_libraries(my-opengl4-program
  ${SDL2_LIBRARIES}
)

Note that GLM is a header-only library, and thus does not need to link any library files.

If you’re running Linux, you should now be able to compile and run the program using:

mkdir build
cd build
cmake ..
make
./my-opengl4-program

Zip file

If you have followed this entire tutorial, then your source folder should have the same contents as this zip-file. If not, you can also download that file, so you don’t need to copy paste all the code snippets (yes, it also contains the modified glad header and source files).

Advertisements

6 thoughts on “Creating an OpenGL 4.5 context using SDL2 and Glad

  1. Pingback: Debugging since OpenGL 4.3 | Voxel-Engine

  2. To make this work in MSVC 2010, I had to modify the line #include <SDL2/SDL.h> to be:

    #ifdef _MSC_VER
    #include <SDL.h>
    #ifdef main
    #undef main //remove SDL's main() hook if it exists
    #endif
    #else
    #include <SDL2/SDL.h>
    #endif
    

    Also, swapinterval has no effect (other people seem to have this problem too), so I put a timed delay in the main loop to get 60fps like so;

    uint32_t ticks = SDL_GetTicks(), lastticks =0;
    while (!quit) {
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      ticks = SDL_GetTicks();
      if ( ((ticks*10-lastticks*10)) < 167 )  
        SDL_Delay( (167-((ticks*10-lastticks*10)))/10 );
      lastticks = SDL_GetTicks();
      SDL_GL_SwapWindow(window);
      // ...
    
  3. Pingback: Copy a texture to screen | Voxel-Engine

  4. Helpful article. A few remarks though: since you are using a C++11 compiler, it is recommended to use ‘nullptr’ instead of ‘NULL’ when initializing/checking a pointer. A glClearColor is called but no glClear is happening; Add glClear(GL_COLOR_BUFFER_BIT); to the render loop.

    • Thanks. I don’t use any C++11 specific features, so it makes sense to remove that compiler flag. It would even be plain-c if I’d change the #includes, as I haven’t used glm yet. When I update the tutorials I’ll add a glClear call.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s