The Nintendo Reverse Engeneering Project

 

Day 4

Added some source. Get the DEMOS

Today we tackle the world of tile based background graphics. This is not really a huge topic so I will also cover a few other interesting things. This is what will be discussed today:

What are tile modes for?
Memory layout of tile modes.
Background scrolling, zooming, and rotation.
Transparency, Mosaic, fading and the BLDMOD register.

What are tile modes for?
If you have not read day one of this tutorial I recommend doing so now as it covers much of the basic tile mode information. Tile modes have been around since the first days of consolees and are the main reason such smooth graphics can be achieved on limited hardware. Basically a tile mode can be broken into two parts. First you have your tiles. These are just small bitmaps that you will use to build a larger much more complex image. In the GBA case these tiles are 8x8 and can be either 16-color or 256-color bitmaps. The next part of tile modes is the map. The map is what you use to tell the GBA which tiles to place were. The main advantage of this is that you can have a huge world built of a small number of tiles that takes up a very small amount of memory and also takes only a short time to update each frame because all you have to do is update the map. Tile modes are used for almost all GBA games even ones like F-zero and Mario cart.

The GBA supports many features that make tile modes even more useful such as:

-Hardware background rotation and scaling.
-Transparency
-Up to four different backgrounds.
-Flipped tiles that allow you to use one tile bitmap to represent multiple orientations.

Memory layout of tile modes.
At first glance the way video memory is laid out for tile modes may seem a bit complex but it is not really that bad. In fact with a few simple functions it is quite intuitive and very flexible. First we will talk about were you put your tile data and map data and then we will talk a bit about how that data is formatted.

Tile data can be placed anywhere in VRAM as long as it is on a 16KB boundary (from 0x6000000-0x600FFFF). This gives you four places to put tile data. Backgrounds can share tile data or use their own separate tile sets. These 16k blocks are often referred to as "Character base blocks". Which character base block you use is completely up to you.

Map data works very similar to tile data except that since map data is usually much smaller than tile data, the VRAM is broken into smaller chunks. Maps can be placed on any 2KB boundary in VRAM and are referred to as "Screen base blocks". Again were you put your map data is completely up to you.

VRAM Address VRAM
Char Base Block 3 0x600F800 Screen Base Block 31
0x600F000 Screen Base Block 30
0x600E800 Screen Base Block 29
0x600E000 Screen Base Block 28
0x600D800 Screen Base Block 27
0x600D000 Screen Base Block 26
0x600C800 Screen Base Block 25
0x600C000 Screen Base Block 24
Char Base Block 2 0x600B800 Screen Base Block 23
0x600B000 Screen Base Block 22
0x600A800 Screen Base Block 21
0x600A000 Screen Base Block 20
0x6009800 Screen Base Block 19
0x6009000 Screen Base Block 18
0x6008800 Screen Base Block 17
0x6008000 Screen Base Block 16
Char Base Block 1 0x6007800 Screen Base Block 15
0x6007000 Screen Base Block 14
0x6006800 Screen Base Block 13
0x6006000 Screen Base Block 12
0x6005800 Screen Base Block 11
0x6005000 Screen Base Block 10
0x6004800 Screen Base Block 9
0x6004000 Screen Base Block 8
Char Base Block 0 0x6003800 Screen Base Block 7
0x6003000 Screen Base Block 6
0x6002800 Screen Base Block 5
0x6002000 Screen Base Block 4
0x6001800 Screen Base Block 3
0x6001000 Screen Base Block 2
0x6000800 Screen Base Block 1
0x6000000 Screen Base Block 0

 

TILE DATA.

Tile data is stored in much the same way as 1D sprite data in that the first 64bytes are the first tile the next 64bytes the second tile and so forth (this is for 256-color sprites, it only takes 32bytes to hold 16-color tiles). For the purpose of this tutorial I am just going to use my sprite stripper to put my tile data in the proper format since it is exactly the same as sprite data.

Map data is formatted a bit differently. For rotation backgrounds it is exactly like you would think. 1 byte for each tile and all it is a tile number. This has a few drawbacks though. First you are limited to 256 unique tiles, second you cannot have flipped tiles and last you cannot use 16-color tiles.

For text backgrounds the map data is a bit different. Each entry is a 16-bit number that gives a bit more information about each tile besides just the tile number. Here is the layout of a text background map entry:

Text background tile entry:

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Palette number VF HF Tile Number


bits 0-9: Tile number
bit 10: Horizontal flip flag
bit 11: Vertical flip flag
bits 12-15: 16-color palette number. Ignored if it is a 256-color background

Now to control the behavior of your background we have a few registers that need to be set properly. These are the background control registers and there is one for each background.

REG_BGxCNT:

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Size W Screen Base Block C M Unused Char Base Block Priority

bits 0-1: Control the priority(0-3)
bits 2-3: Select the character base block(0-3)
bits 4-5: unused
bit 6: mosaic enable
bit 7: If 1 then 256-color mode else 16-color mode
bits 8-12: select the screen base block (0-31)
bit 13: If enabled causes the screen to wrap around else once you get to the edge of the background it becomes transparent. (Only effects rotation backgrounds)
bits 14-15: Screen size (see table below)

Size 0 1 2 3
Text BG 256x256 512x256 256x512 512x512
Rotation BG 128x128 256x256 512x512 1024x1024

To simplify the creation and control of backgrounds it is easiest to use a structure (probably best to use a class actually but since I am, for now, avoiding c++ we will stick to a struct). Each size of map has a slightly different memory layout. For rotation backgrounds it is just a linear array that can be indexed much like the video memory in a bit map mode. For instance to place a tile at the tile location 25,50 (tile_x, tile_y) in a size 2 rotation background you can use the following:

MapPointer[tile_x + tile_y * 64] = TileNumber; //a size two map is 512x512.


This is as long as your MapPointer points to the correct screen base block that you
specified with BGxCNT register. Unfortunately text backgrounds are not quite as simple.
Size 0 (256x256) and size 2(256x512) still work as linear arrays but size 1 and 3 are
broken into separate blocks. To calculate the offset into your map data for a size of
512x256 you need to treat it like it is two separate 256x256 backgrounds placed one next
to the other. A simple function for that would be:

//MapData is your linear map data from your map maker. MapPointer is the map in vram

int OFFSET = 32x32; //offset to get to the right hand part of the map

for(tile_y == 0; tile_y < 32; tile_y++)
{
for(tile_x == 0; tile_x < 64; tile_x++)
{

if(tile_x < 32) //It is in the left hand block

MapPointer[tile_x + tile_y * 32] == MapData[tile_x + tile_y * 64]

else //it is in the right hand block so add the offset of the first block and subtract 32 from tile_x

MapPointer[ (tile_x-32) + tile_y * 32 + OFFSET] == MapData[tile_x + tile_y * 64];

}
}

 

That is because 256*256 is the size of the first screen and the second half memory address starts there. This is not a very efficient way to copy the data in but it works. 512x512 is a bit harder but works much the same. It is broken into 4 separate blocks.

if(tile_y < 32) //top half so same as 512x256 background
{
    if(tile_x < 32) //It is in the left hand block
        MapData[tile_x + tile_y * 32];
    else //it is in the right hand block so add the offset of the first block and subtract 256 from tile_x
        MapData[(tile_x – 32) + tile_y * 32+ 32*32];
}

else //Bottom half there for we need to subtract 256 from y and add the offset of the first two blocks
{
    if(tile_x < 32) //It is in the left hand bottom block
        MapData[tile_x + (tile_y-32) * 32 + 2*32*32];
    else //it is in the right hand block so add the offset of the first 3 blocks and subtract 256
        MapData[(tile_x – 32) + (tile_y-32) * 32 + 3*32*32];
}

This would all make more sense if I just drew a picture but I do not have time at the moment. If enough people complain about not understanding I will add a picture and try to beef up the explanation a bit. If you look in the header file bellow you will find the structure I use to enable and define backgrounds along with the function I use to initialize them.

///////Background.h////////
#ifndef BACKGROUND_H
#define BACKGROUND_H

///BGCNT defines ///
#define BG_MOSAIC_ENABLE 0x40
#definee BG_COLOR_256 0x80
#define BG_COLOR_16 0x0

#define TEXTBG_SIZE_256x256 0x0
#define TEXTBG_SIZE_256x512 0x8000
#define TEXTBG_SIZE_512x256 0x4000
#define TEXTBG_SIZE_512x512 0xC000

#define ROTBG_SIZE_128x128 0x0
#define ROTBG_SIZE_256x256 0x4000
#define ROTBG_SIZE_512x512 0x8000
#define ROTBG_SIZE_1024x1024 0xC000

#define WRAPAROUND 0x2000

// the following simplify the determining of base blocks and will make more sense when you look at the enableBackground function.

#define CharBaseBlock(n) (((n)*0x4000)+0x6000000) //16k * number + start of VRAM = address of character base block
#define ScreenBaseBlock(n) (((n)*0x800)+0x6000000) //2k * number + start of VRAM = address of screen base block
#define CHAR_SHIFT 2 //used to shift the number over to the right place in REG_BGxCNT

#define SCREEN_SHIFT 8

////background struct

typedef struct Bg
{
    u16* tileData; //after call to EnableBackground() tiledata and mapData will point to the
    u16* mapData; //corect VRAM location.
    u8 mosiac; //mosaic enable
    u8 colorMode; //256 or 16 color
    u8 number; //background number (0-3)
    u16 size; //size of the background
    u8 charBaseBlock; //0-3
    u8 screenBaseBlock; //0-31
    u8 wraparound; //wraparound flag
    s16 x_scroll,y_scroll; //the horizontal and vertical offsets
    s32 DX,DY; //scroll for rotation screens
    s16 PA,PB,PC,PD; //hold rotation attributes for the background.
}Bg;

#ifndef BACKGROUND_C

extern void EnableBackground(Bg* bg);
extern void RotateBackground(Bg* bg, int angle, int center_x, int center_y, FIXED zoom);
extern void UpdateBackground(Bg* bg);

#endif
#endif

 

/////background.c/////

#define BACKGROUND_C
#include "gba.h"
#include "backgrounds.h"
#include "screenmode.h"

extern FIXED COS[360];
extern FIXED SIN[360];

void EnableBackground(Bg* bg)
{
    u16 temp; //used to save typing

    bg->tileData = (u16*)CharBaseBlock(bg->charBaseBlock); //compute proper memory address
    bg->mapData = (u16*)ScreenBaseBlock(bg->screenBaseBlock);//same

    //Create my BGxCNT register setting and put in temp to save typing

    temp = bg->size | (bg->charBaseBlock<<CHAR_SHIFT) | (bg->screenBaseBlock<<SCREEN_SHIFT) | bg->colorMode |     bg->mosiac | bg->wraparound;

    switch(bg->number)
    {
        case 0:
        {
            REG_BG0CNT = temp; //set the BGxCNT register to the values we just determined
            REG_DISPCNT |= BG0_ENABLE; //enable the correct background
        }break;

        case 1:
        {
            REG_BG1CNT = temp;
            REG_DISPCNT |= BG1_ENABLE;
        }break;

        case 2:
        {
            REG_BG2CNT = temp;
            REG_DISPCNT |= BG2_ENABLE;
        }break;

        case 3:
        {
            REG_BG3CNT = temp;
            REG_DISPCNT |= BG3_ENABLE;
        }break;

        default:break;

        }
}

Care must be taken when choosing screen base blacks and character base blocks so that your tile and map data do not overlap. And that is it for Setting up the background. There is still the matter of how to generate the tile and map data but I will try to help out there. First I downloaded a copy of GBA map editor beta 4 from www.gbadev.org. This is a very simple map editor that I thought would be good for demonstration purposes. Next I made some tiles in PSP and saved them as a bitmap.

I then loaded the tiles into the map editor and drew my background with the tiles.

Since the map editor does not support the exporting of the tiles into c code I just used my pcx2sprite program on it and it split it into 8x8 tiles and a palette just like I needed (yes I had to resave the bmp as a pcx file in order for this to work). I then exported my map in c code from the map editor and I was good to go. Of course there are many other map editors out there that are much more powerful and have many more features but this one served its purpose for this tutorial and was designed for the GBA in particular. Also you could hand code your maps after you create your tiles but that takes a lot of time. Now we will move on to scrolling and rotation/zooming.

Background scrolling rotation and zooming.
Scrolling text backgrounds is very simple and just requires the updating of two registers, REG_BGxHOFS(horizontal) and REG_BGxVOFS(vertical) (were x is the background number). You simply put the amount you wish to scroll by into these registers and walla! you have a scrolling background. The only caveat to this is that you can not read these registers which means that you must have a temporary copy of the register in a variable that you can read and then once per frame copy that variable to the register.

Rotation backgrounds are almost as simple but instead of using REG_BGxHOFS and REG_BGxVOFS you must use REG_BGxX and REG_BGxY. The reason for this is that you need much finer than integer control over scrolling when you rotate backgrounds. REG_BGxX and REG_BGxY are fixed point numbers with 8 bit fractional parts. They are also 32 bits as opposed to the 16bit horizontal and vertical offset registers.

Rotation and zooming of backgrounds is very similar to sprite rotation. In fact the only difference is that you can change the center of rotation for backgrounds which complicates things a bit. The easiest way to show you how to rotate and zoom is to just show you the function, as I am not about to go into the math.

void RotateBackground(Bg* bg, int angle,int center_x, int center_y, fixed zoom)
{
    center_y = (center_y * zoom)>>8;
    center_x = (center_x * zoom)>>8;

    bg->DX = (bg->x_scroll-center_y*SIN[angle]-center_x*COS[angle]);
    bg->DY = (bg->y_scroll-center_y*COS[angle]+center_x*SIN[angle]);

    bg->PA = (COS[angle]*zoom)>>8; //cos&sin are LUTs that are .8 fixed numbers
    bg->PB = (SIN[angle]*zoom)>>8; //zoom is also fixed
    bg->PC = (-SIN[angle]*zoom)>>8;
    bg->PD = (COS[angle]*zoom)>>8;
}

center_x and center_y are the centers for rotation with 0,0 being the upper left hand pixel of the screen. They are first adjusted for the effect of zooming in and out. DX and DY are the scroll values for the rotation background. They are also adjusted for zooming and the angle of rotation. Then the proper rotation values are placed in PA-PD. The math for all this is a bit much but basically it just applies a 2D translation matrix on the scroll values so the scroll is in the proper direction for the angle of rotation and then it corrects for the zoom and rotates the background. Zoom is a fixed point number with 8 bits of fraction. As zoom increases the apparent closeness of the background also increases.

That is really all there is to the rotation and scaling of the backgrounds. Once per frame just call a function like UpdateBackground that will store the values from the Bg struct into the proper registers:

//////Updates a background//////

void UpdateBackground(Bg* bg)
{
    switch(bg->number)
    {
    case 0:
        REG_BG0HOFS = bg->x_scroll;
        REG_BG0VOFS = bg->y_scroll;
    break;

    case 1:
        REG_BG1HOFS = bg->x_scroll;
        REG_BG1VOFS = bg->y_scroll;
    break;

    case 2:
    if(!(REG_DISPCNT & MODE_0)) //mode 0 is the only mode in which bg2 and 3 are text backgrounds
    {
        REG_BG2X = bg->DX;
        REG_BG2Y = bg->DX;

        REG_BG2PA = bg->PA;
        REG_BG2PB = bg->PB;
        REG_BG2PC = bg->PC;
        REG_BG2PD = bg->PD;
    }

    else //it is a text background
    {
        REG_BG2HOFS = bg->x_scroll;
        REG_BG2VOFS = bg->y_scroll;
    }break;

    case 3:
    if(!(REG_DISPCNT & MODE_0))
    {
        REG_BG3X = bg->DX;
        REG_BG3Y = bg->DX;

        REG_BG3PA = bg->PA;
        REG_BG3PB = bg->PB;
        REG_BG3PC = bg->PC;
        REG_BG3PD = bg->PD;
    }

    else //it is a text background
    {
        REG_BG3HOFS = bg->x_scroll;
        REG_BG3VOFS = bg->y_scroll;
    }break;

    default: break;
    }
}

This will prevent shearing of the background due to updates while the screen is being drawn as long as you call it immediately following WaitForVblank() or during your vblank interrupt.

Transparency, fading, mosaic and the BLDMOD register.
This section will also cover sprites as it is the same code for background effects and sprite effects. First we will start with a description of mosaic as it is the simplest effect to achieve. To enable mosaic all you need to do is set the correct bit in BGxCNT or attribute0 if it is a sprite. Then, using a single register, you can control the mosaic for all backgrounds and sprites. What mosaic does is similar to a zoom in. Basically it just samples the bitmap and repeats the pixels in a blocky fashion. It is kind of hard to describe so I will do a little demo to demonstrate. The demo loads a background and a sprite and by pressing up and down you can vary the background mosaic, left and right to vary the sprite mosaic. Get it here.

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
OBJ Vertical Mosaic OBJ Horizontal Mosaic BG Vertical Mosaic BG Horizontal Mosaic


As you can see this allows for 16 levels of mosaic for each dimension, and a separate control for objects and the backgrounds.

A few quick macros are all we need to work with mosaic.

/////mosaic.h

#define MOS_BG_HOR(n) (n)
#define MOS_BG_VER(n) (n<<4)
#define MOS_OBJ_HOR(n) (n<<8)
#define MOS_OBJ_VER(n) (n<<12)

#define SetMosaic(bh,bv,oh,ov) ((bh)+(bv<<4)+(oh<<8)+(ov<<12))

//usage REG_MOSAIC = SetMosaic(4,4,0,0); for a mosaic of 4x4 on the background

Transparency and fading are a bit more complex but still not that bad.

There are three registers of concern for controlling transparency and fading and the first and most important (not to mention most difficult to use) register is REG_BLDMOD.

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
    BD OBJ BG3 BG2 BG1 BG0 mode BD OBJ BG3 BG2 BG1 BG0

bits 0-5 and 8-13 are the target areas for the effects processing. Basically if you want Bg 0 to show through bg1 then you set the first target (bits 0-5) to bg0 and the second target to bg1. If you want objects to appear through backgrounds then select first target as the background and second as the objects. The BD is the back plain, which is the area you see behind all the backgrounds and is just a solid screen of color zero. There are a few caveats to this and they have to do with the two bits in the middle.

Bits 6 and 7 are the mode control for alpha blending effects. There are four different modes:

Mode 2: is fade in mode. This means that the elements selected by the 1st target will be made brighter by a factor equal to the value stored in REG_COLY. This can be from 0-16 for 17 levels of fading.
Mode 3: (fade out) is the same except intensity is reduced by the factor stored in REG_COLY.
Mode 0: No alpha blending is preformed unless there are transparent sprites above a 2nd target screen.
Mode 1: Normal transparency is executed. In other words background transparency is implemented. If you set OBJ flag in the 1st target screen to 1 then all sprites will be transparent else only the ones that have there transparency flag set will be transparent.

For example if you want background 2 to be transparent above background 1 then you would set 1st target screen flag for bg1 to 1, and 2nd target screen for bg2 to 1. Then set mode to 01. Not too bad but still a little confusing. I still have not mentioned how to control the level of transparency.

There is another register called REG_COLV that has too parts:

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
      EVB       EVA

The first part (EVA) is the control for the 1st target screen and the second part (EVB) is the control for the 2nd target screen. There is a simple equation that can be used to help explain the effect of these two elements:

final color = 1/EVA * 1st screen + 1/EVB * 2nd screen.

This may or may not make sense but it is late and it makes sense to me so that will have to do. I recommend just setting the EVA to 8 and varying the EVB to control the level of transparency. This has given me the most predictable results but, as always, it is up to you. By the way EVA, EVB, and EVY(REG_COLY) are only valid from 0-16 (that is 17 different levels of intensity/transparency).

And that is pretty much it for day 4. Hope it was as fun for you as it was for me. I am actually working on a packman demo that will demonstrate all these cool little features for background stuff. All this code has been pulled from that demo and verified to be working but none of it really thoroughly tested. I probably should wait until I have written a few more demos before posting this but it may be several days before I get around to it and I know a few of you are anxious. This day will probably have many errors because of my rush and lack of time lately so please feel free to correct me on any mistakes you find.If you Want to see it on hardware then you need the MBV2 cable or the Flash advance linker. There is a review in my tools section Or you can just click the link below and go get one :)

EZF Advance 256Mb 468x60

 

Day 3 | Day 5