Emulating Classic 2D Games in HD – Part 1

Many emulators for 3D consoles support rendering games at higher resolutions than supported by the original hardware. Some emulators for older 3D consoles even support replacing the texture maps used in games with higher-resolution versions. Emulators for 2D consoles, such as the venerable Super NES, have no such functionality. Frames are rendered at the original native resolution, and then may be upsampled to higher resolutions using a handful of simple filters.

Why can’t we play classic 2D games in HD? Couldn’t we take the same concept of replacing low-resolution texture maps and apply it to inserting high-resolution sprites or background tiles into 2D games?

I’ve seen comments from emulator creators that this just isn’t possible, but I hadn’t really seen a solid technical explanation. So I decided to find out the answer for myself. Just as a little teaser, though, I’ll say that I was pleasantly surprised by what I could achieve (my own poorly-drawn programmer art aside):



Super Mario Land, now at 4 times the resolution!

The Game Boy

In order to understand both the opportunities and challenges, we need to cover some background on sprite-based 2D graphics hardware. To keep things simple, both for my implementation and for this article, I’ve chosen to focus on the original Nintendo Game Boy.

The Game Boy has a 160×144 pixel LCD screen, and uses 4 grayscale colors (2-bit color). Rather than store the image to be displayed in a frame buffer, the hardware renders the image by compositing three kinds of elements: the background, sprites, and window. For simplicity, I’ll only talk about the first two.

Background Tile Map

The background is a 32×32 grid of tiles, where each tile is an 8×8 pixel image. Thus the background defines a conceptual 256×256 pixel image.. We can represent the background as a 32×32 array of tile indices (the tile map) along with an array of the corresponding tile images.

uint8_t tileMap[32][32];
TileImage tileImages[256];

Since the background is larger than the screen, two 8-bit hardware registers are used to scroll the background and select what part will be visible. As we scroll past the edge of the background, it repeats. A game can replace tile indices that are out of view to implement a continuously-scrolling map.

Sprites

The sprites are defined by a structure called the Object Attribute Map (OAM). The Game Boy supports 40 sprites, and each sprite has an X and Y position, flags for horizontal and vertical flip, and the index of the tile image to use:

struct Sprite {
    uint8_t x;
    uint8_t y;
    bool flipX;
    bool flipY;
    uint8_t tileIndex;
};
Sprite oam[40];

Tiles

Both the background and sprite use tiles to define their image data. Each tile is a 2-bit-per-pixel paletted image (yes, even though the Game Boy only supports 2-bit color, it uses paletted images). An 8×8 pixel tile takes up 8x8x2=128 bits (16 bytes). There are distinct color palettes for sprites and the background:

Color bgPalette[4];
Color spritePalette[4];

In the sprite palette, color index zero is special: it indicates a transparent pixel.

Take-Away

There are, of course, a lot more details to the Game Boy rendering hardware, and other 2D platforms will of course have different designs. Still, these basic concepts – tile maps, sprites, and paletted tile images – are shared by many different 2D consoles.

Tile Replacement

In order to render a Game Boy game at higher resolution, we must find a way to map the existing 8×8 tiles to high-res replacements. A simple approach would be to hash the contents of the tile image, and use the resulting hash as a key for a table of replacements. In the case of the Game Boy, tile images are only 16 bytes, so we can just use the tile data itself as our hash key. Of course we also need the replacement images; for now I’ll be using my own programmer art:

On the left we have an original sprite from Super Mario Land, and on the right we have my replacement. The use of paletted images complicates this process somewhat, but let’s ignore those details for right now.

A Simple Scheme

At first blush it seems like we could accomplish our goal with a relatively simple renderer. Whenever a frame needs to be drawn we can:

  • Iterate over the background tile map, and draw one textured quad per background tile.
  • Iterate over the sprites, and draw one textured quad per sprite.

We need a cache that maps tile images to texture maps, but that can easily be folded into our tile-replacement step.

Let’s try this scheme out on a few games (note, I’m only replacing a subset of tiles right now – even programmer art takes time to create):




Link's Awakening

Those results seem good (my terrible art skills aside). Suspiciously good, in fact, if I’m claiming that this problem merits a whole article. And yes, if we look a little harder we can find examples where the results of our simple renderer are not as satisfying:




Super Mario Land again, not looking as good




Castlevania II looks like a complete mess

What is going on in these images that makes them fail? In Part 2 of this series, we will discuss the timing of sprite hardware, and see how to resolve these graphical issues.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s