GB101:Working with Palettes
GB101 Class Notes | |
---|---|
|
Introduction[edit]
In this example we'll take a look at palette graphics. In the palette modes each 8-bit byte in the video buffer represents an index in an array of colors called the palette. If you've ever worked in mode 13h (VGA) on a PC then you will be familiar with this approach. We'll also examine interrupt handling on the GBA, horizontal and vertical refreshes, and my favorite demo routine, raster bars.
Palettes[edit]
The bitmap palette starts at 0x5000000 and we write to it with the same RGB5 macro we used before.
Interrupts[edit]
The easiest way to think about interrupts is as event handlers. When enabled the CPU will call your code upon certain events like timers, input, or as we'll use here horizontal and vertical sync.
Horizontal and Vertical Blanking[edit]
When the GBA is finished drawing a line, or a screen there is a pause. During these times is when you'll want to make changes to the screen to avoid artifacts like tearing. Most often you'll use the vertical blank period to make your changes after each screen refresh. In this example we'll use both to make a cool effect.
Raster Bars[edit]
If you've ever bootlegged an 8-bit home computer game you've probably seen this effect, it's a demoscene stable. The coolest thing about raster bars is that they don't actually draw anything to the screen, they manipulate the palette at the right times to achieve the effect. First we fill the screen with color 0, the first palette entry in 0x5000000. At the end of each screen refresh (VBlank) we fill an array the same size as the height of the screen with the colors we want for the bars on the next refresh. At the end of each line refresh (HBlank) we change the color of palette index 0 to the appropriate color stored in the array. When the GBA draws the line it will be the color we specified. Now it looks like we're moving lots of bits around when we're not really doing much at all.
Implementation[edit]
#include <gba_console.h>
#include <gba_video.h>
#include <gba_interrupt.h>
#include <gba_systemcalls.h>
// Positions of the bars
int gBar[5] = {0,15,31,47,63};
// Velocity of the bars
int gInc[5] = {2,2,2,2,2};
// The palette 0 values. Each represents a line of the screen
int gColors[SCREEN_HEIGHT];
// Called when the GBA finishes drawing the screen. Here we'll shift
// gColors to make the bars move.
void vBlankInterrupt(void) {
int i,j;
// Increment each one of our bars, bouncing it at the appropriate spots
for (i=0; i<5; i++) {
gBar[i] += gInc[i];
if (gBar[i] >= SCREEN_HEIGHT-31 || gBar[i] <= 0)
gInc[i] = -gInc[i];
}
// Now load up our palette with the colors of the bars
for (i=0; i<SCREEN_HEIGHT; i++) {
// Initialize to zero so we erase old bars.
gColors[i] = 0;
// For each bar, if we're in that part of the array
// set the appropriate color
for (j=0; j<5; j++) {
// Set the colors going up
if (i >= gBar[j] && i < gBar[j] + 15) {
// Color = Offset from the top of the bar times 2
gColors[i] = RGB5((i-gBar[j])<<1,0,0);
}
// Set the colors going back down
else if (i >= gBar[j] + 15 && i < gBar[j] + 31) {
// Color = Reverse offset from the middle of the bar times 2
gColors[i] = RGB5((31-((i-gBar[j])-15))<<1,0,0);
}
}
}
}
// Called when the GBA finishes drawing a line. Here we'll set the value of
// the first color of the palette (0) to the current line we're drawing in
// gColors.
void hBlankInterrupt(void) {
// At each Hblank, set the color of 0 to the current vertical line position
BG_COLORS[0] = gColors[REG_VCOUNT];
}
int main(void) {
unsigned short* VideoBuffer = (unsigned short*)0x6000000;
unsigned int i;
// Enable interrupts
irqInit();
// Set the interrupts for viertual and horizontal refresh to our functions
irqSet(IRQ_VBLANK,vBlankInterrupt);
irqSet(IRQ_HBLANK,hBlankInterrupt);
// Enable the interrupts
irqEnable(IRQ_HBLANK | IRQ_VBLANK);
// Set up mode 4 so we can have palette gfx
SetMode(MODE_4 | BG2_ENABLE);
// Initialize the bar palette
vBlankInterrupt();
// Paint the screen with just color 0 (We have to address in 16-bits)
for(i = 0; i < SCREEN_WIDTH * SCREEN_HEIGHT / 2; i++) {
*VideoBuffer = 0x0000;
VideoBuffer++;
}
while(1) { /* Do nothing */ }
}