Flow A: SDL startup and main loop

End-to-end trace from process start through ROM load to the perpetual GB_run() loop. This is the reference integration pattern for custom frontends.

Trigger

User runs ./sameboy game.gb or opens SameBoy from the desktop.

Call chain

main() [SDL/main.c:1443] ├─ SDL_Init, parse CLI flags ├─ load_configuration() prefs.bin ├─ SDL_CreateWindow, init audio/OpenGL └─ run(filename) [SDL/main.c:~1100] ├─ GB_init(&gb, model) [Core/gb.c:164] ├─ register callbacks [SDL/main.c:1168-1193] ├─ GB_load_rom / GB_load_isx ├─ GB_load_battery (.sav) ├─ GB_load_cheats (.cht) └─ while (true) [SDL/main.c:1273] ├─ handle rewind / pause ├─ GB_run(&gb) [Core/gb.c:1190] └─ handle_pending_command() → may goto restart
Step 1 — main() bootstrap

main() initializes SDL subsystems (video, audio, events), parses flags (--model, --fullscreen, --stop-debugger), loads prefs.bin from beside the binary or SDL_GetPrefPath("","SameBoy"), creates the window, and calls run(rom_path).

Configuration lives in global configuration_t (SDL/configuration.c): key bindings, scale factor, color correction, rewind length, rumble mode, etc.

main() entry

Step 2 — GB_init and callback registration

On first ROM load, run() calls GB_init(&gb, model) which allocates WRAM/VRAM, zeroes state, calls GB_reset(), and loads default border tile data.

SDL then wires the core to OS services:

RegistrationHandlerPurpose
GB_set_boot_rom_load_callbackload_boot_romLoad dmg_boot.bin etc. from DATA_DIR
GB_set_vblank_callbackvblankFlip buffers, render texture, poll input
GB_set_pixels_outputactive_pixel_bufferIndexed pixel write target
GB_set_rgb_encode_callbackrgb_encodePalette index → ARGB32
GB_apu_set_sample_callbackgb_audio_callbackQueue PCM to SDL audio
GB_set_update_input_hint_callbackhandle_eventsPoll SDL events mid-frame
GB_set_rumble_callbackrumbleGamepad rumble

Callback registration

Step 3 — ROM and peripheral file load

GB_load_rom(&gb, filename) reads the file into gb->rom, computes CRC32, calls GB_configure_cart() to detect MBC type from header byte 0x0147.

Side files loaded by path substitution:

  • game.savGB_load_battery (MBC RAM + RTC)
  • game.chtGB_load_cheats
  • game.sym + bundled registers.sym → debugger symbols

If GBEmulatedModel is "auto", model may change after ROM load (model_to_use()).

ROM and sidecar loading

Step 4 — Main emulation loop

The infinite loop is not frame-based:

while (true) {
    if (paused || rewind_paused) {
        SDL_WaitEvent(NULL);
        handle_events(&gb);
    } else {
        if (do_rewind) GB_rewind_pop(&gb);
        GB_run(&gb);  // one CPU quantum
    }
    if (handle_pending_command()) goto restart;
}

Validation: CLI model flag, ROM readability (error dialog via log capture).

Business logic: All in Core; SDL only sets pending_command flags for reset/save/load state from hotkeys.

State writes: Core mutates GB_gameboy_t; SDL writes prefs.bin on exit via atexit.

Errors: Load failures show SDL message box; unreadable save path logs warning.

Main loop

Step 5 — vblank presentation (output path)

When PPU completes a frame, GB_display_vblank sets vblank_just_occured and invokes vblank_callback. SDL's vblank():

  1. Calls handle_events(gb) again (input at frame boundary)
  2. Optionally blends previous frame (configuration.blending_mode)
  3. render_texture() → OpenGL shader or SDL_Renderer blit
  4. Swaps pixel buffers and re-points GB_set_pixels_output

Audio flows independently via gb_audio_callback on each APU sample batch, with queue backpressure at freq/8.

Failure modes

  • Missing boot ROM → callback returns false; game may run without boot animation
  • Re-entrant GB_run → assertion in debug builds
  • goto restart on model switch — full re-init path, must re-register callbacks if GB_free called