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;
}
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);
}
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 :
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.
Circles using Bresenham algorithm
Next Steps :
- Double buffering to avoid flicklering
- Geometrical shapes
- Creation of a Z-buffer to handle depth