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

Shapes

stdgba provides a consteval API for generating sprite pixel data from geometric shapes. All pixel data is computed at compile time and stored directly in ROM.

For file-based asset pipelines, see Embedding Images.

Quick start

#include <gba/shapes>
using namespace gba::shapes;

// Define 16x16 sprite geometry
constexpr auto sprite = sprite_16x16(
    circle(8.0, 8.0, 4.0),   // palette index 1
    rect(2, 2, 12, 12)        // palette index 2
);

// Load colours into palette memory
gba::pal_obj_bank[0][1] = { .red = 31 };    // red circle
gba::pal_obj_bank[0][2] = { .green = 31 };  // green rectangle

// Copy pixel data to VRAM
auto* dest = gba::memory_map(gba::mem_vram_obj);
std::memcpy(dest, sprite.data(), sprite.size());

// Set OAM attributes
gba::obj_mem[0] = sprite.obj(gba::tile_index(dest));

How it works

Each sprite_WxH() call takes a list of shape groups. Each group is assigned a sequential palette index starting from 1 (palette index 0 is transparent). The shapes within each group are rasterized into 4bpp pixel data.

Available sprite sizes

SizeFunctionBytes
8x8sprite_8x8()32
16x16sprite_16x16()128
16x32sprite_16x32()256
32x16sprite_32x16()256
32x32sprite_32x32()512
32x64sprite_32x64()1024
64x32sprite_64x32()1024
64x64sprite_64x64()2048

Shape types

ShapeSignatureNotes
Circlecircle(cx, cy, r)Float centre + radius for pixel alignment
Ovaloval(x, y, w, h)Bounding box coordinates
Rectanglerect(x, y, w, h)Bounding box coordinates
Triangletriangle(x1, y1, x2, y2, x3, y3)Three vertices
Lineline(x1, y1, x2, y2, thickness)Endpoints + thickness
Circle Outlinecircle_outline(cx, cy, r, thickness)Hollow circle
Oval Outlineoval_outline(x, y, w, h, thickness)Hollow oval
Rect Outlinerect_outline(x, y, w, h, thickness)Hollow rectangle
Texttext(x, y, "string")Built-in 3x5 font

Circle pixel alignment

The float centre and radius control how circles align to the pixel grid:

circle(8.0, 8.0, 4.0)   // 8px even diameter, centre between pixels
circle(8.0, 8.0, 3.5)   // 7px odd diameter, centre on pixel 8
oval(4, 4, 8, 8)         // Same 8px circle via bounding box

Erasing with palette index 0

Palette index 0 is transparent. Switch to it to cut holes in shapes:

constexpr auto donut = sprite_16x16(
    circle(8.0, 8.0, 6.0),     // Filled circle (palette 1)
    palette_idx(0),              // Switch to transparent
    circle(8.0, 8.0, 3.0)       // Erase inner circle
);

Grouping shapes

Use group() to assign multiple shapes to the same palette index:

constexpr auto sprite = sprite_16x16(
    group(circle(8.0, 8.0, 3.0), line(0, 0, 16, 16, 1)),  // Both palette 1
    group(rect(0, 0, 16, 16))                               // Palette 2
);

OAM attributes

Each sprite result provides a pre-filled obj method that sets the correct shape, size, and colour depth for OAM:

auto obj_attrs = sprite.obj(gba::tile_index(dest));
obj_attrs.x = 120;
obj_attrs.y = 80;
gba::obj_mem[0] = obj_attrs;

Example output

Several consteval shapes rendered as sprites:

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

#include <cstring>

using namespace gba::shapes;

// Compile-time sprites
constexpr auto spr_circle = sprite_16x16(circle(8.0, 8.0, 7.0));

constexpr auto spr_donut = sprite_16x16(circle(8.0, 8.0, 7.0), palette_idx(0), circle(8.0, 8.0, 3.0));

constexpr auto spr_rect = sprite_16x16(rect(1, 1, 14, 14));

constexpr auto spr_triangle = sprite_16x16(triangle(8, 1, 15, 14, 1, 14));

constexpr auto spr_face = sprite_32x32(circle(16.0, 16.0, 14.0), // Head (palette 1)
                                       group(                    // Eyes (palette 2)
                                           circle(11.0, 12.0, 2.5), circle(21.0, 12.0, 2.5)),
                                       group( // Mouth (palette 3)
                                           oval(10, 20, 12, 4)),
                                       palette_idx(0),     // Erase
                                       oval(11, 21, 10, 2) // Inner mouth cutout
);

constexpr auto spr_label = sprite_64x32(text(2, 2, "stdgba"),
                                        group(),                      // Reserve palette 2
                                        rect_outline(0, 0, 64, 14, 1) // Border (palette 3)
);

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,
        .linear_obj_tilemap = true,
        .enable_obj = true,
    };

    // Background
    gba::pal_bg_mem[0] = {.red = 4, .green = 6, .blue = 10};

    // Sprite palettes
    gba::pal_obj_bank[0][1] = {.red = 28, .green = 8, .blue = 8};  // Red
    gba::pal_obj_bank[1][1] = {.red = 8, .green = 28, .blue = 8};  // Green
    gba::pal_obj_bank[2][1] = {.red = 8, .green = 8, .blue = 28};  // Blue
    gba::pal_obj_bank[3][1] = {.red = 28, .green = 28, .blue = 8}; // Yellow

    // Face palette
    gba::pal_obj_bank[4][1] = {.red = 31, .green = 25, .blue = 12}; // Skin
    gba::pal_obj_bank[4][2] = {.red = 4, .green = 4, .blue = 8};    // Eyes
    gba::pal_obj_bank[4][3] = {.red = 24, .green = 8, .blue = 8};   // Mouth

    // Label palette
    gba::pal_obj_bank[5][1] = {.red = 31, .green = 31, .blue = 31}; // Text
    gba::pal_obj_bank[5][3] = {.red = 16, .green = 20, .blue = 28}; // Border

    // Copy tile data to OBJ VRAM
    auto* dest = gba::memory_map(gba::mem_vram_obj);
    auto* base = dest;

    auto copy_sprite = [&](const auto& spr) {
        auto idx = gba::tile_index(dest);
        std::memcpy(dest, spr.data(), spr.size());
        dest += spr.size() / sizeof(*dest);
        return idx;
    };

    auto idx_circle = copy_sprite(spr_circle);
    auto idx_donut = copy_sprite(spr_donut);
    auto idx_rect = copy_sprite(spr_rect);
    auto idx_triangle = copy_sprite(spr_triangle);
    auto idx_face = copy_sprite(spr_face);
    auto idx_label = copy_sprite(spr_label);

    // Place sprites across the screen
    auto place = [](int slot, auto spr_data, unsigned short tile_idx, unsigned short x, unsigned short y,
                    unsigned short pal) {
        auto obj = spr_data.obj(tile_idx);
        obj.x = x;
        obj.y = y;
        obj.palette_index = pal;
        gba::obj_mem[slot] = obj;
    };

    place(0, spr_circle, idx_circle, 20, 64, 0);
    place(1, spr_donut, idx_donut, 52, 64, 1);
    place(2, spr_rect, idx_rect, 84, 64, 2);
    place(3, spr_triangle, idx_triangle, 116, 64, 3);
    place(4, spr_face, idx_face, 156, 56, 4);
    place(5, spr_label, idx_label, 88, 120, 5);

    // Hide remaining sprites
    for (int i = 6; i < 128; ++i) {
        gba::obj_mem[i] = {.disable = true};
    }

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

Shapes demo