Just a heads-up: In order to speed up development on Breezy, I've put these notes pages "on hold" for the time being. Everything that's already here should still be accurate, but new pages and major updates won't be pushed any time soon.

OpenGL Complete Example

Last Update: May 25, 2026

Here's a complete code example, built by combining the snippets from the following pages:

  1. OpenGL > Overview
  2. OpenGL > Buffer Objects
  3. OpenGL > Shader Programs
#include <stdio.h>
#include <stdlib.h>

#include <glad/gles2.h>
#include <GLFW/glfw3.h>

float vertices[] = {
    // Position (xy)  // Color (rgb)
     0.5f,  0.5f,     1.0f, 0.0f, 0.0f,  // 0: top right,    red
     0.5f, -0.5f,     1.0f, 1.0f, 1.0f,  // 1: bottom right, white
    -0.5f, -0.5f,     0.0f, 0.0f, 1.0f,  // 2: bottom left,  blue
    -0.5f,  0.5f,     1.0f, 1.0f, 1.0f,  // 3: top left,     white
};

unsigned int indices[] = {
    0, 1, 3, // Triangle 1 (red)
    1, 2, 3, // Triangle 2 (blue)
};


static char *load_file(const char *filename);
static GLuint compile_shader(GLenum type, const char *code);

int main()
{
    // =============================================================================================
    //  GLFW Setup
    // ---------------------------------------------------------------------------------------------

    // Initialize GLFW
    if (!glfwInit())
    {
        fprintf(stderr, "Failed to initialize GLFW.\n");
        return -1;
    }

    // Create the application window
    GLFWwindow *window = glfwCreateWindow(800, 600, "Hello, breezy!", NULL, NULL);
    if (!window)
    {
        fprintf(stderr, "Failed to create GLFW window.\n");
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSwapInterval(1);


    // =============================================================================================
    //  OpenGL / GLAD Setup
    // ---------------------------------------------------------------------------------------------

    // Load our OpenGL functions
    if (!gladLoadGLES2(glfwGetProcAddress))
    {
        fprintf(stderr, "Failed to initialize GLAD\n");
        glfwDestroyWindow(window);
        glfwTerminate();
        return -1;
    }


    // =============================================================================================
    //  Create our geometry
    // ---------------------------------------------------------------------------------------------

    // Prep the VAO
    GLuint vao;
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    // Prep the VBO and store its data
    GLuint vbo;
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // Prep the EBO and store its data
    GLuint ebo;
    glGenBuffers(1, &ebo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    // Tell the VAO how to read the "position" (x,y) attribute from our buffer objects
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *)0);
    glEnableVertexAttribArray(0);

    // Tell the VAO how to read the "color" (r,g,b) attribute from our buffer objects
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *)(2 * sizeof(float)));
    glEnableVertexAttribArray(1);

    // Deactivate our VAO so we don't accidentally change it. (Optional, but recommended)
    glBindVertexArray(0);


    // =============================================================================================
    //  Create our shader program
    // ---------------------------------------------------------------------------------------------

    // Compile our shaders
    char *vert_contents = load_file("./shaders/shader.vert.glsl");
    char *frag_contents = load_file("./shaders/shader.frag.glsl");
    GLuint vert_shader = compile_shader(GL_VERTEX_SHADER,   vert_contents);
    GLuint frag_shader = compile_shader(GL_FRAGMENT_SHADER, frag_contents);

    // Create / link our program
    GLuint shader_program = glCreateProgram();
    glAttachShader(shader_program, vert_shader);
    glAttachShader(shader_program, frag_shader);
    glLinkProgram(shader_program);

    // Error checking / handling
    GLint status;
    glGetProgramiv(shader_program, GL_LINK_STATUS, &status);
    if (status == GL_FALSE)
    {
        // It failed, so read the latest message from the OpenGL program log and print it out
        char message[512];
        glGetProgramInfoLog(shader_program, 512, NULL, message);
        fprintf(stderr, "Link error: %s\n", message);
    }

    // We no longer need our actual shader objects. They're built in to the program.
    glDeleteShader(vert_shader);
    glDeleteShader(frag_shader);

    free(vert_contents);
    free(frag_contents);


    // =============================================================================================
    //  Render Loop!
    // ---------------------------------------------------------------------------------------------

    while (!glfwWindowShouldClose(window))
    {
        // Clear the last render
        glClear(GL_COLOR_BUFFER_BIT);

        // Load our program and VAO
        glUseProgram(shader_program);
        glBindVertexArray(vao);

        // Draw our triangles from our EBO ("Element" Buffer Object)
        // 2 triangles * 3 points = 6 points total
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

        glfwSwapBuffers(window);
    }


    // =============================================================================================
    //  Clean up
    // ---------------------------------------------------------------------------------------------

    glfwDestroyWindow(window);
    glfwTerminate();

    glDeleteVertexArrays(1, &vao);
    glDeleteBuffers(1, &vbo);
    glDeleteBuffers(1, &ebo);
    glDeleteProgram(shader_program);

    return 0;
}

static char *load_file(const char *filename)
{
    FILE *f = fopen(filename, "r");

    // Get the file length
    fseek(f, 0, SEEK_END);
    const long length = ftell(f);
    rewind(f);

    // Get file contents
    char *contents = malloc(sizeof(char) * (length+1));
    fread(contents, sizeof(char), length, f);
    contents[length] = '\0';
    fclose(f);

    return contents;
}

static GLuint compile_shader(GLenum type, const char *code)
{
    // Create / compile the shader
    GLuint shader = glCreateShader(type);
    glShaderSource(shader, 1, &code, NULL);
    glCompileShader(shader);

    // Error checking / handling
    GLint status;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
    if (status == GL_FALSE)
    {
        // It failed, so read the latest message from the OpenGL shader log and print it out
        char message[512];
        glGetShaderInfoLog(shader, 512, NULL, message);
        fprintf(stderr, "Shader error: %s\n", message);
    }

    // It all worked, return the goods!
    return shader;
}
#version 320 es

layout(location = 0) in highp vec2 a_position;
layout(location = 1) in lowp vec3 a_color;
out lowp vec3 v_color;

void main() {
    gl_Position = vec4(a_position.x, a_position.y, 0.0, 1.0); // x, y, z, and w.
    v_color = a_color;
}
#version 320 es

in lowp vec3 v_color;
out lowp vec3 fragColor;

void main() {
    fragColor = v_color;
}