Tiles and palettes

The basic units that make all graphics on the Mega Drive are tiles (the tiny images making up larger ones) and palettes (the colors used by the images).

I suggest to get an emulator that can show you the video memory contents (like BlastEm or Regen), since you won't see anything on screen yet but you can check if the graphics got loaded into memory.

What's a tile?

Every graphic you see on screen (sprites, foreground, background, etc.) is made out of tiles. A tile is an 8×8 image that can have up to 15 colors (and transparent pixels). Every possible color has a number associated with it (see palettes below).

A tile with the letter "A" in it may look like this:

Example: tile containing the letter A. It's an 8 by 8 grid, where each pixel has a number that represents the color they're painted with (in this example, 0 for background, 1 for yellow, 2 for a slightly brighter yellow).

Larger graphics are achieved by putting multiple tiles together:

Example: a 16 by 16 ball made out of multiple tiles. In this case four tiles are used: top left, top right, bottom left and bottom right.

In memory, tiles are stored as 32 bytes, where every 4 byte (a longword) represents a row, and every four bits is a pixel (which conveniently happens to be 1 hexadecimal digit per pixel). The letter "A" tile shown above looks like this in memory:

LetterA:
    dc.l  $00111000  ; 0 = background
    dc.l  $01101100  ; 1 = darker shade
    dc.l  $22000220  ; 2 = lighter shade
    dc.l  $22000220
    dc.l  $11111110
    dc.l  $11000110
    dc.l  $11000110
    dc.l  $00000000

What's a palette?

The limited amount of memory means you have to pick a set of colors to use. This is called a palette. The Mega Drive can show up to four palettes on screen, and each palette consists of 16 colors (however, the first color is always transparent, unless used as the background color). Tiles may use any of the four palettes.

An example generic palette:

Color 0 is transparent, colors 1 to 3 are skin shades, colors 4 to 6 are blue shades, colors 7 to 9 are red shades, colors 10 and 11 are yellow shades, color 12 is white, colors 13 and 14 are gray shades, color 15 is black.

In memory, palettes are 32 bytes each, where every color is two bytes. Colors are in BGR format (like RGB but blue comes first). Each of the blue, green and red components are one hexadecimal digit, though only even numbers are used (i.e. 02468ACE). So for example, $E00 is blue and $048 is brown.

The example palette looks like this (remember first color is always transparent):

Palette:
    dc.w  $000                  ; Transparent
    dc.w  $6AE,$26A,$026        ; Skin shades
    dc.w  $E00,$A00,$600        ; Blue shades
    dc.w  $00E,$00A,$006        ; Red shades
    dc.w  $0EE,$0AA             ; Yellow shades
    dc.w  $EEE,$AAA,$666,$000   ; Gray shades

Loading tiles into video RAM

To use on screen, tiles are loaded into "video RAM" (VRAM). This memory is 64KB large, having room for up to 2048 unique tiles (however, some of this space will be eaten up by tables, so assume more or less 75% of that amount). Of course you don't need to keep every tile your game ever uses here, only those you need to use right now.

We're going to deal with the VDP ports (the control port and the data port). Let's get some labels for their addresses:

VdpCtrl:  equ $C00004  ; VDP control port
VdpData:  equ $C00000  ; VDP data port

To copy a tile in memory, first you need to know its location in video memory. Since every tile is 32 bytes, it basically goes like this (note that tile numbers start at 0, not 1):

tile number × 32

Although this being an old CPU, since we're multiplying by a power of two (25 in this case) it's better to use bit shift:

tile number << 5

Now we need to write the tiles to video memory (we use the SetVramAddrReg macro from that page). Note that since 32 bytes is 8 longwords, we directly write all longwords each iteration of the loop and make our life easier (as well as making the loop faster).

; d0 = first tile number
; d1 = number of tiles
; a0 = pointer to tiles

LoadTiles:
    ; Tell VDP where to write
    lsl.w   #5, d0
    SetVramAddrReg d0
    
    ; Copy them to VRAM
    ; 1 iteration = 1 tile
    subq.w  #1, d1
    lea     (VdpData), a1
@Loop:
    move.l  (a0)+, (a1)
    move.l  (a0)+, (a1)
    move.l  (a0)+, (a1)
    move.l  (a0)+, (a1)
    move.l  (a0)+, (a1)
    move.l  (a0)+, (a1)
    move.l  (a0)+, (a1)
    move.l  (a0)+, (a1)
    dbf     d1, @Loop
    
    rts

Loading palettes into color RAM

Palettes get their own unique memory called "color RAM" (CRAM). The procedure to load them is similar however. The palette address is computed in a similar way (since they're also 32 bytes):

palette number << 5

So to copy the palettes we do the same thing as when loading tiles, except this time we use the SetCramAddrReg macro since the palettes are in CRAM:

; in d0 = palette number
; in a0 = pointer to palette

LoadPalette:
    ; Tell VDP where to write
    lsl.w #5, d0
    SetCramAddrReg d0
    
    ; Copy the whole palette
    lea     (VdpData), a1
    move.l  (a0)+, (a1)
    move.l  (a0)+, (a1)
    move.l  (a0)+, (a1)
    move.l  (a0)+, (a1)
    move.l  (a0)+, (a1)
    move.l  (a0)+, (a1)
    move.l  (a0)+, (a1)
    move.l  (a0)+, (a1)
    
    rts

Iwis says

You don't have to write entire palettes, you can write individual color entries too (remember 1 color = 2 bytes). Doing this is left as an exercise to the reader, but the above should give you enough information to figure out how it'd work.

Background color

The background color is the color that's shown on screen where nothing else is drawn (where there are no sprites and neither of the tilemaps is opaque).

The background color can be picked from any of the 64 colors. This is the only time where a "transparent" color can be used (i.e. the first color of a palette). You'll usually want to spend it as another color for the stage background, so you'll probably want to assign it to a stage-specific palette.

The background color is determined by one of the VDP registers (especifically VDP register $87xx). In case you don't already have it, here's a constant to go with it:

VDPREG_BGCOL: equ $8700

The background color needs the full color number (which includes its palette as well), which as you can guess it's based on its address in color RAM. Thankfully, it's easy to figure out:

(palette number × 16) + color number

Changing the background color is as simply as writing to that register:

    and.w   #$FF, d0          ; Remove excess bits
    or.w    #VDPREG_BGCOL, d0 ; Include the register number
    move.w  d0, (VdpCtrl)     ; Write the register

Even easier if the color is fixed, which is a common situation (e.g. this sets the background color to the color 0 of palette 3):

    move.w  #VDPREG_BGCOL|$30, (VdpCtrl)