Graphics Engine Documentation

Background Layer System & VRAM Management

Klonoa: Empire of Dreams uses the GBA's four hardware background layers in Mode 0. The graphics engine manages a pipeline of compressed ROM data → decompression → DMA to VRAM, with runtime tilemap streaming for smooth scrolling. This page documents the subsystem as understood from decompilation so far.

VRAM Layout

Each of the three gameplay BG layers is assigned its own 16 KB character base block (charblock) in VRAM. This prevents tile data from different layers from conflicting.

Layer Charblock VRAM Address Screenblock Tilemap VRAM
BG0 (far)00x06000000280x0600E000
BG1 (mid)10x06004000290x0600E800
BG2 (near)20x06008000300x0600F000

The title screen uses all four charblocks (0–3), loading shared tilesets for the HUD, item icons, and UI elements. During gameplay, charblock 3 (0x0600C000) holds additional shared graphics loaded by SetupSceneGfx.

ROM Data Tables

Level Background Tables

Three parallel pointer tables store compressed background data for every level in the game. Each table is indexed by a combined vision/world/layer formula.

Table Address Entries Indexing Content
BG Tile Table 0x08189034 162 (vision−1)×27 + world×3 + layer Compressed 4bpp tile charblock data
BG Tilemap Table 0x081892BC 162 (vision−1)×27 + world×3 + layer Compressed screenblock tilemap data
BG Palette Table 0x08188F5C 54 (vision−1)×9 + world Compressed 512-byte RGB555 palette (shared by all 3 layers)

There are 6 visions × 9 worlds × 3 layers = 162 tile/tilemap entries and 6 × 9 = 54 palette entries (one palette is shared across all three layers of each scene).

Layer Configuration Tables

A lookup table at 0x08057ACC maps (levelIdx, sublevel) to an entry index used by the tile/tilemap loaders. Seven additional sub-tables define per-layer properties:

AddressNameContent
0x08057ACCLayer Config LookupEntry index + charblock config per level
0x080576D4Scroll FlagsScroll mode flags per layer
0x08057714Layer WidthTilemap width per layer config
0x08057794Layer HeightTilemap height per layer config
0x080578D4Charblock IndexWhich charblock base to use
0x08057914Screenblock IndexWhich screenblock base to use
0x08057894Tile BPPTile color depth (4bpp/8bpp)

Compression Format

All background assets use the game's DecompressData pipeline (at 0x08043AF4). Each compressed blob has a 4-byte header followed by the compressed payload:

struct CompressedAsset {
    u32 header;      // bit 31: Huffman flag, bits 0-30: decompressed size
    u8  payload[];   // compressed data
};

Two compression paths:

After decompression, the first 4 bytes of the result are a sub-header that encodes the BGCNT register template (screen size, screenblock base) and a DMA size hint. The remaining bytes are the actual tile/tilemap data.

struct DecompressedBGAsset {
    u16 bgcnt;       // BGCNT register template (CBB always 0, filled at runtime)
    u16 dmaHint;     // approximate DMA size / 256
    u8  data[];      // tile charblock or screenblock tilemap data
};

BGCNT Template Encoding

The bgcnt field follows the GBA hardware register format but with CBB = 0 always. The game's InitLevelBG overrides the CBB field at runtime based on which charblock is assigned to each layer.

BitsFieldValues Seen
1–0Priority0–3
3–2Char Base BlockAlways 0 (overridden at runtime)
7Color Mode0 = 4bpp (16 colors), 1 = 8bpp (256 colors)
12–8Screen Base BlockTypically 4
15–14Screen Size0 = 256×256, 1 = 512×256, 2 = 256×512, 3 = 512×512

Function Pipeline

Level background setup flows through a clear pipeline of functions. Here is the call sequence from level entry to first frame:

1. Level Initialization

InitLevelBG()                    @ 0x08001158 — master level BG init
  ├─ Disables VBlank interrupt
  ├─ Reads level/world from gSceneControl
  ├─ For each of 3 layers:
  │   ├─ Reads tile pointer from BG_TILE_TABLE[index]
  │   ├─ AllocAndDecompress(tilePtr)
  │   ├─ DMA tiles → VRAM charblock (CB0/CB1/CB2)
  │   ├─ Reads tilemap pointer from BG_TILEMAP_TABLE[index]
  │   ├─ AllocAndDecompress(tilemapPtr)
  │   └─ DMA tilemap → IWRAM screen buffer
  ├─ Reads palette from BG_PALETTE_TABLE[palIndex]
  ├─ Sets REG_BG0CNT, REG_BG1CNT, REG_BG2CNT
  ├─ DMAs screen buffers → VRAM screenbases
  └─ Re-enables VBlank

2. Per-Level Tile/Tilemap Loading

DispatchLevelLayerSetup()        @ 0x0804C798 — scripted BG dispatch
  └─ For each sublevel:
      ├─ SetupLevelLayerConfig(level, sub)  @ 0x0804B920
      │   └─ Configures gBGLayerState from lookup tables
      ├─ LoadBGTileData(level, sub)         @ 0x0804B464
      │   └─ DecompressAndDmaCopy(romPtr, vramDest, size)
      └─ LoadBGTilemapData(level, sub)      @ 0x0804B4B0
          └─ AllocAndDecompress + DMA rows to screenblock

3. Runtime Scrolling

VBlankDmaTransfer()              @ 0x08000CE2 — called every VBlank
  ├─ DMA gScreenBufferA → VRAM 0x0600E000
  ├─ DMA gScreenBufferB → VRAM 0x0600E800
  ├─ DMA gScreenBufferC → VRAM 0x0600F000
  └─ DMA OAM buffer → hardware OAM

ScrollBGLayer(direction, delta)  @ 0x08001CD0 — tilemap streaming
  ├─ Updates camera position in gBGLayerState
  └─ Streams new tilemap rows/columns into screen buffers

IWRAM Structures

BG Layer State (0x03003430)

Three 28-byte entries (one per gameplay BG layer) control VRAM destinations, scroll positions, and tilemap dimensions.

struct BGLayerState {           // 28 (0x1C) bytes per entry
    u32 tileVramDest;            // +0x00  VRAM charblock address for tile DMA
    u32 tmapVramDest;            // +0x04  VRAM screenbase for tilemap DMA
    u16 scrollX;                 // +0x08  horizontal scroll (subpixel)
    u16 scrollY;                 // +0x0A  vertical scroll (subpixel)
    u16 field_0C;                // +0x0C
    u16 field_0E;                // +0x0E
    u16 mapWidth;                // +0x10  tilemap width in tiles
    u16 mapHeight;               // +0x12  tilemap height in tiles
    u16 flags;                   // +0x14
    u16 dmaTileCount;            // +0x16  tile rows for DMA transfer
    u8  dmaRowSize;              // +0x18  bytes per DMA row
};

// gBGLayerState[0] = { .tileVramDest = 0x06000000, .tmapVramDest = 0x0600E000 }
// gBGLayerState[1] = { .tileVramDest = 0x06004000, .tmapVramDest = 0x0600E800 }
// gBGLayerState[2] = { .tileVramDest = 0x06008000, .tmapVramDest = 0x0600F000 }

Decompression Buffer Control (0x03004790)

struct DecompBufferCtrl {
    u8 *buf0;   // +0x00  tile data A / tilemap layer 0
    u8 *buf1;   // +0x04  tile data B / tilemap layer 1
    u8 *buf2;   // +0x08  tile data C
    u8 *buf3;   // +0x0C  tile data D
    u8 *buf4;   // +0x10
    u8 *buf5;   // +0x14  tilemap layer 2
};

Screen Staging Buffers

Three IWRAM buffers (each 2 KB) act as staging areas for tilemap data. During VBlank, VBlankDmaTransfer DMAs them to the corresponding VRAM screenbases. This double-buffering prevents tearing.

BufferIWRAM AddressVRAM DestinationLayer
Screen Buffer A0x030009000x0600E000BG0
Screen Buffer B0x030011000x0600E800BG1
Screen Buffer C0x030019000x0600F000BG2

Tilemap Entry Format

Each tilemap cell is a standard GBA 16-bit screenblock entry:

// 16-bit tilemap entry (GBA BG screenblock format)
u16 entry;
u16 tileId  = entry & 0x3FF;       // bits 0-9:   tile index (0-1023)
u16 hflip   = (entry >> 10) & 1;   // bit 10:     horizontal flip
u16 vflip   = (entry >> 11) & 1;   // bit 11:     vertical flip
u16 palBank = (entry >> 12) & 0xF; // bits 12-15: palette bank (0-15)

Tile IDs are relative to the layer's assigned charblock. Each 4bpp tile is 32 bytes (8×8 pixels, 4 bits per pixel). A single charblock holds up to 512 tiles.

Scene-Specific Shared Tilesets

SetupSceneGfx (0x0804886C) loads several hardcoded tilesets into VRAM that are shared across all levels. These provide the HUD, collectible icons, and UI elements that overlay the per-level backgrounds.

ROM AddressDescriptionVRAM Destination
0x082F4D3CShared tiles → charblock 00x06000000
0x082F518CShared tiles → charblock 10x06004000
0x082F5D0CShared tiles → charblock 20x06008000
0x082F7D64Shared tiles → charblock 30x0600C000
0x08366214Large shared tileset (8 × 4 KB blocks)Via VRAM cursor
0x08367468Small shared tileset (3 × 512-byte blocks)Via VRAM cursor

When a level loads its per-layer tile data (InitLevelBG), it overwrites the charblock regions with level-specific tiles. The per-level tilemaps may reference both the level tiles (at low tile IDs) and the shared tiles (at higher tile IDs that weren't overwritten).

Graphics Stream System

Beyond the BG layer system, the game uses a graphics command stream for scripted scene transitions, cutscenes, and dynamic effects. A byte stream stored in ROM (accessed via gStreamPtr at 0x03004D84) encodes a sequence of commands that are dispatched each frame.

Stream Architecture

The stream processor reads command bytes and dispatches to handler functions. Each command consumes 2–7 bytes from the stream (command byte + arguments). Three dynamically allocated buffers support the stream:

Stream Lifecycle

LoadAndDecompressStream(streamIdx)  // decompress from ROM, set gStreamPtr
AllocAndClearGfxBuffer()             // alloc 32-byte GFX state
AllocAndClearBuffer_52A4()          // alloc 0x480-byte scratch

// ... frame-by-frame command dispatch ...

ShutdownGfxSubsystem()              // on scene exit:
  ├─ ShutdownGfxStream()             // frees stream entries + stream buffer
  ├─ FreeSoundStruct()
  ├─ FreeBuffer_52A4()
  ├─ FreeGfxBuffer()
  ├─ FreeDecompStreamBuffer()
  └─ StopAllMusicPlayers()

Known Stream Commands

AddressNameBytesDescription
0x0804C0BEStreamCmd_ResetEntries2Frees all active stream entries, resets video state
0x0804C1FCStreamCmd_DmaSpriteData4DMAs sprite tile data from stream buffer to OBJ VRAM
0x0804C6A8StreamCmd_EnableMosaic3Sets mosaic on BG2/BG3, reads 4-bit mosaic level
0x0804C776StreamCmd_SetRenderMode2Modifies render mode flags in level state struct
0x0804C798DispatchLevelLayerSetupvarTriggers full per-level BG layer setup pipeline
0x0804C7FCStreamCmd_SetBGScroll7Sets layer scroll X/Y from stream (subpixel «4)
0x0804E76EStreamCmd_StopMusic2Halts all music players
0x0804E7D2StreamCmd_StopSound2Stops sound effects
0x0804E7E6StreamCmd_Nop33No-op (advances stream by 3 bytes)
0x0804E7FAStreamCmd_StopMusicAndDisableIRQ2Stops music and disables VBlank/HBlank interrupts
0x0804E42AStreamCmd_ClearRenderMode2Clears render mode bits in level state
0x0804E5C6StreamCmd_ToggleLayerFlag2Toggles bit 5 of layer state byte
0x0804E5F0StreamCmd_SetBlendMode4Sets REG_BLDCNT from blend mode lookup table
0x0804E708StreamCmd_SetWindowRegs6Writes REG_WININ and REG_WINOUT from stream
0x0804E73AStreamCmd_EnableScrollMode2Sets scroll mode bits in level state
0x0804E814StreamCmd_DisableVBlank2Disables VBlank IRQ and interrupt handlers
0x0804E8FEStreamCmd_DisableVBlankAndStopMusic2Disables VBlank + stops all music
0x0804E974StreamCmd_SetMusicParams4Sets music tempo/volume from stream data
0x0804E9DCStreamCmd_ConfigureBlendvarConfigures blend mode, coefficients, and alpha
0x0804EA94StreamCmd_RunScriptvarRuns an inline script sub-stream
0x0804C24CStreamCmd_ConfigureSpritevarConfigures OAM sprite parameters from stream

The stream command system uses a dispatch table indexed by the command opcode. Commands range from simple 2-byte operations (advance stream, call one function) to complex variable-length scripts that configure multiple hardware registers and manage sprite/palette state. A total of 22 stream commands have been identified so far.

Named Functions

AddressNameModuleDescription
0x08001158InitLevelBGengineMaster level BG init: decompresses all tile/tilemap/palette data, DMAs to VRAM, configures BGCNT registers
0x08001CD0ScrollBGLayerengineRuntime tilemap streaming during BG scrolling
0x08000CE2VBlankDmaTransferengineVBlank handler: DMAs screen buffers and OAM to VRAM
0x08003904ResetVideoRegistersengineFull video init for level entry
0x08003D58ClearVideoStateengineZeroes OAM shadow buffer
0x08003DC0SetupOAMSpritecode_0Configures a single OAM shadow entry
0x08043AF4DecompressDatacode_3Processes sub-header, Huffman+LZ77 or LZ77-only decompression
0x08043B34DecompressAndCopyToPalettecode_3Decompress and DMA to palette/OBJ VRAM
0x0804886CSetupSceneGfxgfxTitle/intro scene setup: loads shared tilesets into all 4 charblocks
0x0804832CUpdateBGScrollRegistersgfxWrites scroll positions to hardware registers
0x08048498UpdateBGTileAnimationgfxAnimates tile data (palette cycling, frame updates)
0x08049726LoadBGPalettegfxLoads background palette from ROM table
0x0804B424DecompressAndDmaCopygfxDecompress ROM data and DMA to VRAM destination
0x0804B464LoadBGTileDatagfxPer-level tile data loader via lookup table
0x0804B4B0LoadBGTilemapDatagfxPer-level tilemap loader with multi-quadrant handling
0x0804B920SetupLevelLayerConfiggfxConfigures BG layer struct from lookup tables
0x0804BBD4SetupWorldMapBGgfxWorld map / dialog background init
0x0804C798DispatchLevelLayerSetupgfxScripted dispatch calling all per-level BG setup functions
0x0804BAB4FinalizeLevelLayerSetupgfxLoads level BG palette from ROM table to palette RAM
0x0804BAD4LoadAndDecompressStreamgfxDecompresses ROM stream data, sets global stream pointer
0x0804BAFCFreeDecompStreamBuffergfxFrees the decompressed stream buffer
0x0804BB12ClearScreenBufferBgfxDMA-fills screenblock buffer B with 0xF000 (blank tilemap)
0x0804BB3CAllocAndClearGfxBuffergfxAllocates and zero-fills the 32-byte GFX state buffer
0x0804BB74FreeGfxBuffergfxFrees the GFX state buffer
0x0804BB88AllocAndClearBuffer_52A4gfxAllocates and zero-fills the 0x480-byte scratch buffer
0x0804BF7CShutdownGfxSubsystemgfxTears down all graphics on scene exit (free chain + stop music)
0x0804BFD0InitGfxStreamStategfxAllocates stream buffer, clears OAM, resets VRAM cursors
0x0804C050ResetGfxStreamEntriesgfxFrees all active stream entries, resets video state
0x0804C0ECLoadGfxStreamEntrygfxLoads/decompresses a stream entry from ROM table
0x0804C1C0DmaSpriteToObjVramgfxDMAs sprite tile data from stream buffer to OBJ VRAM
0x0804C218SetSpriteTableFromIndexgfxSets sprite table pointer from ROM lookup at 0x08189E84
0x0804E42AStreamCmd_ClearRenderModegfxClears render mode bits in level state
0x0804E814StreamCmd_DisableVBlankgfxDisables VBlank IRQ + interrupt handlers
0x0804E8FEStreamCmd_DisableVBlankAndStopMusicgfxDisables VBlank + stops all music
0x08050134StopSoundEffectsm4aStops sound effect playback channels
0x080500FCStopAllMusicPlayersm4aIterates ROM_MUSIC_TABLE to halt all 4 music players
0x0804BF7CShutdownGfxSubsystemgfxTears down all graphics on scene exit (free chain + stop music)
0x0804BE08SetupGfxCallbacksgfxInitializes VBlank/HBlank callback function pointers
0x08048768FadeOutControllergfxManages BG scroll fade-out + callback transition
0x0804BDB4InitLevelStateDefaultsgfxSets default map dimensions, scroll, window registers
0x0804BD8AClearScreenBufferB_AltgfxAlternate screenblock fill (non-word-aligned variant)
0x0804E5F0StreamCmd_SetBlendModegfxSets REG_BLDCNT from blend mode lookup table
0x08000F70UpdateFadeEffectengineApplies brightness fade using REG_BLDY
0x08000FA2HBlankScrollUpdateengineWrites BG scroll from scanline-indexed tables
0x0800111EWaitVBlankAndClearMosaicengineWaits for VBlank, clears REG_MOSAIC
0x0800113EAcknowledgeInterruptengineWrites REG_IE to REG_IF (interrupt acknowledge)
0x08003DA0ClearOamEntries6PlusengineZeroes OAM shadow entries 6–91
0x0800043CAbssystemAbsolute value of signed integer
0x08000448StrCmpsystemByte-by-byte string comparison
0x08000460StrCpysystemNull-terminated string copy
0x08000948FixedMul8system8.8 fixed-point signed multiply (s16×s16 » 8)

Memory Management

The graphics subsystem uses three dynamically allocated buffers, each with a matching alloc/free pair:

BufferGlobalSizeAlloc FunctionFree FunctionPurpose
Decomp Stream gDecompBuffer Variable LoadAndDecompressStream FreeDecompStreamBuffer Holds decompressed graphics command stream data
GFX State gGfxBufferPtr 32 bytes AllocAndClearGfxBuffer FreeGfxBuffer Per-scene graphics state (palette indices, flags)
Scratch Buffer gBuffer_52A4 0x480 bytes AllocAndClearBuffer_52A4 FreeBuffer_52A4 Temporary workspace for graphics stream processing

All three use the same pattern: allocate via thunk_FUN_080001e0, DMA3-fill with zeros using fixed-source mode, then store the pointer in an IWRAM global. The free functions simply call thunk_FUN_0800020c on the stored pointer.

Decompilation Status

Functions in this subsystem that have been fully decompiled to matching C:

FunctionStatusNotes
DecompressDataMatchedTwo-stage (Huffman+LZ77) or single-stage decompression
DecompressAndDmaCopyMatchedDecompress + DMA to VRAM + free
DecompressAndCopyToPaletteMatchedDecompress + 16-bit DMA to palette
AllocAndDecompressMatchedAlloc buffer + decompress into it
FreeDecompStreamBufferMatchedSimple free wrapper
FreeBuffer_52A4MatchedSimple free wrapper
FinalizeLevelLayerSetupMatchedPalette table lookup (asm barrier technique)
LoadAndDecompressStreamMatchedStream table lookup (asm barrier technique)
DisableInterruptsForGfxSetupMatchedSound shutdown (late variable init technique)
IsSelectButtonPressedMatchedInput check (asm barrier technique)
MPlayStopMatchedMusic player stop (no special technique needed)
ShutdownGfxSubsystemMatchedTears down graphics on scene exit (free chain)
StreamCmd_ResetEntriesMatchedStream command: reset all active entries
StreamCmd_EnableMosaicMatchedStream command: enable BG2/BG3 mosaic
StreamCmd_StopMusicMatchedStream command: halt all music
StreamCmd_StopMusicAndDisableIRQMatchedStream command: stop music + disable IRQ
StreamCmd_DisableVBlankMatchedStream command: disable VBlank interrupt
UpdateFadeEffectMatchedApplies brightness fade via REG_BLDY
TransitionToWorldMapMatchedWorld map transition with callback state setup
GetEntityLookupDataMatchedEntity table lookup (shift-pair interleaving technique)
FixedMul8Matched8.8 fixed-point multiply (register asm("r1") for forced copy)
InitSceneStateMatchedSound scene init via voice table lookup
m4aSongNumStopMatchedStop song if currently playing
m4aMPlayCommandMatchedStop player for matching song
SetupGfxCallbacksMatchedWorld map VBlank/callback initialization
FadeOutControllerMatchedScreen fade-out with scene transition switch
MPlayMain_SetAndProcessMatchedStore to gSoundInfo + call MidiProcessEvent (u32 return type trick)
FreeAllDecompBuffersMatchedFree 6 decomp buffers + collision map, stop 4 music channels
SoundCmd_DispatchMatchedRead command byte, dispatch via ROM_SOUND_CMD_TABLE
StreamCmd_StopSoundAndSyncMatchedStop sound, sync freq to BG scroll state
StreamCmd_SetSoundFreqsMatchedRead two u16 frequencies from stream

Functions that are understood but blocked by agbcc code generation differences:

FunctionBlocker
LoadBGTileDataRegister allocation: compiler uses push {lr} instead of push {r4,r5,lr}
LoadBGTilemapDataRegister allocation around table lookup
FreeGfxBufferHand-written bx lr leaf
AllocAndClearGfxBufferDMA3 with stack-allocated source: register allocation around sp
AllocAndClearBuffer_52A4Same DMA3 stack-source pattern
ClearScreenBufferBNon-word-aligned + bx lr leaf
StreamCmd_SetBGScrollRegister allocation mismatch across ReadUnalignedU16 calls
StopAllMusicPlayersUnusual u16 widening pattern in loop init
StreamCmd_StopSoundNon-word-aligned function start
StreamCmd_ClearRenderModeNon-word-aligned bx lr leaf
StreamCmd_SetRenderModeNon-word-aligned bx lr leaf
StreamCmd_Nop3Non-word-aligned bx lr leaf
StreamCmd_DisableVBlankAndStopMusicNon-word-aligned function start
InitGfxStreamStateDMA3 with stack-allocated source
ResetGfxStreamEntriesLoop structure / pointer arithmetic mismatch
Abs, StrCmp, StrCpyHand-written bx lr utility leaves (system module)
HBlankScrollUpdate, AcknowledgeInterruptNon-word-aligned bx lr engine leaves
SetPaletteAnimEntryRegister allocation: compiler swaps r0/r1 for parameter copy