Skip to content

L01 — Hello ROM

Goal

Build your first libdragon ROM and see text on screen in Ares. That proves the toolchain, Makefile, and emulator path work before anything harder.

What you will see

A dark blue screen with:

text
N64 Educator
L01 — Hello ROM
If you can read this,
your toolchain works!

Project layout

text
lessons/l01-hello-rom/
  Makefile
  src/main.c

The Makefile sets ROMNAME and includes the shared fragment:

make
ROMNAME   := l01_hello
ROM_TITLE := "L01 Hello"

include ../../common/lesson.mk

common/lesson.mk expects N64_INST, includes libdragon’s n64.mk, compiles every .c under src/, and links l01_hello.z64.

Source walkthrough

Includes and main

c
#include <libdragon.h>

int main(void)
{

Almost every libdragon program starts this way. Subsystems are initialized explicitly.

Display + RDPQ

c
    display_init(RESOLUTION_320x240, DEPTH_16_BPP, 2, GAMMA_NONE,
                 FILTERS_RESAMPLE);
    rdpq_init();
CallMeaning
display_initResolution, color depth, framebuffer count (here: 2 for double-buffering)
rdpq_initReality Display Processor command queue helpers — we use them for clear + text

320×240 at 16 bpp is a common, friendly mode for development.

Font

c
    rdpq_text_register_font(1, rdpq_font_load_builtin(FONT_BUILTIN_DEBUG_VAR));

Font id 1 is a built-in debug font. Later lessons use nicer fonts; this one needs no assets.

The loop

c
    while (1) {
        surface_t *disp = display_get();
        rdpq_attach(disp, NULL);

        rdpq_clear((color_t){ .r = 16, .g = 24, .b = 48, .a = 255 });

        rdpq_text_print(NULL, 1, 40, 80, "N64 Educator");
        /* ... more lines ... */

        rdpq_detach_show();
    }
StepRole
display_getWait for a free framebuffer
rdpq_attachDirect RDPQ drawing at that surface (NULL z-buffer for 2D-only)
rdpq_clearFill background color
rdpq_text_printDraw a string (font id, x, y)
rdpq_detach_showFinish and present when ready

There is no “engine” yet — just an infinite loop that redraws the same frame. Module 0 later adds timing, input, and assets.

Build & run

From the repository root:

bash
# Native toolchain
export N64_INST=/opt/libdragon   # your path
make -C lessons/l01-hello-rom

# Or Docker
libdragon make -C lessons/l01-hello-rom

Open lessons/l01-hello-rom/l01_hello.z64 in Ares with Homebrew mode enabled.

Clean

bash
make -C lessons/l01-hello-rom clean

Exercises

  1. Change the clear color and rebuild. Can you get a warm sunset background?
  2. Move the text by editing the x, y arguments to rdpq_text_print.
  3. Add a fourth line with your name.

Troubleshooting

ProblemFix
N64_INST is not setSee Setup
Compile errors about missing headerslibdragon not installed / wrong branch
Emulator shows nothing / crashesUse Ares; enable Homebrew mode
Text missing but color OKFont registration failed — compare your main.c to the lesson file

Full lesson source

The blocks below are imported from the real repository files at build time (VitePress <<< snippets). They are not hand-copied into this markdown.

lessons/l01-hello-rom/Makefile · lessons/l01-hello-rom/src/main.c

lessons/l01-hello-rom/Makefile
make
# Lesson 01 — Hello ROM
# Build: make -C lessons/l01-hello-rom
# Requires N64_INST (libdragon preview toolchain).

ROMNAME  := l01_hello
ROM_TITLE := "L01 Hello"

include ../../common/lesson.mk
lessons/l01-hello-rom/src/main.c
c
/**
 * L01 — Hello ROM
 * ============================================================================
 *
 * LEARNING GOAL
 * -------------
 * Prove the toolchain works: compile C → .z64 → run in Ares and see text.
 *
 * WHAT HAPPENS EACH FRAME
 * -----------------------
 *   display_get()     wait for a free framebuffer (display-paced; see L02 vsync)
 *   rdpq_attach()     all following RDP draws go to that buffer
 *   rdpq_clear()      fill background color
 *   rdpq_text_print() draw strings with a registered font
 *   rdpq_detach_show() present when the RDP is done
 *
 * There is no "engine" yet — just an infinite loop redrawing the same message.
 *
 * BUILD: make -C lessons/l01-hello-rom
 * DOCS:  docs/guide/m0/l01-hello-rom.md
 */

#include <libdragon.h>

int main(void)
{
    /*
     * display_init(resolution, depth, num_buffers, gamma, filters)
     *   320x240  — classic friendly homebrew res
     *   16 bpp   — 5:5:5:1 color, less RAM than 32 bpp
     *   2 buffers — double buffering (draw one while showing the other)
     */
    display_init(RESOLUTION_320x240, DEPTH_16_BPP, 2, GAMMA_NONE,
                 FILTERS_RESAMPLE);

    /* RDPQ = helpers that talk to the Reality Display Processor. */
    rdpq_init();

    /*
     * Register a font under numeric id 1.
     * Later rdpq_text_print(..., font_id=1, ...) uses this.
     * Built-in debug font needs no assets on the ROM.
     */
    rdpq_text_register_font(1, rdpq_font_load_builtin(FONT_BUILTIN_DEBUG_VAR));

    /* N64 games almost never exit main — loop forever. */
    while (1) {
        /* Block until a framebuffer is free (ties us to display timing). */
        surface_t *disp = display_get();

        /*
         * NULL z-buffer: we are 2D-only this lesson.
         * 3D lessons pass display_get_zbuf() as the second argument.
         */
        rdpq_attach(disp, NULL);

        /* Background color: dark blue (RGBA 0–255). */
        rdpq_clear((color_t){ .r = 16, .g = 24, .b = 48, .a = 255 });

        /*
         * rdpq_text_print(style, font_id, x, y, string)
         *   x,y are in pixels from the top-left of the framebuffer.
         */
        rdpq_text_print(NULL, 1, 40, 80, "N64 Educator");
        rdpq_text_print(NULL, 1, 40, 100, "L01 — Hello ROM");
        rdpq_text_print(NULL, 1, 40, 140, "If you can read this,");
        rdpq_text_print(NULL, 1, 40, 156, "your toolchain works!");

        /* Finish RDP work and queue this buffer for display. */
        rdpq_detach_show();
    }
}

What you learned

  • libdragon project shape (Makefile + src/)
  • Display init and a minimal present loop
  • RDPQ clear + built-in text

Next

L02 — Game loop & display

N64 Educator v1.2.2 — libdragon + Tiny3D · branch master