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
- Hardware Overview
- Build Setup
- Memory Constraints
- Graphics Limitations
- Audio on Dreamcast
- Asset Embedding
- File System and VFS
- CD-ROM Considerations
- Debugging
- Optimization Checklist
- 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:
- Sorts all polygons into tiles
- Processes each tile independently
- 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_DREAMCASTfor platform-specific code paths - [ ] Profile with
SIMULANT_PROFILE=1and analyze withgprof - [ ] 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:
- 16MB RAM -- the absolute limiting factor for asset complexity
- Fixed-function rendering -- no custom shaders, plan visuals accordingly
- 2 lights per object -- bake lighting or use simple setups
- Texture size limits -- max 1024px, power-of-two recommended
- Embedded assets -- all content compiled into the ELF
- OGG music, WAV SFX -- stream music to save RAM
- Low-poly models -- create Dreamcast-specific variants
- 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
- Asset Pipeline Guide -- Asset preparation and formats
- Performance Guide -- Optimization techniques
- 3D Game Development Guide -- 3D development best practices
- Platform Notes -- Platform API overview
- C++ Guidelines -- Coding standards for embedded targets