easy-gl › Getting Started
Getting Started
Prerequisites
- C++20 compiler (GCC 10+, Clang 12+, MSVC 2019 16.8+)
- CMake 3.21+
- meta-gl library (easy-gl depends on it)
- An OpenGL context provider (SDL3, GLFW, SFML, etc.)
CMake integration
Add easy-gl and meta-gl as subdirectories or use FetchContent:
# CMakeLists.txt
cmake_minimum_required(VERSION 3.21)
project(MyApp)
include(FetchContent)
FetchContent_Declare(easy-gl
GIT_REPOSITORY https://github.com/openeggbert/easy-gl.git
GIT_TAG main)
FetchContent_MakeAvailable(easy-gl)
add_executable(MyApp main.cpp)
target_link_libraries(MyApp PRIVATE easy-gl)
easy-gl's CMake target is easy-gl. It automatically pulls in meta-gl and sets the include paths.
Including the library
A single umbrella header includes everything:
#include <easygl/easygl.hpp>
Or include individual class headers for faster compilation:
#include <easygl/Device.hpp>
#include <easygl/Buffer.hpp>
#include <easygl/Program.hpp>
#include <easygl/VertexArray.hpp>
Step 1 — Create an OpenGL context
easy-gl does not create a window or context itself. Use SDL3, GLFW, or any other library for that. The key requirement is that you have an active OpenGL context before calling device.initialize().
// SDL3 example — create window + context
SDL_Init(SDL_INIT_VIDEO);
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);
SDL_Window* window = SDL_CreateWindow("My App", 800, 600, SDL_WINDOW_OPENGL);
SDL_GLContext glCtx = SDL_GL_CreateContext(window);
SDL_GL_MakeCurrent(window, glCtx);
Step 2 — Initialize the Device
Device is the central object. Initialise it by passing a function pointer loader that resolves OpenGL symbols at runtime:
easygl::Device device;
try {
device.initialize(
reinterpret_cast<easygl::GLGetProcAddressFn>(SDL_GL_GetProcAddress));
} catch (const easygl::Exception& e) {
std::cerr << "easy-gl init failed: " << e.what() << '\n';
return 1;
}
device.capabilities() to query the vendor, renderer, version and supported features.Step 3 — Create GPU resources
All resource classes follow the same pattern: construct, then call create() to allocate the GPU handle.
// A buffer
easygl::Buffer vbo;
vbo.create();
// A vertex array object
easygl::VertexArray vao;
vao.create();
// A compiled+linked program (one-shot constructor)
easygl::Program prog(vertexSrc, fragmentSrc);
device.initialize().Step 4 — Upload data
constexpr float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
vao.bind();
vbo.bind(easygl::BufferTarget::Array);
vbo.set_data(easygl::BufferTarget::Array, vertices, sizeof(vertices));
vao.set_attribute(easygl::VertexAttribute{
.index = 0,
.components = 3,
.type = easygl::DataType::Float,
.normalized = false,
.stride_in_bytes = 3 * sizeof(float),
.offset_in_bytes = 0,
.enabled = true
});
Step 5 — Render loop
while (running)
{
device.set_clear_color(0.1f, 0.1f, 0.1f, 1.0f);
device.clear(easygl::ClearFlags::Color);
prog.use();
vao.bind();
device.draw_arrays(easygl::PrimitiveType::Triangles, 0, 3);
SDL_GL_SwapWindow(window);
}
Step 6 — Cleanup
Resources are RAII. They are destroyed automatically when they go out of scope. The only rule: ensure all easy-gl objects are destroyed before the OpenGL context is destroyed. The simplest way is a scoped block:
// all easy-gl objects inside this scope
{
easygl::Device device;
device.initialize(...);
easygl::Buffer vbo;
vbo.create();
// ... render loop ...
} // vbo and device destroyed here, while context is still alive
SDL_GL_DestroyContext(glCtx); // safe to destroy context now
destroy() calls to avoid this.