OpenGL with SDL3

Introduction

I’m not sure why people consider OpenGL to be an obsolete technology, even though it is still used in newer and recent software, including embedded systems and new game titles.

OpenGL is a simpler API compared to Vulkan, Metal and DX12, and it can be used as introduction to graphics programming. That’s the reason I’m writing this post, since there is almost no available resources that show how to integrate both OpenGL and SDL3 in the same application. Yes, there is an example on ImGUI, but it is not a barebone explanation and has a lot noise that can be a little bit distracting.

Project source code will be available here: github.com/cedmundo/hello-opengl

Requeriments

We will be using the following libraries and tools to build our application:

  • Any C compiler compatible with C11 standard
  • Any code editor
  • CMake 3.22.1
  • OpenGL Core 4.1 (runtime, not dev libraries)
  • SDL3 v0.5.0 (build from sources, but binary dists also work)
  • GLAD (Generated from page)
  • Git (optional, but recommended)

And that’s it.

What is GLAD and why it is required

I’m assuming that reader knows what OpenGL and SDL3 are, however, we will be also using GLAD, which is a runtime OpenGL loader, the reason is that we can’t directly link to the OpenGL libraries (well, in practice we can but we should not), this component is responsible for locating the libOpenGL.so and loading its symbols into callable functions.

It’s a very useful piece of software that can be generated here: https://glad.dav1d.de/, and you can configure whatever extensions you may need to your application. If you are not sure what to include, just select:

  • Language: C/C++
  • Specification: OpenGL
  • API/gl: Version 4.1, leave others as None
  • Profile: Compatibility

And in extensions, just hit the button Add all. Then make sure that Generate a loader checkbox is on, leave others unchecked. Then hit Generate.

Project structure

This project follows a simple linear structure, no src and friends, just raw files with a folder or two to help organize the code better:

hello-opengl/
├── CMakeLists.txt                  - Main build script
├── .git                            - Git files
    └── ...
├── .gitignore                      - Standard ignore file
├── main.c                          - Our main file
└── vendor                          - Third party dependencies in source tree
    ├── CMakeLists.txt              - Build dependencies
    └── glad                        - GLAD generated source
        ├── CMakeLists.txt          - GLAD library (not generated)
        ├── include                 - GLAD Headers
        │   ├── glad
        │   │   └── glad.h
        │   └── KHR
        │       └── khrplatform.h
        └── src                     - GLAD Sources
            └── glad.c

CMake

We are going to be a little hand-waving with CMake configuration, lets start with hello-opengl/CMakeLists.txt:

# CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
project(hello-opengl
  VERSION "1.0.0"
  DESCRIPTION "Hello OpenGL with SDL3"
  LANGUAGES C)

Here we can change the description, version and project name. Next we setup some configuration options that will be useful later:

# CMake includes and options
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_C_STANDARD 11)

# Third party dependencies
find_package(SDL3 REQUIRED CONFIG COMPONENTS SDL3)
add_subdirectory(vendor)

Note the find_package directive, here we wil be using what SDL3 documentation says README-cmake. Feel free to add SDL3 however your configuration fits. After that we will include our main target:

add_executable(hello-opengl)
target_sources(hello-opengl PRIVATE main.c)
target_link_libraries(hello-opengl PRIVATE SDL3 glad)

The next CMake file will include all vendored dependencies (which may be copied onto the source directory), althrough we are including just glad for now.

In hello-opengl/vendor/CMakeLists.txt we add:

# vendor/CMakeLists.txt
# Source tree dependencies:
add_subdirectory(glad)

And then, for the glad library, we shall copy the contents of the zipped file within vendor/glad, then add a new CMakeLists in hello-opengl/vendor/glad/CMakeLists.txt:

# vendor/glad/CMakeLists.txt
add_library(glad STATIC)
target_sources(glad PRIVATE src/glad.c)
target_include_directories(glad PUBLIC include)

Main file

Before writing the actual code, let’s start with a simple main.c

// main.c
#include <SDL3/SDL.h>
#include <SDL3/SDL_init.h>
#include <glad/glad.h>

int main() {
  if (!SDL_Init(SDL_INIT_VIDEO)) {
    SDL_Log("Could not initialize SDL: %s", SDL_GetError());
    return -1;
  }

  SDL_Log("Hello OpenGL version %d", GLAD_GL_VERSION_4_1);
  SDL_Quit();
  return 0;
}

To check if we have everything already, we will run:

$ cmake -B cmake-build-debug -S . && cmake --build cmake-build-debug

It should output something like this:

-- The C compiler identification is GNU 11.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/carlos/Projects/Misc/hello-opengl/cmake-build-debug
[ 25%] Building C object vendor/glad/CMakeFiles/glad.dir/src/glad.c.o
[ 50%] Linking C static library libglad.a
[ 50%] Built target glad
[ 75%] Building C object CMakeFiles/hello-opengl.dir/main.c.o
[100%] Linking C executable hello-opengl
[100%] Built target hello-opengl

If not, check the documentation of SDL3 or the project files in github.com/cedmundo/hello-opengl, one common issue that I have, specially in windows, is that M library is not automatically included, you can add it in CMakeLists.txt:

# ...
target_link_libraries(hello-opengl PRIVATE SDL3 glad m) # HERE: add "m"
# ...

Also, if you have issues linking against SDL3, try with SDL3-shared instead:

# ...
target_link_libraries(hello-opengl PRIVATE SDL3-shared glad m)
# ...

After everything is compiling we should be able to run the program:

$ ./cmake-build-debug/hello-opengl
Hello OpenGL version 0

Sometimes in windows the application won’t start and quit rightaway with a weird status code. If this is happening, try copying SDL DLLs intocmake-build-debug directory, you can change CMake to include a custom rule to copy the files automatically, but for now let’s keep it simple.

Hello OpenGL

Onece we got the base example running, we can write the code needed to execute a simple GL application. First let’s start with the headers:

#include <SDL3/SDL.h>
#include <SDL3/SDL_init.h>
#include <SDL3/SDL_video.h>
#include <glad/glad.h>

We can now set the attributes for OpenGL context here:

  // Initialize application
  if (!SDL_Init(SDL_INIT_VIDEO)) {
    SDL_Log("Error: Could not initialize SDL: %s", SDL_GetError());
    return -1;
  }

  // Set attributes
  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
  SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
                      SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);

Feel free to set the depth, stencil size, or whatever other attribute you need here. After that, we can create the window:

  // Create window
  SDL_Window *window = SDL_CreateWindow("Hello OpenGL", 200, 200, SDL_WINDOW_OPENGL);
  if (!window) {
    SDL_Log("Error: Failed to create window: %s", SDL_GetError());
    return -1;
  }

Note that SDL_WINDOW_OPENGL flag is needed, you can change title and size as needed. Now, let’s create OpenGL context:

  SDL_GLContext gl_context = SDL_GL_CreateContext(window);
  if (gl_context == NULL) {
    SDL_Log("Error: Failed to create GL context: %s", SDL_GetError());
    return 1;
  }

Once we got a window and a context, we must link the functions from GLAD into OpenGL runtime, to do that just call gladLoadGLLoader and pass the SDL_GL_GetProcAddress in similar way we do glfwGetProcAddress in GLFW.

  if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) {
    SDL_Log("Error: Failed to initialize GLAD");
    return 1;
  }

  SDL_GL_MakeCurrent(window, gl_context);
  SDL_GL_SetSwapInterval(1);

The last two lines help to claim the context to the window and enable vsync.

Now, we are ready to write actual OpenGL code:

  bool running = true;
  while (running) {
    SDL_Event event;

    // Handle events
    while (SDL_PollEvent(&event)) {
      if (event.type == SDL_EVENT_QUIT ||
          (event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED &&
           event.window.windowID == SDL_GetWindowID(window))) {
        running = false;
      }
    }

    // Render application
    {
      int w = 0, h = 0;
      SDL_GetWindowSize(window, &w, &h);
      glViewport(0, 0, w, h);

      glClearColor(0.2, 0.2, 0.5, 1.0);
      glClear(GL_COLOR_BUFFER_BIT);
    }
    SDL_GL_SwapWindow(window);
  }

And don’t forget to clear everything afterwards:

  SDL_GL_DestroyContext(gl_context);
  SDL_DestroyWindow(window);
  SDL_Quit();

That should be it! Let’s try compile and run it:

$ cmake -B cmake-build-debug -S . && cmake --build cmake-build-debug && ./cmake-build-debug/hello-opengl
-- Configuring done
-- Generating done
-- Build files have been written to: /home/carlos/Projects/Misc/hello-opengl/cmake-build-debug
Consolidate compiler generated dependencies of target glad
[ 50%] Built target glad
Consolidate compiler generated dependencies of target hello-opengl
[ 75%] Building C object CMakeFiles/hello-opengl.dir/main.c.o
[100%] Linking C executable hello-opengl
[100%] Built target hello-opengl

We should be looking a window with a nice blue as background:

Window with blue background

Now, we can build our GL application as normal. If you want to learn more about GL, try Learn OpenGL it is a wonderful resource!


2026-06-23