• 03/06/02 11:37 AM
    Sign in to follow this  

    GBA Development From the Ground Up, Volume 2

    General and Gameplay Programming

    Myopic Rhino
    [size="5"] What will this article cover?

    This article will describe how to change the screen mode for the GBA and how to draw images in the bitmap modes 3, 4, and 5.

    Let's begin.


    [size="5"]Getting the Compiler Working

    I hope you downloaded ALL the files needed for your operating system from the DevKitAdv site, as they will all be indispensable (except for maybe the C++ additions, but I like C++ and will probably use it in this article). Unzip all the files to the root directory, and you should have a new directory called DevKitAdv. Congratulations, you just installed DevKitAdv with everything you need.

    Now open your friendly Notepad and type in some code. Save this code as anything you want with a .c extension (I'll use test.c for this example).

    #include

    int main()
    {
    return 0;
    }
    Yes, I know it doesn't do anything. This is just an example to get the compiler working. Also, make sure you hit return after the final brace or else your compiler will whine at you (for some reason there has to be a new line at the end of every file).

    Now open up your text editor again and type in the following:
    [bquote][font="Courier New"][color="#000080"]path=c:\devkitadv\bin
    gcc -o test.elf test.c
    objcopy -O binary test.elf test.bin[/color][/font][/bquote]
    Save this file as Make.bat. Note that some of the information in the file might have to change depending on the name of your file and what drive DevKitAdvance is installed on.

    Now double click on Make.bat. Wait for the program to end. Congratulations, you just wrote your first program. What the make file does is call gcc to create test.elf from test.c, then it calls objcopy to create test.bin from test.elf. You might want to read that a few times if you didn't get it. However, regardless of if you understand that or not, those are the only three lines you'll need to put in a make file to get your program to compile (although those lines may have to vary depending on how many source files you use and the names of these files). For example, if you were using two source files named test.c and input.c, you'd simply change the second line to:
    [bquote][font="Courier New"][color="#000080"]gcc -o test.elf test.c input.c[/color][/font][/bquote]
    I hope you understand, because things only get more complicated from here. :-)

    Now that we know how to compile a simple program, let's move on.


    [size="5"]Using What You Create

    When you compile your program, two files should be created - a .elf and a .bin. You want to use the .bin. Simply run this .bin using your respective emulator to view your creations.

    If you have a linker and a cart, use the linker (instructions are included and more info can be found at www.visoly.com) to write the .bin to the cart. Push the cart in your GBA, turn the GBA on, and viola! Your creations are running on hardware!


    [size="5"]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 in the attached resource file.

    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.


    [size="5"]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.


    [size="5"]Drawing a Pre-Made Image

    Drawing an image made in another program is remarkably easy - I've practically given you all the information already.

    First, get an image converter/creation program. www.gbadev.org is a great place to get one. With an image converter, take the .bmp or .gif or .pcx or whatever (depending on what the program uses), and convert the image to a standard C .h file. If your image uses a palette, this .h file should have both palette information and image information stored in separate arrays.

    Now, all drawing the image consists of is reading from the arrays. Read from the palette array (if there is any) and put the data into the palette memory. To draw the image, simply run a loop that goes through your array and puts the pixels defined in the array at the desired location.

    Naturally, these steps will be slightly different depending on what program you're using. However, being the nice guy that I am, I'm going to provide you with a quick example.

    The program: Gfx2Gba v1.03 by Darren (can be found at www.gbadev.org, read the readme for instructions)

    The image: A 240*160, 8 bit image named gbatest.bmp

    The command line: gfx2gba gbatest.bmp gbatest.h -8 -w 240

    The code:

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

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

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

    int main()
    {
    SetMode(SCREENMODE4|BG2ENABLE);

    //Copy the palette
    u16 i;
    for ( i = 0; i < 256; i++ )
    theScreenPalette[ i ] = gbatestPalette[ i ];

    //Cast a 16 bit pointer to our data so we can read/write 16 bits at a time easily
    u16* tempData = (u16*)gbatest;

    //Write the data
    //Note we're using 120 instead of 240 because we're writing 16 bits
    //(2 colors) at a time.
    u16 x, y;
    for ( x = 0; x < 120; x++ )
    for ( y = 0; y < 160; y++ )
    theVideoBuffer[ y * 120 + x ] = tempData[ y * 120 + x ];

    return 0;
    }
    And that's all! I didn't use the backbuffer, because I was just trying to make a point. I used Mode 4 so that I could press the fact that you MUST write 16 bits at a time. If you were using Mode 3/16 bit color, you wouldn't need the tempData pointer. You would also change the 120s back to 240s.

    In fact, that's the end of this article. By now, you should have bitmapped modes mastered, or at least you should have a relatively firm grasp on them.


    [size="5"]Next Article

    In the next article, I'm going to give you information on a rather easy topic - input. If you're lucky, I might even make a game demo to show off.


    [size="5"]Acknowledgements

    I would like to thank dovoto, as his tutorials have been my biggest source of information on GBA development since I started. Check out his site at www.thepernproject.com. I'd also like to thank the guys in #gamedev and #gbadev on EFNet in IRC for all their help, and all the help they'll be giving me as I write these articles. Furthermore, I would like to thank www.gbadev.org/. The site is a great resource, and it is definitely worth your time.

    If you have any questions or comments, you can always e-mail me at [email="genesisgenocide@yahoo.com"]genesisgenocide@yahoo.com[/email] or [email="nairb@usa.com"]nairb@usa.com[/email]. I can't promise I'll answer your questions, like your comments, or listen to your suggestions, but I'll at least read the e-mail (as long as it doesn't look like spam).


      Report Article
    Sign in to follow this  


    User Feedback

    Create an account or sign in to leave a review

    You need to be a member in order to leave a review

    Create an account

    Sign up for a new account in our community. It's easy!

    Register a new account

    Sign in

    Already have an account? Sign in here.

    Sign In Now

    There are no reviews to display.