Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
64 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

Contents
 Getting the Compiler
 Working

 Screen modes
 Drawing a
 pre-made image


 Printable version
 Discuss this article

The Series
 Volume I
 Volume II
 Volume III

Screen Modes, Among Other Things

As I said earlier, the GBA has 6 different screen modes to choose between. From the moment I said that, I bet you were wondering, "How do I change between them?" The answer lies in the REG_DISPCNT that is defined in gba.h.

However, you may be wondering where this elusive "gba.h" is. Well, the answer is quite simple: you don't have it. It's rather long, and its contents are going to be divulged throughout the course of all these articles, so I'm going to do what I don't like doing and just throw the entire thing at you. Get it here: gba.h

This wonderful file has pointers to sprite data, input data, screen data, and just about every piece of data you're ever going to mess with. You'll need it in every GBA project you create.

REG_DISPCNT is a 16 bit register at memory address 0x4000000. Each bit within this register controls something of the GBA. Here's a description:

Bits 0-2: Control the screen mode and can be any value between 0 and 5
Bit 3: Automatically set on a GBA if a GB or GBC cartridge is put in (ignore it).
Bit 4: Used for double buffering in screen modes 4 and 5.
Bit 5: Allows OAM to be set during horizontal blank (I'll explain further when we get to sprites).
Bit 6: Determines if we are using 1D or 2D mapping for sprites. Again, I'll give more information when that bridge must be crossed.
Bit 7: Puts the screen into a forced blank.
Bits 8-11: Enable backgrounds 0-4 (more on this when we get to tile maps).
Bit 12: Enables hardware rendered sprites.
Bits 13-15:   Enables window displays (I don't have much info on these).

Handy for one 16 bit number, 'eh? Personally I probably would have rather remembered a bunch of different variable names than one variable name and the specifics on every bit in it, but oh well. Now that we know what each bit stands for, we need a way to change the values. This can be done by bit masking (the | operator) a series of numbers into the register. But who wants to remember a bunch of numbers when we can create a header file and use variable names instead? Let's do that.

//screenmodes.h
#ifndef __SCREENMODES__
#define __SCREENMODES__

#define SCREENMODE0    0x0           //Enable screen mode 0
#define SCREENMODE1    0x1           //Enable screen mode 1
#define SCREENMODE2    0x2           //Enable screen mode 2
#define SCREENMODE3    0x3           //Enable screen mode 3
#define SCREENMODE4    0x4           //Enable screen mode 4
#define SCREENMODE5    0x5           //Enable screen mode 5
#define BACKBUFFER     0x10          //Determine backbuffer
#define HBLANKOAM      0x20          //Update OAM during HBlank?
#define OBJMAP2D       0x0           //2D object (sprite) mapping
#define OBJMAP1D       0x40          //1D object(sprite) mapping
#define FORCEBLANK     0x80          //Force a blank
#define BG0ENABLE      0x100         //Enable background 0
#define BG1ENABLE      0x200         //Enable background 1
#define BG2ENABLE      0x400         //Enable background 2
#define BG3ENABLE      0x800         //Enable background 3
#define OBJENABLE      0x1000        //Enable sprites
#define WIN1ENABLE     0x2000        //Enable window 1
#define WIN2ENABLE     0x4000        //Enable window 2
#define WINOBJENABLE   0x8000        //Enable object window

#define SetMode(mode)    (REG_DISPCNT = mode)

#endif

There we have our header file. Now if we wanted to set the screen mode to screen mode 3 and support sprites, we'd simply include this file in our main source file and say:

SetMode( SCREENMODE3 | OBJENABLE );

Naturally, this header file is terribly important, and I recommend including it in all your projects.

Drawing to the Screen

Drawing to the screen is remarkably easy now that we have the screen mode set up. The video memory, located at 0x6000000, is simply a linear array of numbers indicating color. Thus, all we have to do to put a pixel on the screen is write to the correct offset off this area of memory. What's more, we already have a #define in our gba.h telling us where the video memory is located (called VideoBuffer). One minor thing you should take note of is that in the 16bit color modes, colors are stored in the blue, green, red color format, so you'll want a macro to take RGB values and convert them to the appropriate 16bit number. Furthermore, we only actually use 15 bytes. Also, when you set the screen mode to a pixel mode, Background 2 must be turned on because that's where all the pixels are drawn.

Here's an example:

#include "gba.h"
#include "screenmodes.h"

u16* theVideoBuffer = (u16*)VideoBuffer;
#define RGB(r,g,b) (r+(g<<5)+(b<<10))    //Macro to build a color from its parts

int main()
{
  SetMode( SCREENMODE3 | BG2ENABLE );    //Set screen mode
  int x = 10, y = 10;                    //Pixel location

  theVideoBuffer[ x + y * 240 ] = RGB( 31, 31, 31 ); //Plot our pixel
  return 0;
}

All this example does is plot a white pixel at screen position 10,10. Very easy.

Mode 5 is nearly identical. Simply replace the SCREENMODE3 with SCREENMODE5 in the SetMode macro, and instead of theVideoBuffer[ x + y * 240 ] say theVideoBuffer[ x + y * 160 ] because the screen is only 160 pixels in width. If you want to use the second buffer for Mode 5, read on, because I'll describe double buffering for Mode 4 in just a bit. Double buffering is identical in both screen modes - the method of drawing to the buffer is slightly changed, though.

Mode 4 is slightly more complex since it is only an 8bit color mode and it utilizes the backbuffer. Naturally, change the SCREENMODE3 to SCREENMODE4 in SetMode (this should be self-explanatory, and I'm not going to note the change anymore). However, now we need a few more things.

Before we do any drawing with Mode 4, we need a palette. The palette is stored at 0x5000000 and is simply 256 16bit numbers representing all the possible colors. The memory is pointed to in gba.h, and the pointer is called BGPaletteMem.

u16* theScreenPalette = (u16*)BGPaletteMem;

To put a color in the palette we simply say

theScreenPalette[ position ] = RGB( red, green, blue );

Now mind you, I don't recommend you hard code all the palette entries. There are programs on the internet to get the palette from files and put it in an easy to use format. Furthermore, the color at position 0 should always be black (0,0,0) - this will be your transparent color.

Now we need to point to the area of memory where the backbuffer is stored (0x600A000). Guess what - this pointer is already in our gba.h. Behold, BackBuffer!

u16* theBackBuffer = (u16*)BackBuffer;

Now always write to the backbuffer instead of the video buffer. Then you can call the Flip function to switch between the two. Basically, the Flip function changes which part of video memory is being viewed and changes the pointers around. Thus, you will always be viewing the buffer in the front and always be drawing to the buffer in the back. Here's the function:

void Flip()
{
  if (REG_DISPCNT & BACKBUFFER)
  {
    REG_DISPCNT &= ~BACKBUFFER;
    theVideoBuffer = theBackBuffer;
  }
  else
  {
    REG_DISPCNT |= BACKBUFFER;
    theVideoBuffer = theBackBuffer;
  }
}

However, you only want to call this function one time in your main loop. That time is during the vertical blank. The vertical blank is a brief period when the GBA hardware isn't drawing anything. If you draw any other time, there is a possibility that images will be choppy and distorted because you're writing data at the same time the drawing is taking place. This effect is known as "Shearing."

Luckily, the function to wait for the vertical blank is very simple. All it does is get a pointer to the area that stores the position of the vertical drawing. Once this counter reaches 160 (the y resolution, and thus the end of the screen), the vertical blank is occurring. Nothing should happen until that vertical blank happens, so we trap ourselves in a loop. Note that in Mode 5, the y resolution is only 128, so you'll want to change your wait accordingly.

void WaitForVblank()
{
  #define ScanlineCounter *(volatile u16*)0x4000006;

  while(ScanlineCounter<160){}
}

Take that all in? Good, because there's another pitfall to Mode 4. Although it is an 8bit mode, the GBA hardware was set up so you can only write 16 bits at a time. This basically means that you either have to do a little extra processing to take into account 2 adjacent pixels (which is slow and unrecommended) or draw two pixels of the same color at a time (which cuts your resolution and is also unrecommended).

Here's a "short" example of drawing a pixel to help you take all this in, because it's a lot of information all at once.

#include "gba.h"
#include "screenmodes.h"

u16* theVideoBuffer = (u16*)VideoBuffer;
u16* theBackBuffer = (u16*)BackBuffer;
u16* theScreenPalette = (u16*)BGPaletteMem;

#define RGB(r,g,b) (r+(g<<5)+(b<<10))   //Macro to build a color from its parts


void WaitForVblank()
{
  #define ScanlineCounter *(volatile u16*)0x4000006

  while(ScanlineCounter<160){}
}


void Flip()
{
  if (REG_DISPCNT & BACKBUFFER)
  {
    REG_DISPCNT &= ~BACKBUFFER;
    theVideoBuffer = theBackBuffer;
  }
  else
  {
    REG_DISPCNT |= BACKBUFFER;
    theVideoBuffer = theBackBuffer;
  }
}


int main()
{
  SetMode( SCREENMODE4 | BG2ENABLE );      //Set screen mode
  int x = 0, y = 0;                        //Coordinate for our left pixel
  theScreenPalette[1] = RGB( 31, 31, 31);  //Add white to the palette
  theScreenPalette[2] = RGB(31,0,0);       //Add red to the palette

  u16 twoColors = (( 1 << 8 ) + 2);  //Left pixel  = 0, right pixel = 1
  theBackBuffer[x + y * 240 ] = twoColors; //Write the two colours
  WaitForVblank();                         //Wait for a vertical blank
  Flip();                                  //Flip the buffers
  return 0;
}

There you have it. What does that all build up to?

I don't recommend you use Mode 4 unless you really have to or you plan everything out meticulously, but I thought you should have the information in case you needed it.



Next : Drawing a pre-made image