The Game Boy outputs graphics to a 160Γ144 pixel LCD, using a quite complex mechanism to facilitate rendering.
Similarly to other retro systems, pixels are not manipulated individually, as this would be expensive CPU-wise. Instead, pixels are grouped in 8Γ8 squares, called tiles (or sometimes "patterns" or "characters"), often considered as the base unit in Game Boy graphics.
A tile does not encode color information. Instead, a tile assigns a color index to each of its pixels, ranging from 0 to 3. For this reason, Game Boy graphics are also called 2bpp (2 bits per pixel). When a tile is used in the Background or Window, these color indices are associated with a palette. When a tile is used in an object, the indices 1 to 3 are associated with a palette, but ID 0 means transparent.
A palette consists of an array of colors, 4 in the Game Boy's case. Palettes are stored differently in monochrome and color versions of the console.
Modifying palettes enables graphical effects such as quickly flashing some graphics (damage, invulnerability, thunderstorm, etc.), fading the screen, "palette swaps", and more.
The Game Boy has three "layers", from back to front: the Background, the Window, and the Objects.
The background is composed of a tilemap. A tilemap is a large grid of tiles. However, tiles aren't directly written to tilemaps, they merely contain references to the tiles. This makes reusing tiles very cheap, both in CPU time and in required memory space, and it is the main mechanism that helps work around the paltry 8 KiB of video RAM.
The background can be made to scroll as a whole, writing to two hardware registers. This makes scrolling very cheap.
The window is sort of a second background layer on top of the background. It is fairly limited: it has no transparency, it's always a rectangle and only the position of the top-left pixel can be controlled.
Possible usage includes a fixed status bar in an otherwise scrolling game (e.g. Super Mario Land 2).
The objects layer is designed for elements that need to move independently: objects are made of 1 or 2 stacked tiles (8Γ8 or 8Γ16 pixels) and can be displayed anywhere on the screen.
To summarise:
Tile data is stored in VRAM in the memory area at $8000β$97FF; with each tile taking 16 bytes, this area defines data for 384 tiles. In CGB Mode, this is doubled (768 tiles) because of the two VRAM banks.
Each tile has 8Γ8 pixels and a color depth of 2 bits per pixel, allowing each pixel to use one of 4 colors or gray shades.
| Tile IDs forβ¦ | Block 0$8000β87FF | Block 1$8800β8FFF | Block 2$9000β97FF |
|---|---|---|---|
| Objects | 0β127 | 128β255 | β |
| BG/Win, LCDC.4=1 | 0β127 | 128β255 | β |
| BG/Win, LCDC.4=0 | β | 128β255 | 0β127 |
Tiles are always indexed using an 8-bit integer, but the addressing method may differ:
Block 1 is shared by both addressing methods. Objects always use "$8000 addressing", but the BG and Window can use either mode, controlled by LCDC bit 4.
Each tile occupies 16 bytes, where each line is represented by 2 bytes. For each line, the first byte specifies the least significant bit of the color ID of each pixel, and the second byte specifies the most significant bit. In both bytes, bit 7 represents the leftmost pixel, and bit 0 the rightmost.
For example, the tile data $3C $7E $42 $42 $42 $42 $42 $42 $7E $5E $7E $0A $7C $56 $38 $7C:
Row 0: $3C $7E β 0 2 3 3 3 3 2 0 Row 1: $42 $42 β 0 3 0 0 0 0 3 0 Row 2: $42 $42 β 0 3 0 0 0 0 3 0 Row 3: $42 $42 β 0 3 0 0 0 0 3 0 Row 4: $7E $5E β 0 3 0 1 1 1 1 0 Row 5: $7E $0A β 0 0 1 0 1 0 1 0 Row 6: $7C $56 β 0 3 0 1 0 1 1 0 Row 7: $38 $7C β 0 1 1 1 1 1 0 0
For the first row, $3C $7E are 00111100 and 01111110 in binary. The leftmost bits are 0 and 0, thus color index is 00 = 0. The next bits are 0 and 1, thus color index is 10 = 2 (MSB first!).
The Game Boy contains two 32Γ32 tile maps in VRAM at $9800β$9BFF and $9C00β$9FFF. Any of these maps can be used to display the Background or the Window.
Each tile map contains 1-byte indexes of the tiles to be displayed. Since one tile has 8Γ8 pixels, each map holds a 256Γ256 pixel picture. Only 160Γ144 of those pixels are displayed on the LCD at any given time.
In CGB Mode, an additional map of 32Γ32 bytes is stored in VRAM Bank 1:
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|
| Priority | Y flip | X flip | β | Bank | Color palette | ||
Priority between the BG (and window) layer and the OBJ layer is declared in three places: BG Map Attribute bit 7, LCDC bit 0, and OAM Attributes bit 7.
| LCDC.0 | OAM.7 | BG.7 | Priority |
|---|---|---|---|
| 0 | Γ | Γ | OBJ always |
| 1 | 0 | 0 | OBJ |
| 1 | 0 | 1 | BG color 1β3, else OBJ |
| 1 | 1 | 0 | BG color 1β3, else OBJ |
| 1 | 1 | 1 | BG color 1β3, else OBJ |
The PPU can display up to 40 movable objects, each 8Γ8 or 8Γ16 pixels. Only 10 objects can be displayed per scanline. Object tiles are taken from tile blocks 0 and 1 at $8000β$8FFF with unsigned numbering.
Object attributes reside in OAM at $FE00β$FE9F. Each of the 40 entries consists of 4 bytes:
Y = Object's vertical position on the screen + 16.
Y=0 hides an objectY=16 displays at top of screenY=152 displays 8Γ8 object at bottomY=160 hides an objectX = Object's horizontal position on the screen + 8. An off-screen value (X=0 or Xβ₯168) hides the object, but it still counts towards the 10-objects-per-scanline limit.
In 8Γ8 mode, specifies the tile index ($00β$FF) from $8000β$8FFF. In 8Γ16 mode, the top tile is NN & $FE, the bottom tile is NN | $01.
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|
| Priority | Y flip | X flip | DMG pal | Bank | CGB palette | ||
Selection priority: During OAM scan, the PPU scans OAM sequentially ($FE00 to $FE9F), selecting up to 10 objects whose Y coordinates overlap the current line.
Drawing priority:
FF46 β DMA: Writing to this register starts a DMA transfer from ROM or RAM to OAM.
The transfer takes 160 M-cycles: 640 dots (1.4 lines) in normal speed. During OAM DMA on DMG, the CPU can only access HRAM ($FF80β$FFFE).
LCDC is the main LCD Control register. Its bits toggle what elements are displayed on the screen.
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|
| LCD enable | Win tilemap | Win enable | BG/Win tiles | BG tilemap | OBJ size | OBJ enable | BG/Win enable |
LY indicates the current horizontal line (0β153). Values 144β153 indicate VBlank.
When LY == LYC, the "LYC=LY" flag in STAT is set and (if enabled) a STAT interrupt is requested.
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|
| β | LYC int | Mode 2 int | Mode 1 int | Mode 0 int | LYC==LY | PPU mode | |
Specify the top-left coordinates of the visible 160Γ144 pixel area within the 256Γ256 BG map. The viewport wraps around.
WX=7, WY=0 places the Window at top-left, fully covering the background.
The window keeps an internal line counter similar to LY, but it only increments when the window is visible on that scanline. This means hiding the Window mid-frame inhibits the Y-position counter increment.
| 7β6 | 5β4 | 3β2 | 1β0 |
|---|---|---|---|
| Color for ID 3 | Color for ID 2 | Color for ID 1 | Color for ID 0 |
| Value | Color |
|---|---|
| 0 | White |
| 1 | Light gray |
| 2 | Dark gray |
| 3 | Black |
Work like BGP, except the lower two bits are ignored (color index 0 is transparent for OBJs).
CGB has palette RAM (CRAM) accessed through BCPS/BGPI ($FF68) and BCPD/BGPD ($FF69). Each color is stored as little-endian RGB555:
8 palettes Γ 4 colors Γ 2 bytes = 64 bytes of BG palette memory. OBJ palettes (OCPS/OBPI $FF6A, OCPD/OBPD $FF6B) work identically but are separate.
The image is drawn by the PPU progressively, directly to the screen. A frame consists of 154 scanlines; during the first 144, the screen is drawn top to bottom, left to right.
A "dot" = one 222 Hz (β 4.194 MHz) time unit. One frame = 70224 dots @ ~59.7 fps.
| Mode | Action | Duration | Accessible Memory |
|---|---|---|---|
| 2 | OAM Scan | 80 dots | VRAM, CGB palettes |
| 3 | Drawing pixels | 172β289 dots | None |
| 0 | HBlank | 376 β Mode 3 | VRAM, OAM, CGB palettes |
| 1 | VBlank | 4560 dots (10 lines) | VRAM, OAM, CGB palettes |
Mode 3's minimum length is 172 dots (160 pixels + 12 setup dots). Three things cause extra penalties:
SCX % 8 dots penalty at beginningException: an OBJ with OAM X=0 always incurs 11-dot penalty regardless of SCX.
There are two pixel FIFOs: one for background pixels and one for object pixels. Each FIFO can hold up to 16 pixels. Each pixel in the FIFO has four properties:
The fetcher has 5 steps (first four take 2 dots each, fifth attempts every dot):
When both FIFOs have pixels, one pixel is popped from each. If the OAM pixel is not transparent and LCDC.1 is enabled, the OAM pixel's priority is compared. If LCDC.0 is disabled, the BG pixel color is forced to 0 (DMG) or loses priority (CGB). Final color is looked up from the appropriate palette register.