Phew, it's finally time to write some code! Open up your main.c, it should look like this: (from earlier chapters)
#include <nds.h>
int main( void )
{
irqInit();
irqEnable( IRQ_VBLANK );
while(1)
{
swiWaitForVBlank();
}
return 0;
}
|
Create a new function, it will be used to load all of the graphics and setup the display mode. I'll call it setupGraphics(). Add this function to main() after setting up the interrupts.
The first thing we want to do in it is setup the VRAM banks we will use. Bank E will be used for the backgrounds (next chapter), and Bank F will be used for sprites (upcoming chapters). Setup both to be used by the main engine.
void setupGraphics( void )
{
vramSetBankE( VRAM_E_MAIN_BG );
vramSetBankF( VRAM_F_MAIN_SPRITE );
...
|
Here is a visual representation of half of VRAM bank E when no data is loaded.

Each of those cells represent a 16-color tile entry. Each 8x8 16 color tile uses up 32 bytes of memory (8 * 8 * 1/2 bytes).
BIG WARNING: Although the memory may be thought of as blank when your program starts, it may not really be blank! It may contain random garbage, or leftovers from whatever booted your code. This is one of the biggest issues that occur when switching from emulator to hardware. When programming, do not expect anything to be initialized to zero.
Now, lets zoom in on the upper left corner of this block. This is what we can imagine the VRAM will look like up there when we load our tiles.

We will clear the first tile to zero during the setup function. We will copy the brick tile into tile 1, and the gradient tiles into tiles 2->9. The question marks represent uninitialized data (see above warning).
To copy the tiles, we will use the dmaCopyHalfWords() function. This function uses the DMA (direct memory access) hardware to copy blocks of data from one place to another. We are going to copy the data from our program into VRAM.
First, include the references that were produced by grit, also make some definitions of where the data will be loaded.
//----------------------------------------------- // graphic references //----------------------------------------------- #include "gfx_ball.h" #include "gfx_brick.h" #include "gfx_gradient.h" //----------------------------------------------- // tile entries //----------------------------------------------- #define tile_empty 0 // tile 0 = empty #define tile_brick 1 // tile 1 = brick #define tile_gradient 2 // tile 2 to 9 = gradient // macro for calculating BG VRAM memory // address with tile index #define tile2bgram(t) (BG_GFX + (t) * 16) |
Now in your setup code, copy the graphics into vram...
void setupGraphics( void )
{
vramSetBankE( VRAM_E_MAIN_BG );
vramSetBankF( VRAM_F_MAIN_SPRITE );
// generate the first blank tile by clearing it to zero
int n;
for( n = 0; n < 16; n++ )
BG_GFX[n] = 0;
// copy bg graphics
dmaCopyHalfWords( 3, gfx_brickTiles, tile2bgram( tile_brick ), gfx_brickTilesLen );
dmaCopyHalfWords( 3, gfx_gradientTiles, tile2bgram( tile_gradient ), gfx_gradientTilesLen );
...
}
|
There are a few things here that need explaining. First of all, we have BG_GFX. This is a pointer defined by libnds that gives the base address for the main engine's BG VRAM.
tile2bgram is a small macro that calculates the offset of a certain tile in the BG graphics memory. It adds t * 16 which really adds t * 32 bytes because BG_GFX uses 16-bit entries.
We also see direct access to BG_GFX when we write the first empty tile, each tile is 32 bytes, so this will write 16 half-words (16-bits) to the first tile in bg memory.
The last thing is the parameters of dmaCopyHalfWords. The first parameter is the DMA channel selection. There are 4 DMA channels (0->3) that can be used for different things. Channel 3 may be used for all general purpose transfers (so we will use it here). Channels 1 & 2 have higher priority than channel 3 (with GBA, 1&2 were used for transferring sound data). Channel 0 has the highest priority and can be used to transfer critical data. The second parameter of dmaCopyHalfWords is the source address. The third parameter is the destination address. The last parameter is how much data to copy. X bytes of data (replace X with the last parameter) will be copied from the source address to the destination address.
When 16 or 256 color graphics are used, they are rendered using a palette. The main engine's palette memory is mapped for access at address 0x5000000->0x50003FF. Each entry is a 16-bit color value. Here is a visual representation of the first 256 colors of the palette memory when it is empty (but remember that it may not be empty when you load the game on hardware!).

The first 256 color block is used by the backgrounds, the other 256 color block after is used by the sprites. Back in the SNES days, you only had one 256 color palette for both :(.
Now, in 16 color mode, this palette is divided up into 16 pieces, you can select any one of them to render any 16 color graphic. With 256 color graphics, there is no palette selection, because they can use the whole palette. Here is the palette divided into 16 sub-palettes.

Now, the first color of each palette is special. It is the transparent color, this color is not ever seen on the screen, it is used to make see through parts of an image. To make parts of your image transparent, paint the transparent parts with a unique color, and fix that color at index 0! In 256 color mode, only index 0 of 255 is transparent.
One more exception though, although colors 16,32,48,64,etc are never rendered when using 16 color graphics, color 0 may be rendered! (top left color 0, as seen in image) This color is the backdrop. If the screen isn't completely covered by graphics, anything left over will be filled with the backdrop color. Here is what the palette will look like when we are done loading it.

There is something wrong here, it's the first backdrop color. Right now it's loaded with the hidden red transparent color for the bricks, but it will be used for the backdrop! After we are done
loading the palettes, we will overwrite this entry with a blueish color. 
Now lets copy the palettes, start by adding a few definitions for placement.
// BG PALETTES #define pal_bricks 0 // brick palette (entry 0->15) #define pal_gradient 1 // gradient palette (entry 16->31) #define backdrop_colour RGB8( 190, 225, 255 ) #define pal2bgram(p) (BG_PALETTE + (p) * 16) |
Now at the next section of setupGraphics:
// palettes goto palette memory dmaCopyHalfWords( 3, gfx_brickPal, pal2bgram(pal_bricks), gfx_brickPalLen ); dmaCopyHalfWords( 3, gfx_gradientPal, pal2bgram(pal_gradient), gfx_gradientPalLen ); // set backdrop color BG_PALETTE[0] = backdrop_colour; |
BG_PALETTE is a libnds pointer that addresses the main engine's background palette. We set index 0 in the end to our backdrop color.
We can have a chance to see some results now, let's setup a test video mode. We'll use bg mode 0 without any backgrounds enabled, and normal display.
swiWaitForVBlank(); videoSetMode( MODE_0_2D ); |
Some people don't care about waiting for a new frame before they set the video mode. It's not so important, but it prevents a tiny bit of crap from showing when you set the display.
Compile the code (run make from the program directory/alt+1 if you are using programmers notepad). Run the produced .nds file with no$gba. You should see your blue backdrop displayed.
| Previous: Converting the graphics | Contents | Next: The background |