Current Github stargazers of fbgl:
Current Github contributors of fbgl:
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.
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.
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.
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?
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:
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;
}
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.
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
}
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
}
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));
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:
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;
}
fbgl is still in its early stages, but I’m excited about its potential. Here are some features I plan to add:
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.