Simple Graphical Engine

Introduction

The goal is to learn how graphical engines works by creating one from scratch.

Boiler Plate

#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define WIN_WIDTH 1280
#define WIN_HEIGHT 720


int main() {
    Display *display;
    Window window;
    XEvent event;
    int screen;
    
    display = XOpenDisplay(NULL);
    if (display == NULL) {
        fprintf(stderr, "Cannot open display\n");
        exit(1);
    }
    
    screen = DefaultScreen(display);
    window = XCreateSimpleWindow(display, RootWindow(display, screen), 10, 10, WIN_WIDTH, WIN_HEIGHT, 1,
                                 BlackPixel(display, screen), WhitePixel(display, screen));
    
    XSelectInput(display, window, ExposureMask | KeyPressMask);
    XMapWindow(display, window);
    
    GC gc = XCreateGC(display, window, 0, NULL);
    XSetForeground(display, gc, BlackPixel(display, screen));
    
    while (1) {
        XNextEvent(display, &event);
        if (event.type == Expose) {
            XDrawRectangle(display, window, gc, 200, 200, 400, 200);
        }
        if (event.type == KeyPress)
            break;
    }
    
    XCloseDisplay(display);
    return 0;
}

image

Frame Buffer

What is a framebuffer? A framebuffer is simply an array in memory where each element represents a pixel to be displayed on the screen. This involves:

  • Allocating a buffer in RAM (an array of unsigned integers).
  • Manipulating the pixels directly.
  • Copying this buffer to the screen using XImage.

Implementation

The implementation of the framebuffer is rather simple :

  • Declaration of a framebuffer array unsigned int framebuffer[WIN_WIDTH * WIN_HEIGHT] so we can store the individual pixels before displaying them onscreen.
  • Creation of a put_pixel(x, y, color) function so we can directly modify pixels in the framebuffer.
  • Using XImage for displaying the image stored in RAM.

We can then use the put_pixel function to draw a line for example :

for (int i = 0; i < 500; i++) {
    put_pixel(100 + i, 100 + i, 0xFFFFFF);
}

image

Draw Text

In order to draw text onscreen we have to define the font using a 8 bit bitmap for each characters :

const uint8_t font[26][8] = {
    { // A
        0b00111100,
        0b01000010,
        0b10000001,
        0b10000001,
        0b11111111,
        0b10000001,
        0b10000001,
        0b10000001
    },
}

We can now write a function to draw the individual characters using the put_pixel function created earlier :

void draw_char(int x, int y, char c, unsigned int color) {
    int index = c - 'A'; 

    for (int i = 0; i < 8; i++) {
        for (int j = 0; j < 8; j++) {
            if (font[index][i] & (0b10000000 >> j)) {
                put_pixel(x + j, y + i, color);
            }
        }
    }
}
    draw_char(1280/2, 720/2 - 100, 'H', 0xFFFFFF);
    draw_char(1280/2, 720/2 - 90, 'E', 0xFFFFFF);
    draw_char(1280/2, 720/2 - 80, 'L', 0xFFFFFF);
    draw_char(1280/2, 720/2 - 70, 'L', 0xFFFFFF);
    draw_char(1280/2, 720/2 - 60, 'L', 0xFFFFFF);
    draw_char(1280/2, 720/2 - 50, 'O', 0xFFFFFF);
    draw_char(1280/2, 720/2 - 30, 'W', 0xFFFFFF);
    draw_char(1280/2, 720/2 - 20, 'O', 0xFFFFFF);
    draw_char(1280/2, 720/2 - 10, 'R', 0xFFFFFF);
    draw_char(1280/2, 720/2, 'L', 0xFFFFFF);
    draw_char(1280/2, 720/2 + 10, 'D', 0xFFFFFF);

Result :

image

Integrating full ASCII bitmap font

While the font I made was a good start, it limited to the 26 characters of alphabet in uppercase. The goal is to implement a full off the shelf 8 bit bitmap font contained all the usefull character. For that I used dhepper’s font8x8_basic and integrate it in my project.

image

Circles using Bresenham algorithm

image

Next Steps :

  • Double buffering to avoid flicklering
  • Geometrical shapes
  • Creation of a Z-buffer to handle depth