Using MAME's tilemap system
Many arcade systems have features for displaying tilemaps. Unlike a bitmap or pixelmap, which specifies an image in terms of a grid of pixels, a tilemap specifies an image in terms of a grid of tiles. Why use tilemaps? First of all, with all the tiles drawn in advance, the game can be simpler because it just needs to say where to place the tiles, rather than drawing each pixel. Second, you can save storage space by reusing tiles.
Some games use fixed, ROM-based tilemaps, where the arrangement of tiles is stored in ROM. One example of this is background in some scrolling vertical shooters: the graphics ROM contains reusable terrain tiles, and the tilemap ROM contains the tile codes and attributes. Some games use dynamic, RAM-based tilemaps, where the game stores the tile codes and attributes in RAM.
This page gives a general overview of how you can use MAME's tilemap system. For more detailed information, look at the comments in src/emu/tilemap.h. For more examples, look through src/mame/video for games with tilemaps.
Core Concepts
A tilemap is a 2D grid of rectangular tiles. Each tile has its own characteristics that determine how it is rendered. When creating a tilemap, you specify the number of rows and columns, and the size of the tiles in pixels.
A tile is a single rectangular element within a tilemap. Tiles may be any size, but all tiles in a tilemap must be the same size as each other.
A 4-bit category can be specified for each tile. Each category can be rendered independently.
Depending on the pen, each pixel will be assigned to a layer. There are three layers, but in general, only layer 0 is used. If a pixel doesn't belong to a layer, it will be considered transparent.
Creating your Tilemaps
In MAME, a tilemaps are created by calling tilemap_create. You usually create tilemaps when initialising the video hardware emulation. Before you can create a tilemap, you need to answer two questions: how big are the tiles, and how big is the tilemap? The answer to the first question is usually obvious: it will be the size of the graphics elements used by the hardware. The answer to the second question may seem obvious, too: wouldn't you just make it as big as what the hardware deals with? Usually yes, but some games use absolutely massive ROM-based tilemaps, and making MAME render the whole thing just wastes a lot of memory. In these cases, it's better to your tilemap smaller than what the hardware sees, and invalidate it when the game scrolls out of range.
tilemap *tilemap_create(tile_get_info, mapper, type, tilewidth, tileheight, cols, rows)
Pass a pointer to your tile info callback for tile_get_info (this is discussed later).
Pass a pointer to a function that converts a row and column position to a tile index as mapper. You can write your own if you need to, but usually you can just use one of the predefined tilemap mappers:
- tilemap_scan_rows scans rows from left to right consecutively from top to bottom (so in a 2x2 tilemap, 0 is top left, 1 is top right, 2 is bottom left and 3 is bottom right)
- tilemap_scan_rows_flip_x scans rows from right to left consecutively from top to bottom (so in a 2x2 tilemap, 0 is top right, 1 is top left, 2 is bottom right and 3 is bottom left)
- tilemap_scan_rows_flip_y scans rows from left to right consecutively from bottom to top (so in a 2x2 tilemap, 0 is bottom left, 1 is bottom right, 2 is top left and 3 is top right)
- tilemap_scan_rows_flip_xy scans rows from right to left consecutively from bottom to top (so in a 2x2 tilemap, 0 is bottom right, 1 is bottom left, 2 is top right and 3 is top left)
- tilemap_scan_cols scans columns from top to bottom consecutively from left to right (so in a 2x2 tilemap, 0 is top left, 1 is bottom left, 2 is top right and 3 is bottom right)
- tilemap_scan_cols_flip_x scans columns from top to bottom consecutively from right to left (so in a 2x2 tilemap, 0 is top right, 1 is bottom right, 2 is top left and 3 is bottom left)
- tilemap_scan_cols_flip_y scans columns from bottom to top consecutively from left to right (so in a 2x2 tilemap, 0 is bottom left, 1 is top left, 2 is bottom right and 3 is top right)
- tilemap_scan_cols_flip_xy scans columns from bottom to top consecutively from right to left (so in a 2x2 tilemap, 0 is bottom right, 1 is top right, 2 is bottom left and 3 is top left)
type controls how pixels are assigned to layers. Currently, the only recommended value TILEMAP_TYPE_PEN, which means the raw pen value is looked up in a table to determine which layer a pixel belongs to.
tilewidth and tileheight are the width and height of the tiles in pixels.
cols and rows are the number of rows and columns in the tilemap, respectively. The size of the tilemap in pixels can be found by multiplying the number of columns by the tile width and the number of rows by the tile height.
Tilemaps are tracked by the MAME core, so they are automatically saved and freed. There is no corresponding call to manually dispose of a tilemap.
The Tile Info Callback
When MAME wants to know what to draw at a particular location in your tilemap, it calls the tile info callback function that you supplied when you created the tilemap. This function takes the tile index as a parameter, and sets the tile information. You can declare tile info callbacks with the TILE_GET_INFO macro. The two most important parameters are tile_index (the tile index), and param (a user-specified value set with tilemap_set_user_data).
You usually set the tile information with the SET_TILE_INFO macro:
SET_TILE_INFO(gfx, code, color, flags)
gfx is the graphics bank to take the tile from, code is the graphics code and color is the color code. You can combine the following values for the flags:
- TILE_FLIPX to flip the tile horizontally
- TILE_FLIPY to flip the tile vertically
- TILE_4BPP if the tile data is packed four bits per pixel format
- TILE_FORCE_LAYER0, TILE_FORCE_LAYER1 or TILE_FORCE_LAYER2 to force all pixels in the tile to be assigned to a particular layer (TILE_FORCE_LAYER0 is often used to force a tile to be opaque)
Setting Up Transparency
Sorry, haven't written this yet.
Manipulating Tilemaps
MAME can do a few tricks with tilemaps with no work on your part. The most common tilemap manipulation functions are detailed here.
tilemap_set_user_data(tmap, user_data)
Sets the value passed to a tilemap's tile info callback as param. tmap is the tilemap pointer and user_data is the value (void pointer).
tilemap_set_palette_offset(tmap, offset)
Apply a palette offset to the color values for a tilemap, to be added to each pixel value before looking up the palette.. tmap is the tilemap pointer and offset is the palette offset.
tilemap_set_enable(tmap, enable)
Enable or disable drawing of a tilemap. When a tilemap is disabled, requests to draw it are ignored. tmap is the tilemap pointer; pass non-zero for enable to enable the tilemap, or zero to disable it.
tilemap_set_flip(tmap, attributes)
Set flipping for a tilemap, or for all tilemaps. tmap is the tilemap pointer, or ALL_TILEMAPS to apply the transform to all tilemaps; attributes should be a zero, TILEMAP_FLIPX, TILEMAP_FLIPY or the logical sum of these values.
Scrolling
Sorry, I haven't got to writing this yet.
When Things Change
MAME caches tilemaps aggressively to avoid unnecessary CPU load, so you need to let it know when parts of a tilemap need to be redrawn. Examples of when this may happen are when the game writes to tilemap memory for a RAM-based tilemap, or when the tilemap scrolls out of range when you're only rendering part of a large ROM-based tilemap. There are two functions that can be used: tilemap_mark_tile_dirty marks a single tile as needing to be redrawn, and tilemap_mark_all_tiles_dirty marks entire tilemaps as needing to be redrawn.
tilemap_mark_tile_dirty(tmap, memory_index)
Marks a single tile as needing to be redrawn. tmap is the tilemap pointer and memory_index is the index of the tile to be redrawn.
tilemap_mark_all_tiles_dirty(tmap)
Marks entire tilemaps as needing to be redrawn. tmap the tilemap pointer, or ALL_TILEMAPS to mark all tilemaps as needing to be redrawn.
Drawing Tilemaps
You can draw a tilemap into a bitmap any time you want. In general, though, you do this in your VIDEO_UPDATE callback, to get graphics onto the emulated display. The most common function used for this is tilemap_draw:
tilemap_draw(dest, cliprect, tmap, flags, priority)
dest is the destination bitmap. Usually, you pass the bitmap argument you received in your VIDEO_UPDATE callback.
cliprect is the clipping rectangle. Drawing will be restricted to this area. You usually pass the cliprect argument that you received in your VIDEO_UPDATE callback.
tmap is the tilemap pointer.
flags is a combination of the following values:
- TILEMAP_DRAW_CATEGORY( x ) specifies the category of tiles to draw (default 0)
- TILEMAP_DRAW_LAYER0 to draw only layer 0
- TILEMAP_DRAW_LAYER1 to draw only layer 1
- TILEMAP_DRAW_LAYER2 to draw only layer 2
- TILEMAP_DRAW_OPAQUE to draw everything, even if it's normally transparent
- TILEMAP_DRAW_ALPHA to draw with alpha blending
priority is the priority value to store in the priority bitmap for pixels that are not transparent.
Advanced Tilemap Drawing
Haven't thought about this section yet, either. It should cover priority masks, ROZ, etc.
Examples
Will get to this when time permits.