Repository navigation
Chapter 1 ‐ Tile Rendering
We are going to implement a very simple client which is capable of displaying tiled graphics.
Let's talk a little bit about the good old GameBoy did graphics. It hat a 160x144 pixel display capable of displaying 4 different "colors" (or shades of gray/green). It also had a whopping 8KiB of RAM. When you look at images you normally have a 24-bit RGB image on modern systems. This means you need 3 bytes per pixel, which would result in 160 x 144 x 3 = 69120 bytes of image data for the gameboy. As you can see this will not fit into the RAM and the CPU could not move that many data in 60Hz. So how did the GameBoy graphics?
The idea almost every video game console used back then was tiling. Just take a look at this Zelda image again.

As you can see the screen is perfectly divided in to 8x8 pixel images called tiles. There are 20 x 18 of those tiles visible on the screen. When you use one byte per tile you can use 256 different tiles and the whole video image can be represented in 360 bytes only! Now we have saved a lot of memory. The downside of this approach is actually that everything has to be aligned in this 20x18 grid. But with some clever drawing you can mask this so it will look nice.
For simplicity we also stick to 8x8 pixel tiles but reduce the screen space a little bit to 128x128 pixels in total. This gives us 16x16 tiles we can display on the screen. As the tileset will be loaded from a BMP file it can have more than the 4 colors on the GameBoy, welcome to the magic of modern computers. But still we can only display 256 different tiles on the screen.
- 8x8 pixel tiles
- 16x16 tiles on the screen
- 128x128 total pixel resolution
- 256 different tiles
Here is the tileset we are going to use for the first iteration. It's simply a rip from the IBM CGA video card which actually used 8x8 pixel "images" as the glyphs. You can see there are 256 different characters and they are layout in the ASCII order. So we can easily write simple functions to print text on the screen.

As mentioned we can only modify the screen within the 16x16 grid using 8x8 pixel images. So we can simply define the screen content like this:
uint8_t screen[VIDEO_ROWS][VIDEO_COLS]; // tilemapThree very simple functions are used to modify the screen content:
// clear the screen
static void clear_screen(void) {
SDL_zero(state.video.screen);
}
// draw tile
static void draw_tile(const int x, const int y, const uint8_t tile) {
if ((x >= 0) && (x < VIDEO_COLS) && (y >= 0) && (y < VIDEO_ROWS))
state.video.screen[y][x] = tile;
}
// draw text
static void draw_text(const int x, const int y, const char *text) {
for (int xx = x; *text; ++text, ++xx)
draw_tile(xx, y, (uint8_t)*text);
}And this set of functions is used to render the current screen content to our window:
// render single tile to screen
static void render_tile(const int x, const int y, const uint8_t tile) {
const SDL_Rect src = { .x = (tile % 16) * TILE_SIZE, .y = (tile / 16) * TILE_SIZE, .w = TILE_SIZE, .h = TILE_SIZE };
const SDL_Rect dst = { .x = x * TILE_SIZE, .y = y * TILE_SIZE, .w = TILE_SIZE, .h = TILE_SIZE };
SDL_RenderCopy(state.video.renderer, state.video.texture, &src, &dst);
}
// render the video
static void render_video(void) {
SDL_RenderClear(state.video.renderer);
for (int y = 0; y < VIDEO_ROWS; ++y) {
for (int x = 0; x < VIDEO_COLS; ++x) {
render_tile(x, y, state.video.screen[y][x]);
}
}
SDL_RenderPresent(state.video.renderer);
}The rest of the code is more or less boilerplate to load the assets and initialize the screen. We define also a main loop which updates the screen in a 20Hz manner. The update function actually "draws" something to the screen:
// run single client tick
static void run_tick(void) {
state.tick++;
clear_screen();
draw_text(0, 0, "Hello World!");
draw_text(0, 1, format_string("tick: %d", state.tick));
}See the whole source code for the current state of the implementation.
The final output should look something like this:
