Documentation

Learn Simulant

Everything you need to know to build games with Simulant

Sega Dreamcast Development Guide

This guide covers the specifics of developing for the Sega Dreamcast with Simulant, including hardware limitations, build setup, asset constraints, and optimization strategies.


Table of Contents

  1. Hardware Overview
  2. Build Setup
  3. Memory Constraints
  4. Graphics Limitations
  5. Audio on Dreamcast
  6. Asset Embedding
  7. File System and VFS
  8. CD-ROM Considerations
  9. Debugging
  10. Optimization Checklist
  11. Distribution

1. Hardware Overview

The Sega Dreamcast has severely constrained resources compared to modern desktop systems. Understanding the hardware is essential for effective development.

Specifications

Component Specification
CPU Hitachi SH-4 @ 200MHz (360 MIPS, 1.4 GFLOPS)
RAM 16MB main system RAM
VRAM 8MB video RAM (expandable to 16MB for development)
GPU NEC PowerVR2 (PVR2) -- tile-based deferred renderer
Audio Yamaha AICA -- 64-channel PCM/ADPCM
Storage GD-ROM drive (12x max CAV, 128KB buffer)
Video Output NTSC/PAL composite, VGA (640x480)

Renderer Architecture

The PowerVR2 uses a tile-based deferred rendering architecture. Unlike immediate-mode renderers (desktop OpenGL/DirectX), the PVR2:

  1. Sorts all polygons into tiles
  2. Processes each tile independently
  3. Resolves depth per-tile, eliminating overdraw

This means overdraw is much cheaper on Dreamcast than on desktop GPUs. However, polygon count and texture memory are the primary bottlenecks.

GLdc Renderer

Simulant uses GLdc, an OpenGL ES 1.x implementation for the Dreamcast, with the KOSPVR backend. This means:

  • Rendering is fixed-function only -- no custom shaders
  • OpenGL ES 1.1 feature set
  • Texture formats include native DTEX and KMG

2. Build Setup

Prerequisites

Building for Dreamcast requires the KallistiOS (KOS) toolchain. The recommended approach is using Docker:

# Pull the Dreamcast SDK Docker image
docker pull kazade/dreamcast-sdk

Alternatively, you can install the toolchain manually using DreamSDK or by compiling KallistiOS from source.

Building

# Build for Dreamcast
simulant build dreamcast

# This uses Docker to cross-compile in a controlled environment
# On first run, it will download the Docker image (may take a while)

Compiler Flags

The Dreamcast build uses specific compiler flags for the SH-4 CPU:

-mfsrra -mfsca -ffp-contract=fast

These enable SH-4-specific instructions (fsrra for reciprocal square root, fsca for sin/cos) and aggressive floating-point contraction. The SH-4 has a built-in FPU with transcendental instructions that can significantly speed up math operations.

Debug vs Release

# Debug build (with symbols)
simulant build dreamcast --debug

# Release build (stripped, optimized)
simulant build dreamcast --release

In release builds, debugging information is stripped and debug info is generated separately (SIMULANT_SEPERATE_DEBUGINFO=ON by default for Dreamcast).


3. Memory Constraints

16MB RAM

This is the single biggest constraint. 16MB must hold:

  • Your game code
  • All loaded assets (meshes, textures, sounds)
  • Runtime data (scene graph, physics, UI)
  • Stack and heap overhead

Practical limits:

  • Keep total asset footprint under 10-12MB to leave room for runtime
  • Textures are the largest consumer of memory
  • Sound effects loaded into memory add up quickly

Memory Management Strategies

1. Use small textures:

// Dreamcast: use 256x256 or smaller
auto tex = assets->load_texture("textures/hero_256.png");

// Avoid large atlases -- split into multiple smaller ones

2. Embed fonts at specific sizes:

// Embed font data directly in the executable
#include "fonts/orbitron_ttf.h"  // Generated by xxd -i

smlt::FontFlags flags;
flags.size = 16;
auto font = assets->create_font_from_memory(Orbitron_ttf, Orbitron_ttf_len, flags);

3. Stream music, load SFX selectively:

// Stream background music from disc
auto music = assets->load_sound("music/level.ogg");  // OGG is streamed

// Load only the SFX you need for the current scene
smlt::SoundFlags sfx_flags;
sfx_flags.stream_audio = false;
auto jump = assets->load_sound("sfx/jump.wav", sfx_flags);

4. Run garbage collection between scenes:

void on_deactivate() override {
    // Release scene-specific asset references
    level_mesh_id_ = smlt::AssetID();

    // Force cleanup
    assets->run_garbage_collection();
}

5. Use power-of-two texture dimensions:

While not strictly required on Dreamcast, power-of-two textures are more memory-efficient and render correctly on the PVR2.


4. Graphics Limitations

Renderer: Fixed-Function OpenGL ES 1.x

The GLdc renderer uses fixed-function OpenGL ES 1.1. This means:

  • No custom vertex shaders
  • No custom fragment shaders
  • Lighting is handled through fixed-function per-vertex lighting
  • Texturing uses fixed-function texture combiners

If your game relies on custom shaders, you must implement the visual effect through alternative means (pre-baked textures, vertex manipulation, multi-pass rendering).

Maximum 2 Lights Per Object

The Dreamcast renderer supports only 2 lights per renderable. This is a hard limit set at compile time.

// On Dreamcast, only the 2 closest lights affect each object
// Plan lighting accordingly:
// - 1 ambient light (affects everything)
// - 1 directional light (sun/moon)
// Point lights and spot lights compete for the 2 slots

Workaround for limited lights:

  • Bake lighting into textures (lightmaps)
  • Use vertex colors for additional illumination variation
  • Use bright ambient light to reduce dependence on dynamic lights

Maximum Texture Size: 1024px

Textures larger than 1024x1024 will not work reliably. Recommended maximum is 512x512 for safety.

Usage Recommended Size
Character textures 256x256
Environment textures 256x256 or 512x512
UI textures 128x128 or 256x256
Skybox faces 256x256

No Custom Shaders

All visual effects must be achieved through:

  • Multi-pass rendering with different blend modes
  • Pre-baked textures (lightmaps, AO maps)
  • Vertex colors
  • Fixed-function texture combiners

Avoid Skyboxes

Cube map textures can easily exceed the 8MB VRAM limit on Dreamcast. Instead, use a solid color or a simple gradient background:

pipeline_->viewport->set_color(smlt::Color(0.4f, 0.6f, 0.9f, 1.0f));

5. Audio on Dreamcast

Supported Formats

Format Extension Streaming Notes
OGG Vorbis .ogg Yes Recommended for music. Streamed from disc.
WAV .wav No Loaded fully into memory. Use for short SFX.

Streaming Music

// Background music - streamed from disc to save RAM
smlt::SoundFlags music_flags;
music_flags.stream_audio = true;
auto music = assets->load_sound("music/level.ogg", music_flags);

Sound Effects

// Sound effects - loaded into memory for instant playback
smlt::SoundFlags sfx_flags;
sfx_flags.stream_audio = false;
auto jump = assets->load_sound("sfx/jump.wav", sfx_flags);

Keep the number of simultaneously loaded SFX low. Each WAV file consumes RAM proportional to its duration and sample rate.

CD Audio Interruption

File reads from the GD-ROM can interrupt CD audio playback. If you experience audio glitches during level loading, use:

vfs->enable_read_blocking();

This serializes file reads to avoid interrupting the audio stream.


6. Asset Embedding

On Dreamcast, there is no traditional filesystem. Simulant embeds all assets directly into the executable during the build process.

How Embedding Works

The build system converts all files listed in your asset_paths into C data arrays that are compiled into the ELF. At runtime, the Virtual File System (VFS) serves these embedded assets as if they were regular files.

Configuring Asset Paths

Ensure simulant.json lists all directories containing assets:

{
    "name": "mygame",
    "target_platforms": ["linux", "dreamcast"],
    "asset_paths": ["assets"],
    "core_assets": true
}

Build Size

The resulting Dreamcast ELF executable includes all assets. Keep an eye on the final binary size:

  • A typical Dreamcast CD holds ~1GB, but the ELF must fit in RAM at load time
  • Aim for a binary under 12MB to stay within memory constraints
  • Strip debug symbols in release builds to reduce size

7. File System and VFS

Virtual File System

Simulant's VFS abstracts file access across platforms. On Dreamcast, the VFS serves embedded assets. On desktop, it reads from disk.

Platform-Specific Paths

The VFS supports platform-specific path prefixes:

Platform Supported Prefixes
Desktop Standard paths (models/hero.obj)
Dreamcast /, cdrom:

For Dreamcast, you can also organize assets in platform-specific subdirectories:

assets/
├── models/
│   ├── hero.obj          # Desktop version (high-poly)
│   └── dreamcast/
│       └── hero.obj      # Dreamcast version (low-poly)
└── textures/
    ├── ground.png        # Desktop (512x512)
    └── dreamcast/
        └── ground.png    # Dreamcast (256x256)

When loading "models/hero.obj", the VFS automatically tries models/dreamcast/hero.obj first on Dreamcast, falling back to models/hero.obj.

Conditional Asset Loading

You can also handle platform differences explicitly in code:

#ifdef SIMULANT_PLATFORM_DREAMCAST
    auto mesh = assets->load_mesh("meshes/hero_dc.obj");
    auto tex = assets->load_texture("textures/hero_256.png");
#else
    auto mesh = assets->load_mesh("meshes/hero.obj");
    auto tex = assets->load_texture("textures/hero_512.png");
#endif

8. CD-ROM Considerations

GD-ROM Drive

The Dreamcast's GD-ROM drive has these characteristics:

  • 12x max speed (CAV -- constant angular velocity)
  • 128KB buffer RAM
  • Seek times are significant
  • Reading data during CD audio playback can cause interruptions

Loading Strategies

1. Load all assets at scene start:

Since assets are embedded, loading is fast (memory copy). Load everything in on_load():

void on_load() override {
    // All assets are in the ELF -- just load references
    auto mesh = assets->load_mesh("meshes/level_1.obj");
    auto tex = assets->load_texture("textures/level_1.png");
    auto music = assets->load_sound("music/level_1.ogg");
    // ... create scene nodes ...
}

2. Preload between scenes:

Use the SceneManager preloading system:

scenes->preload_in_background("level_2").then([this]() {
    level_2_ready_ = true;
});

3. Avoid runtime asset loading during gameplay:

Embedded loading is fast but not instantaneous. Loading a large texture during gameplay can cause a visible hitch.


9. Debugging

dcload-ip

For debugging on real hardware or emulators, use dcload-ip to upload and run ELF files over the network:

dc-load-ip -t my_game.elf

Profiling

Enable profiling mode to generate gmon.out for analysis with gprof:

# Run with profiling
dc-load-ip -c my_game.elf.debug

# Analyze with gprof
sh-elf-gprof -b -p my_game.elf.debug gmon.out > profile.txt

Logging

Use S_INFO and S_DEBUG macros for logging. Output goes to the dcload console:

S_INFO("Scene loaded with {} actors", actor_count);
S_DEBUG("Player position: {}, {}, {}", pos.x, pos.y, pos.z);

Emulators

  • lxdream -- Good Dreamcast emulator for Linux. Can run CDI images directly:
    simulant run dreamcast
    
  • Redream -- Modern, accurate emulator. Good for testing final CDI images.

10. Optimization Checklist

Use this checklist when preparing a Dreamcast build:

Textures

  • [ ] All textures are power-of-two dimensions (128, 256, 512, 1024)
  • [ ] No texture exceeds 1024px (prefer 256-512px)
  • [ ] Texture atlases are split into smaller chunks (max 512x512)
  • [ ] Consider using DTEX/KMG formats for native Dreamcast textures

Models

  • [ ] Use low-poly models (Dreamcast-specific variants)
  • [ ] Remove unnecessary submeshes
  • [ ] Keep vertex count under ~50,000 triangles on screen

Lighting

  • [ ] Use ambient + 1 directional as primary lighting
  • [ ] Minimize point lights and spot lights (compete for 2 light slots)
  • [ ] Consider baking lighting into textures for static geometry

Audio

  • [ ] Use OGG for music (streamed)
  • [ ] Use WAV for SFX (loaded into memory)
  • [ ] Minimize simultaneously loaded SFX
  • [ ] Use vfs->enable_read_blocking() if CD audio glitches

Memory

  • [ ] Total binary size under 12MB
  • [ ] Embed fonts from memory rather than loading from files
  • [ ] Run garbage collection between scene transitions
  • [ ] Avoid skyboxes -- use solid color or gradient backgrounds

Physics

  • [ ] Use simple collider shapes (boxes, spheres, capsules)
  • [ ] Reduce physics frequency to 30Hz if CPU-bound
  • [ ] Avoid triangle mesh colliders for dynamic objects

Code

  • [ ] Use #ifdef SIMULANT_PLATFORM_DREAMCAST for platform-specific code paths
  • [ ] Profile with SIMULANT_PROFILE=1 and analyze with gprof
  • [ ] Test on real hardware, not just emulators

11. Distribution

Building a CDI Image

simulant package dreamcast

This generates a .cdi (or .bin) disc image ready for burning or emulation.

Burning to CD-R

The generated image can be burned to a CD-R using standard disc burning software. The Dreamcast can read standard CD-R media.

Testing in Emulators

# Using lxdream
lxdream packages/mygame_dreamcast.cdi

# Or via simulant-tools
simulant run dreamcast

Online Distribution

For distribution via homebrew channels or websites:

  • Provide the CDI image file
  • Include instructions for burning or loading in an emulator
  • Consider providing a lower-resolution variant for slower network loading

Summary

Dreamcast development in Simulant requires careful attention to:

  1. 16MB RAM -- the absolute limiting factor for asset complexity
  2. Fixed-function rendering -- no custom shaders, plan visuals accordingly
  3. 2 lights per object -- bake lighting or use simple setups
  4. Texture size limits -- max 1024px, power-of-two recommended
  5. Embedded assets -- all content compiled into the ELF
  6. OGG music, WAV SFX -- stream music to save RAM
  7. Low-poly models -- create Dreamcast-specific variants
  8. Profile on real hardware -- emulators can mask performance issues

Despite these constraints, the Dreamcast is a capable 3D platform with a devoted homebrew community. Simulant abstracts much of the platform complexity, letting you focus on game development.


Further Reading