easy-gl › Hello Triangle
Hello Triangle
A complete, annotated, minimal example using SDL3 + easy-gl. Renders a colour-animated triangle using a vertex buffer, VAO, and a shader program.
Vertex shader
const char* vertSrc = R"(#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
out vec3 vertexColor;
void main() {
gl_Position = vec4(aPos, 1.0);
vertexColor = aColor;
})";
Fragment shader
const char* fragSrc = R"(#version 330 core
in vec3 vertexColor;
uniform vec4 uTint;
out vec4 FragColor;
void main() {
FragColor = vec4(vertexColor, 1.0) * uTint;
})";
Vertex data
constexpr float vertices[] = {
// position color
-0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f,
};
Full main()
int main(int, char**)
{
// ---- SDL3 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_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_Window* window = SDL_CreateWindow("Hello Triangle", 800, 600, SDL_WINDOW_OPENGL);
SDL_GLContext glCtx = SDL_GL_CreateContext(window);
SDL_GL_MakeCurrent(window, glCtx);
SDL_GL_SetSwapInterval(1);
// ---- easy-gl resources in a scope so they are destroyed before the context ----
{
easygl::Device device;
device.initialize(
reinterpret_cast<easygl::GLGetProcAddressFn>(SDL_GL_GetProcAddress));
// Print GPU info
const auto& info = device.capabilities().context_info();
std::cout << info.vendor << " / " << info.renderer << '\n';
// Compile and link program in one call
easygl::Program prog(vertSrc, fragSrc);
const int tintLoc = prog.uniform_location("uTint");
// VAO + VBO
easygl::VertexArray vao;
easygl::Buffer vbo;
vao.create();
vbo.create();
vao.bind();
vbo.bind(easygl::BufferTarget::Array);
vbo.set_data(easygl::BufferTarget::Array, vertices, sizeof(vertices));
constexpr std::size_t stride = 6 * sizeof(float);
vao.set_attribute(easygl::VertexAttribute{0, 3, easygl::DataType::Float, false, stride, 0, true});
vao.set_attribute(easygl::VertexAttribute{1, 3, easygl::DataType::Float, false, stride, 3 * sizeof(float), true});
// Set initial viewport
int w = 0, h = 0;
SDL_GetWindowSizeInPixels(window, &w, &h);
device.set_viewport(0, 0, w, h);
// ---- Render loop ----
bool running = true;
while (running) {
SDL_Event ev;
while (SDL_PollEvent(&ev)) {
if (ev.type == SDL_EVENT_QUIT) running = false;
if (ev.type == SDL_EVENT_KEY_DOWN && ev.key.key == SDLK_ESCAPE) running = false;
if (ev.type == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED)
device.set_viewport(0, 0, ev.window.data1, ev.window.data2);
}
device.set_clear_color(0.2f, 0.3f, 0.3f, 1.0f);
device.clear(easygl::ClearFlags::Color);
// Animate tint with time
const float t = static_cast<float>(SDL_GetTicks()) / 1000.0f;
const float green = std::sin(t) * 0.5f + 0.5f;
prog.use();
prog.set_uniform(tintLoc, 0.7f, green, 1.0f, 1.0f);
vao.bind();
device.draw_arrays(easygl::PrimitiveType::Triangles, 0, 3);
SDL_GL_SwapWindow(window);
}
} // vao, vbo, prog, device — all destroyed here
SDL_GL_DestroyContext(glCtx);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
Key points
- All easy-gl objects live in a scope that ends before
SDL_GL_DestroyContext. Program(vertSrc, fragSrc)compiles and links in one step — no separate Shader objects needed.VertexAttributestruct aggregates index, component count, type, stride and offset in one readable call.- The tint uniform is updated every frame via
set_uniform(loc, r, g, b, a). ClearFlags::Coloris a bitmask — use|to combine withClearFlags::Depth.