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:
N64 Educator
L01 — Hello ROM
If you can read this,
your toolchain works!Project layout
lessons/l01-hello-rom/
Makefile
src/main.cThe Makefile sets ROMNAME and includes the shared fragment:
ROMNAME := l01_hello
ROM_TITLE := "L01 Hello"
include ../../common/lesson.mkcommon/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
#include <libdragon.h>
int main(void)
{Almost every libdragon program starts this way. Subsystems are initialized explicitly.
Display + RDPQ
display_init(RESOLUTION_320x240, DEPTH_16_BPP, 2, GAMMA_NONE,
FILTERS_RESAMPLE);
rdpq_init();| Call | Meaning |
|---|---|
display_init | Resolution, color depth, framebuffer count (here: 2 for double-buffering) |
rdpq_init | Reality Display Processor command queue helpers — we use them for clear + text |
320×240 at 16 bpp is a common, friendly mode for development.
Font
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
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();
}| Step | Role |
|---|---|
display_get | Wait for a free framebuffer |
rdpq_attach | Direct RDPQ drawing at that surface (NULL z-buffer for 2D-only) |
rdpq_clear | Fill background color |
rdpq_text_print | Draw a string (font id, x, y) |
rdpq_detach_show | Finish 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:
# Native toolchain
export N64_INST=/opt/libdragon # your path
make -C lessons/l01-hello-rom
# Or Docker
libdragon make -C lessons/l01-hello-romOpen lessons/l01-hello-rom/l01_hello.z64 in Ares with Homebrew mode enabled.
Clean
make -C lessons/l01-hello-rom cleanExercises
- Change the clear color and rebuild. Can you get a warm sunset background?
- Move the text by editing the
x, yarguments tordpq_text_print. - Add a fourth line with your name.
Troubleshooting
| Problem | Fix |
|---|---|
N64_INST is not set | See Setup |
| Compile errors about missing headers | libdragon not installed / wrong branch |
| Emulator shows nothing / crashes | Use Ares; enable Homebrew mode |
| Text missing but color OK | Font 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
# 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.mklessons/l01-hello-rom/src/main.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