Levent Kaya

Home | About | Contacts | Blog Archive

Why and how did I make a zero-dependency graphics library from scratch

Current Github stargazers of fbgl: GitHub Repo stars

Current Github contributors of fbgl: GitHub contributors

fbgl texture

Hello everyone, today I will tell you why and how I programmed my graphics library fbgl for Linux from scratch and with no dependencies other than Linux headers.

How Did It Start?

I can’t play games as much as I used to, but as a player and a programmer, there is one game series that I really love: Doom…

I think I was 11 or 12 when I first played Doom, which is the same period when I started writing C, the first Doom game I played was Doom 3 if I remember correctly, but the one that amazed me the most in the whole series was the first Doom from 1993.

Of course, I was not aware of how great this engineering work was when I was still in middle school, all I wanted was to shoot, as time went by I started reading and learning about id software and John Carmack, the source codes of Doom became public and I had the chance to examine it. I think that no C lover can remain indifferent to this game. Like you, I was a fan. At that time, computers that could only render 2D textures could only make this 3D illusion come true with the hands of a really, really talented programmer; I mean we are talking about John Carmack here…

Time passed, I learned some linear algebra at school and eventually I wanted to write my own ray casting engine, so I could make a game that was a little like Doom and sit back and say, wow I’m a decent programmer at least.

The Pursuit

If you want to display certain graphics on the screen in certain ways, there is a very basic problem you need to solve, how do I draw on the screen?

Yes, I know it seems very simple, that’s what I started thinking, and I started a search to find out how to draw graphics on the screen.

As a result, I have encountered dozens of libraries, I have seen libraries such as sdl, sfml, raylib and even more advanced libraries with 3d rendering capabilities such as opengl and vulkan being used in similar projects. And I don’t know how to describe my discomfort with this situation. All I wanted to do was to render 2D textures to the screen and make them look like 3D with a little math, and for this I had to add tens of thousands of lines of library code as dependency. My pursuit ended in disappointment.

A Revolt Against Bloatware

Those of you who know me know how much I love to build and reinvent things from scratch. But this time it wasn’t about my curiosity, it really has been annoying me for a long time that people rely on other codes for such basic things and make things so complicated. For example, I just want to put an input bar on my website, why do I need a node module folder that takes up 10GB of space for it? Doesn’t make sense, I think so. Finally, I decided to make my own simple graphics library. I was working on my Linux machine, and this library would run as a Linux user level program, so I would write my own graphics library without using any additional libraries other than Linux headers. After all, John Carmack didn’t have Vulkan, did he?

The Technical Challenge

When I decided to create fbgl (Framebuffer Graphics Library), I knew I was setting myself up for a significant challenge. The Linux framebuffer is a direct, low-level method of drawing graphics, essentially a raw memory buffer that maps directly to the display. Unlike high-level graphics libraries, working with the framebuffer means you’re dealing with pixels at their most fundamental level. The core philosophy behind fbgl was simplicity and control. I wanted a library that would:

Understanding the Framebuffer

Before diving into the implementation, let me explain what the framebuffer actually is. In Linux, the framebuffer (/dev/fb0) is a device that represents your display as a memory-mapped region. When you write to this memory, you’re directly manipulating what appears on the screen. It’s like having a gigantic array of pixels that you can modify in real-time.

The initialization process in fbgl is deliberately straightforward. Here’s a peek into the core initialization function:

int fbgl_init(const char *device, fbgl_t *fb)
{
    fb->fd = device == NULL ? open(DEFAULT_FB, O_RDWR) :
                              open(device, O_RDWR);
    
    // Retrieve screen information
    ioctl(fb->fd, FBIOGET_FSCREENINFO, &fb->finfo);
    ioctl(fb->fd, FBIOGET_VSCREENINFO, &fb->vinfo);

    // Memory map the framebuffer
    fb->pixels = (uint32_t *)mmap(NULL, fb->screen_size,
                                  PROT_READ | PROT_WRITE, 
                                  MAP_SHARED, fb->fd, 0);
    
    return 0;
}

Key Design Decisions

Minimal API Surface

I designed fbgl with a minimal, intuitive API. Want to draw a line? fbgl_draw_line(). Want to draw a circle? fbgl_draw_circle_filled(). No complex abstractions, no unnecessary complexity.

Texture Loading

One of the most exciting features is the TGA texture loading. I chose TGA because it’s a simple, straightforward format. The loading function handles 24 and 32-bit textures, with support for bottom-up and top-down image orientations.

fbgl_tga_texture_t *fbgl_load_tga_texture(const char *path)
{
    // Detailed texture loading implementation
    // Supports 24/32 bit textures
    // Handles image orientation
    // Converts color formats
}

Keyboard Input

I didn’t want to rely on complex input libraries. So I implemented a simple, non-blocking keyboard input system using terminal raw mode:

fbgl_key_t fbgl_get_key(void)
{
    char c;
    ssize_t bytes_read = read(STDIN_FILENO, &c, 1);

    // Handle special keys like arrow keys
    // Translate raw input to meaningful key events
}

Text Rendering

fbgl can load and render PSF1 bitmap fonts, allowing you to display text on the screen.

fbgl_render_psf1_text(fb, font, "Hello, World!", 10, 10, FBGL_RGB(255, 255, 255));

bin

Challenges and Solutions

Building fbgl wasn’t without its challenges. Here’s how I tackled some of the toughest problems:

Handling Hardware Variability

The framebuffer’s configuration varies between devices, making it tricky to write portable code. I solved this by:

Debugging Low-Level Code

Direct memory access is unforgiving—one mistake can crash the system. To debug fbgl, I relied on:

Optimizing Performance

Writing directly to the framebuffer can be slow. fbgl uses:

Example Application: A Simple Game

Here’s a basic program that uses fbgl to create a simple game:

#define FBGL_IMPLEMENTATION
#include "fbgl.h"

int main() {
    fbgl_t fb;
    if (fbgl_init(NULL, &fb) != 0) {
        return -1;
    }

    fbgl_clear(FBGL_RGB(0, 0, 0)); // Clear screen to black

    fbgl_point_t ball_pos = {50, 50};
    fbgl_point_t ball_size = {10, 10};
    fbgl_point_t direction = {1, 1};

    while (1) {
        fbgl_clear(FBGL_RGB(0, 0, 0)); // Clear screen

        // Update ball position
        ball_pos.x += direction.x;
        ball_pos.y += direction.y;

        // Bounce off edges
        if (ball_pos.x <= 0 || ball_pos.x + ball_size.x >= fb.width) {
            direction.x = -direction.x;
        }
        if (ball_pos.y <= 0 || ball_pos.y + ball_size.y >= fb.height) {
            direction.y = -direction.y;
        }

        // Draw ball
        fbgl_draw_rectangle_filled(ball_pos, (fbgl_point_t){ball_pos.x + ball_size.x,
								ball_pos.y + ball_size.y}, FBGL_RGB(255, 0, 0), &fb);

        usleep(10000); // Sleep for 10ms
    }

    fbgl_destroy(&fb);
    return 0;
}

The Road Ahead

fbgl is still in its early stages, but I’m excited about its potential. Here are some features I plan to add:

Conclusion

Building fbgl has been an immensely rewarding journey. It started as a personal challenge, born out of frustration with bloated dependencies, but quickly evolved into something much more meaningful. It reminded me why I fell in love with programming in the first place—the joy of understanding, building, and solving problems from the ground up.

This project isn’t just about creating a lightweight graphics library; it’s about rediscovering simplicity and control in an era where complexity often overshadows clarity. fbgl might not be the fastest or the most feature-rich library out there, but it’s mine, and I understand every line of its code.

If you’re like me and find joy in tinkering, learning, and pushing the limits of what you can do with a little math and some basic tools, I encourage you to try something like this. You don’t need the latest frameworks or massive libraries to build something cool. Sometimes, the best way to learn is to strip away the layers of abstraction and dive straight into the fundamentals.

fbgl is far from perfect, and there’s so much more I want to do with it. But for now, I’m proud of what it represents—a small, personal revolt against bloatware and a celebration of what’s possible when you start from scratch.

Tags: [ c  graphics  lowlevel  ]

© Levent Kaya. All Rights Reserved.