Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Tiles & Maps

Tile modes (0-2) are the backbone of GBA graphics. The display hardware composites 8x8 pixel tiles from VRAM, using a tilemap to arrange them into backgrounds. This is extremely memory-efficient and the scrolling is handled entirely by hardware.

How it works

  1. Tile data (the pixel art) is stored in VRAM “character base blocks”
  2. Tilemap (which tile goes where) is stored in VRAM “screen base blocks”
  3. Palette maps pixel indices to colours
  4. The hardware reads the map, looks up each tile, applies the palette, and draws the scanline

Loading tile data

Tile graphics are usually pre-converted at build time and copied into VRAM. Each 8x8 tile in 4bpp mode is 32 bytes (4 bits per pixel, 64 pixels):

#include <gba/peripherals>
#include <gba/dma>
#include <gba/video>

// Assuming tile_data is a const array in ROM
extern const unsigned short tile_data[];
extern const unsigned int tile_data_size;

// Copy tile data to character base block 0 (0x06000000)
gba::reg_dma[3] = gba::dma::copy(
    tile_data,
    gba::memory_map(gba::mem_vram_bg),
    tile_data_size / 4
);

Setting up a background

// Configure BG0: 256x256, 4bpp tiles
// Character base = 0 (tile data at 0x06000000)
// Screen base = 31 (map at 0x0600F800)
gba::reg_bgcnt[0] = {
    .charblock = 0,
    .screenblock = 31,
    .size = 0,  // 256x256 (32x32 tiles)
};

// Scroll BG0
gba::reg_bgofs[0][0] = 0;
gba::reg_bgofs[0][1] = 0;

Background sizes

Size valueDimensions (pixels)Dimensions (tiles)
0256x25632x32
1512x25664x32
2256x51232x64
3512x51264x64

Scrolling

Scrolling is a single register write per axis:

gba::reg_bgofs[0][0] = scroll_x; // BG0 horizontal offset
gba::reg_bgofs[0][1] = scroll_y; // BG0 vertical offset

The hardware wraps seamlessly at the background boundaries. A 256x256 background scrolled past x=255 wraps back to x=0 - perfect for side-scrolling games.

Here is a scrollable checkerboard built from two solid tiles:

#include <gba/interrupt>
#include <gba/video>

int main() {
    gba::irq_handler = {};
    gba::reg_dispstat = {.enable_irq_vblank = true};
    gba::reg_ie = {.vblank = true};
    gba::reg_ime = true;

    gba::reg_dispcnt = {.video_mode = 0, .enable_bg0 = true};
    gba::reg_bgcnt[0] = {.screenblock = 31};

    // Palette
    gba::pal_bg_mem[0] = {.red = 2, .green = 2, .blue = 6};
    gba::pal_bg_bank[0][1] = {.red = 10, .green = 14, .blue = 20};
    gba::pal_bg_bank[0][2] = {.red = 4, .green = 6, .blue = 12};

    // Tile 1: solid light (palette index 1)
    gba::mem_tile_4bpp[0][1] = {
        0x11111111, 0x11111111, 0x11111111, 0x11111111, 0x11111111, 0x11111111, 0x11111111, 0x11111111,
    };

    // Tile 2: solid dark (palette index 2)
    gba::mem_tile_4bpp[0][2] = {
        0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222, 0x22222222,
    };

    // Fill the 32x32 tilemap with a checkerboard
    for (int ty = 0; ty < 32; ++ty)
        for (int tx = 0; tx < 32; ++tx)
            gba::mem_se[31][tx + ty * 32] = {
                .tile_index = static_cast<unsigned short>(((tx ^ ty) & 1) ? 2 : 1),
            };

    int scroll_x = 0, scroll_y = 0;

    while (true) {
        gba::VBlankIntrWait();

        ++scroll_x;
        ++scroll_y;

        gba::reg_bgofs[0][0] = static_cast<short>(scroll_x);
        gba::reg_bgofs[0][1] = static_cast<short>(scroll_y);
    }
}

Tile checkerboard