gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

DS development > Problem Showing Many Sprites

#111081 - spagnutty - Mon Dec 04, 2006 12:28 am

For part of my program I'm working on drawing a number pad on the DS for id entry. I'm having a problem drawing more than 8 sprites. Each button is a 64x64 sprite loaded from a pcx file. They share the same color palette. The first 8 sprites draw correctly, but sprites 9 and 0 incorrectly show up as 1 and 2.

I'm pretty sure that the problem is that I am using up all of my sprite memory space. From what I've gathered on the forums you can get around the memory limitation by enabling DISPLAY_SPR_1D_SIZE_256. Unfortunately when I try to put that into videoSetMode, my images mess up. Only part of the sprite is drawn and pieces of other sprites are drawn with it.

What exactly do I need to do to so I can show more sprites?

Here's my hopefully relevant code:

Code:
//simple sprite struct
typedef struct {
   int x,y;
   int dx, dy;
   SpriteEntry* oam;   
   int gfxID;
}Sprite;

void initOAM(void) {
   int i;
   for(i = 0; i < 128; i++) {
      OAMCopy[i].attribute[0] = ATTR0_DISABLED;
   }   
}

void updateOAM(void) {
   unsigned int i;
   
   for(i = 0; i < 128 * sizeof(SpriteEntry) / 4 ; i++) {
      ((uint32*)OAM)[i] = ((uint32*)OAMCopy)[i];
   }
}

void MoveSprite(Sprite* sp) {
   int x = sp->x;
   int y = sp->y;
   
   sp->oam->attribute[1] &= 0xFE00;
   sp->oam->attribute[1] |= x;
   
   sp->oam->attribute[0] &= 0xFF00;
   sp->oam->attribute[0] |= y;
}

/* Set up the bottom screen as a frame buffer, and set up
* the top screen as a text console.
*/
void video_setup () {
    // put our main screen on the bottom
    lcdSwap ();
   
    // set the sub background up for text display
    videoSetModeSub (MODE_0_2D | DISPLAY_BG0_ACTIVE);
    // set the main screen for two extended rotation backgrounds and sprites
   videoSetMode (MODE_5_2D |
              DISPLAY_SPR_1D | // DISPLAY_SPR_1D_SIZE_256 ?
              DISPLAY_SPR_ACTIVE |
              DISPLAY_BG3_ACTIVE |
              DISPLAY_BG2_ACTIVE);
   // set up the memory banks
    vramSetMainBanks (VRAM_A_MAIN_BG,
                 VRAM_B_MAIN_BG,
                 VRAM_C_SUB_BG,
                 VRAM_D_LCD);
   
    // tell the DS that background 3 is:
    // a 16 bit bitmap of size 256*256 pixels,
    // at memory base 0
    // at the most visible priority
    BG3_CR = BG_BMP16_256x256 | BG_BMP_BASE (0) | BG_PRIORITY (0);
    mainFrontLayer = (u16*) BG_BMP_RAM (0);
    // tell the DS that background 2 is:
    // a 16 bit bitmap of size 256*256 pixels,
    // at memory base 8
    // at the 2nd most visible priority
    BG2_CR = BG_BMP16_256x256 | BG_BMP_BASE (8) | BG_PRIORITY (1);
    mainBackLayer = (u16*) BG_BMP_RAM (8);
   
    // since MODE_5_2D uses rotation backgrounds, we have to specify all this
    // crap just to get the displays to show up
    BG3_XDX = 1 << 8;
    BG3_XDY = 0;
    BG3_YDX = 0;
    BG3_YDY = 1 << 8;
    BG2_XDX = 1 << 8;
    BG2_XDY = 0;
    BG2_YDX = 0;
    BG2_YDY = 1 << 8;
   
   // start of by clearing everything
   int x, y;
    for (y = 0; y < 256; y++)
        for (x = 0; x < 256; x++) {
         mainBackLayer [x * 256 + y] = 0;
         mainFrontLayer [x * 256 + y] = 0;
      }
}

void getImageData (sImage * images) {
   //load our pcx file into an image
   loadPCX((u8*)numpad1_pcx, &images[0]);
   loadPCX((u8*)numpad2_pcx, &images[1]);
   loadPCX((u8*)numpad3_pcx, &images[2]);
   loadPCX((u8*)numpad4_pcx, &images[3]);
   loadPCX((u8*)numpad5_pcx, &images[4]);
   loadPCX((u8*)numpad6_pcx, &images[5]);
   loadPCX((u8*)numpad7_pcx, &images[6]);
   loadPCX((u8*)numpad8_pcx, &images[7]);
   loadPCX((u8*)numpad9_pcx, &images[8]);
   loadPCX((u8*)numpad0_pcx, &images[9]);
   
   //tile it so it is usefull as sprite data
   int i;
   for (i = 0; i < NUM_SPRITES; i++)
      imageTileData(&images[i]);   
}

static int ds_show_numpad (lua_State *L) {
   vramSetBankE(VRAM_E_MAIN_SPRITE);
      
   Sprite sprites[NUM_SPRITES];
   sImage images[NUM_SPRITES];
   
   getImageData(images);
   
   // Initialize the colors of the Sprite palette
   int i;
   int j;
   for(i = 0; i < 256; i++)
      SPRITE_PALETTE[i] = images[0].palette[i];
   // Sprite initialisation
   for (j = 0; j < NUM_SPRITES; j++)
      for(i = j*64*32; i < 64*32*(j+1); i++)
         SPRITE_GFX[i] = images[j].data16[i - 64*32*j];
   
   initOAM();
   
   for(i = 0; i < NUM_SPRITES; i++) {      
      sprites[i].oam = &OAMCopy[i];
      sprites[i].gfxID = i*128;
      
      //set up our sprites OAM entry attributes
      sprites[i].oam->attribute[0] = ATTR0_COLOR_256 | ATTR0_SQUARE; 
      sprites[i].oam->attribute[1] = ATTR1_SIZE_64;
      sprites[i].oam->attribute[2] = sprites[i].gfxID;
   }
   
   position_sprites(sprites);
   
   swiWaitForVBlank();   
   updateOAM();
   
   return 1;
}

static int ds_setup (lua_State *L) {
    irq_setup ();
    video_setup ();
    console_setup ();
    sound_setup ();
   
    return 1;
}


Thank you!


Last edited by spagnutty on Mon Dec 04, 2006 5:15 pm; edited 1 time in total

#111083 - Lick - Mon Dec 04, 2006 12:33 am

What about setting a VRAM bank to VRAM_x_MAIN_SPRITE?
_________________
http://licklick.wordpress.com

#111104 - spagnutty - Mon Dec 04, 2006 3:21 am

I set vramE to MAIN_SPRITE at the beginning of ds_show_numpad.

#111106 - DekuTree64 - Mon Dec 04, 2006 3:35 am

Yeah, you'll need to use DISPLAY_SPR_1D_SIZE_64 or higher to reference more than 8 64x64, 8-bit sprites. Basically what that does is make the 'starting char' in attribute2 refer to larger units. That means that you'll need to scale down your starting char values to be referencing the same VRAM addresses.

Normally the starting char is in 32 byte units, but if you bump it up to 64 bytes (which is enough to reach all of the 64KB VRAM E), then you'll need to divide your starting char values by 2.
_________________
___________
The best optimization is to do nothing at all.
Therefore a fully optimized program doesn't exist.
-Deku

#111116 - spagnutty - Mon Dec 04, 2006 5:43 am

Deku, thank you so much for your quick response. It really helped me to understand. Unfortunately, I still can't get things to come out correctly when I play with my numbers. I keep on getting slices of images. Maybe I'm just doing something dumb.

If I change DISPLAY_SPR_1D to DISPLAY_SPR_1D_SIZE_64, how should I change in this code to get things to work?

Code:

for(i = 0; i < 256; i++)
   SPRITE_PALETTE[i] = images[0].palette[i];
// Sprite initialisation
for (j = 0; j < NUM_SPRITES; j++)
   for(i = j*64*32; i < 64*32*(j+1); i++)
      SPRITE_GFX[i] = images[j].data16[i - 64*32*j];

initOAM();

for(i = 0; i < NUM_SPRITES; i++) {     
   sprites[i].oam = &OAMCopy[i];
   sprites[i].gfxID = i*128;

   //set up our sprites OAM entry attributes
   sprites[i].oam->attribute[0] = ATTR0_COLOR_256 | ATTR0_SQUARE; 
   sprites[i].oam->attribute[1] = ATTR1_SIZE_64;
   sprites[i].oam->attribute[2] = sprites[i].gfxID;
}


Thank you!

#111125 - DekuTree64 - Mon Dec 04, 2006 7:19 am

Try initializing sprites[i].gfxID to i*64 instead of i*128 (the logic being that the sprite is 64wide x 64tall x 1byte per pixel, or 4096 bytes, divided by 64 bytes per char start, gives you 64).

It's strange that you'd be getting strips though. I'd expect it to just show every other sprite since the addresses are basically doubled. Although I guess that does mean the later ones are going past where you've copied data into, so who knows what may be out there.
_________________
___________
The best optimization is to do nothing at all.
Therefore a fully optimized program doesn't exist.
-Deku

#111128 - spagnutty - Mon Dec 04, 2006 8:21 am

I tried i * 64 but...it didn't work. I'm still getting strips of images. I'm going to play around with things in the morning and see if I can make any progress. In the least I'll come back with a more descriptive photo.

#111143 - Dark Knight ez - Mon Dec 04, 2006 11:07 am

Code:
// Sprite initialisation
for (j = 0; j < NUM_SPRITES; j++)
   for(i = j*64*32; i < 64*32*(j+1); i++)
      SPRITE_GFX[i] = images[j].data16[i - 64*32*j];

Pure madness. ;p
I'd say, go for something more readable.
Code:
// Sprite initialisation
for (j = 0; j < NUM_SPRITES; j++)
   for(i =0; i < 64*32; i++)
      SPRITE_GFX[j*64*32 + i] = images[j].data16[i];

I think that's the same thing... but a lot more readable.
A more descriptive photo to the problem would indeed be nice though.
_________________
AmplituDS website

#111169 - spagnutty - Mon Dec 04, 2006 5:14 pm

Ooo, thank you Dark Knight. That is a lot more readable. :D

Okay, all the code is the same as in the first post except for the following two methods:

Code:

void video_setup () {
    // put our main screen on the bottom
    lcdSwap ();
   
    // set the sub background up for text display
    videoSetModeSub (MODE_0_2D | DISPLAY_BG0_ACTIVE);
    // set the main screen for two extended rotation backgrounds and sprites
   videoSetMode (MODE_5_2D |
              DISPLAY_SPR_1D_SIZE_64 |  /////
              DISPLAY_SPR_ACTIVE |
              DISPLAY_BG3_ACTIVE |
              DISPLAY_BG2_ACTIVE);
   // set up the memory banks
    vramSetMainBanks (VRAM_A_MAIN_BG,
                 VRAM_B_MAIN_BG,
                 VRAM_C_SUB_BG,
                 VRAM_D_LCD);
   
    // tell the DS that background 3 is:
    // a 16 bit bitmap of size 256*256 pixels,
    // at memory base 0
    // at the most visible priority
    BG3_CR = BG_BMP16_256x256 | BG_BMP_BASE (0) | BG_PRIORITY (0);
    mainFrontLayer = (u16*) BG_BMP_RAM (0);
    // tell the DS that background 2 is:
    // a 16 bit bitmap of size 256*256 pixels,
    // at memory base 8
    // at the 2nd most visible priority
    BG2_CR = BG_BMP16_256x256 | BG_BMP_BASE (8) | BG_PRIORITY (1);
    mainBackLayer = (u16*) BG_BMP_RAM (8);
   
    // since MODE_5_2D uses rotation backgrounds, we have to specify all this
    // crap just to get the displays to show up
    BG3_XDX = 1 << 8;
    BG3_XDY = 0;
    BG3_YDX = 0;
    BG3_YDY = 1 << 8;
    BG2_XDX = 1 << 8;
    BG2_XDY = 0;
    BG2_YDX = 0;
    BG2_YDY = 1 << 8;
   
   // start of by clearing everything
   int x, y;
    for (y = 0; y < 256; y++)
        for (x = 0; x < 256; x++) {
         mainBackLayer [x * 256 + y] = 0;
         mainFrontLayer [x * 256 + y] = 0;
      }
}

static int ds_show_numpad (lua_State *L) {
   vramSetBankE(VRAM_E_MAIN_SPRITE);
      
   Sprite sprites[NUM_SPRITES];
   sImage images[NUM_SPRITES];
   
   getImageData(images);
   
   // Initialize the colors of the Sprite palette
   int i;
   int j;
   for(i = 0; i < 256; i++)
      SPRITE_PALETTE[i] = images[0].palette[i];
   // Sprite initialisation
   for (j = 0; j < NUM_SPRITES; j++)
      for(i =0; i < 64*32; i++)
         SPRITE_GFX[j*64*32 + i] = images[j].data16[i];
   
   initOAM();
   
   for(i = 0; i < NUM_SPRITES; i++) {      
      sprites[i].oam = &OAMCopy[i];
      sprites[i].gfxID = i*64; /////
      
      //set up our sprites OAM entry attributes
      sprites[i].oam->attribute[0] = ATTR0_COLOR_256 | ATTR0_SQUARE; 
      sprites[i].oam->attribute[1] = ATTR1_SIZE_64;
      sprites[i].oam->attribute[2] = sprites[i].gfxID;
   }
   
   position_sprites(sprites);
   
   swiWaitForVBlank();   
   updateOAM();
   
   return 1;
}


Which ends up looking like this:
http://www.flickr.com/photos/spagnutty/314063841/

Just for reference, this is the image that I started out with:
http://www.flickr.com/photos/spagnutty/314063845/

Anybody have any ideas?

#111181 - Lick - Mon Dec 04, 2006 6:00 pm

Woops, sorry.
_________________
http://licklick.wordpress.com

#111188 - Dark Knight ez - Mon Dec 04, 2006 7:13 pm

Alright... I'm just gonna go over your code now.


Code:
// put our main screen on the bottom
lcdSwap ();

Maybe that works in your case, but it should actually be
Code:
// put our main screen on the bottom
lcdMainOnBottom();



Code:
sprites[i].oam = &OAMCopy[i];

Where is OAMCopy defined?


There are so many odd things in your code (a sprite's attributes are set in various locations) that I'd think it to be wise if you first concentrated on getting only the 1 to show up.
If even showing only 1 sprite fails for you, try to only load in the graphics for the 1-button to make sure it's not the loading in of graphics that is at fault.
_________________
AmplituDS website

#111235 - spagnutty - Tue Dec 05, 2006 2:39 am

Here's the code cut down to show only 1 sprite. Everything that deals with OAMCopy is also included.

As I suspected, I now have only one image, and that one image is showing with missing strips just the same as before.

Code:

#include "numpad1_pcx.h"

SpriteEntry OAMCopy[128];

//simple sprite struct
typedef struct {
   int x,y;            //location
   int dx, dy;         //speed
   SpriteEntry* oam;   
   int gfxID;             //graphics lovation
}Sprite;

void initOAM(void) {
   int i;   
   for(i = 0; i < 128; i++) {
      OAMCopy[i].attribute[0] = ATTR0_DISABLED;
   }   
}

void updateOAM(void) {
   unsigned int i;
   
   for(i = 0; i < 128 * sizeof(SpriteEntry) / 4 ; i++) {
      ((uint32*)OAM)[i] = ((uint32*)OAMCopy)[i];
   }
}

void MoveSprite(Sprite* sp) {
   int x = sp->x;
   int y = sp->y;
   
   sp->oam->attribute[1] &= 0xFE00;
   sp->oam->attribute[1] |= x;
   
   sp->oam->attribute[0] &= 0xFF00;
   sp->oam->attribute[0] |= y;
}

void video_setup () {
    // put our main screen on the bottom
    lcdMainOnBottom();  // :D
   
    // set the sub background up for text display
    videoSetModeSub (MODE_0_2D | DISPLAY_BG0_ACTIVE);
    // set the main screen for two extended rotation backgrounds and sprites
   videoSetMode (MODE_5_2D |
              DISPLAY_SPR_1D_SIZE_64 |
              DISPLAY_SPR_ACTIVE |
              DISPLAY_BG3_ACTIVE |
              DISPLAY_BG2_ACTIVE);
   // set up the memory banks
    vramSetMainBanks (VRAM_A_MAIN_BG,
                 VRAM_B_MAIN_BG,
                 VRAM_C_SUB_BG,
                 VRAM_D_LCD);
   
    // tell the DS that background 3 is:
    // a 16 bit bitmap of size 256*256 pixels,
    // at memory base 0
    // at the most visible priority
    BG3_CR = BG_BMP16_256x256 | BG_BMP_BASE (0) | BG_PRIORITY (0);
    mainFrontLayer = (u16*) BG_BMP_RAM (0);
    // tell the DS that background 2 is:
    // a 16 bit bitmap of size 256*256 pixels,
    // at memory base 8
    // at the 2nd most visible priority
    BG2_CR = BG_BMP16_256x256 | BG_BMP_BASE (8) | BG_PRIORITY (1);
    mainBackLayer = (u16*) BG_BMP_RAM (8);
   
    // since MODE_5_2D uses rotation backgrounds, we have to specify all this
    // crap just to get the displays to show up
    BG3_XDX = 1 << 8;
    BG3_XDY = 0;
    BG3_YDX = 0;
    BG3_YDY = 1 << 8;
    BG2_XDX = 1 << 8;
    BG2_XDY = 0;
    BG2_YDX = 0;
    BG2_YDY = 1 << 8;
   
   // start of by clearing everything
   int x, y;
    for (y = 0; y < 256; y++)
        for (x = 0; x < 256; x++) {
         mainBackLayer [x * 256 + y] = 0;
         mainFrontLayer [x * 256 + y] = 0;
      }
}

static int ds_show_numpad (lua_State *L) {
   vramSetBankE(VRAM_E_MAIN_SPRITE);
      
   Sprite sprites[1];
   
   sImage numpad1;
   loadPCX((u8*)numpad1_pcx, &numpad1);
   imageTileData(&numpad1);
   
   // Initialize the colors of the Sprite palette
   int i;
   for(i = 0; i < 256; i++)
      SPRITE_PALETTE[i] = numpad1.palette[i];
   // Sprite initialisation
   for(i =0; i < 64*32; i++)
      SPRITE_GFX[i] = numpad1.data16[i];
   
   initOAM();
   
   sprites[0].oam = &OAMCopy[0];
   sprites[0].gfxID = 0;

   //set up our sprites OAM entry attributes
   sprites[0].oam->attribute[0] = ATTR0_COLOR_256 | ATTR0_SQUARE; 
   sprites[0].oam->attribute[1] = ATTR1_SIZE_64;
   sprites[0].oam->attribute[2] = sprites[0].gfxID;

   sprites[0].x = 0;
   sprites[0].y = 0;
   
   MoveSprite(&sprites[0]);
   
   swiWaitForVBlank();   
   updateOAM();
   
   return 1;
}

#111265 - Dark Knight ez - Tue Dec 05, 2006 10:36 am

Are the pcx files stored as 8-bit images?

Anyway, take a look again at your posted shots.
The first 1 is not just "half of 1".
The flaw is more apparent at the 2 below it.

I *think* something like this happens:
First 8 lines of your pcx image is shown.
- 8 lines are skipped -
Next 8 lines of your pcx image is shown.
- 8 lines are skipped -
Next [... etc]

This results in what you see. (Half the height of an image, but not a proper looking half.)
To me, this means that the image is not stored as it should be in SPRITE_GFX.
This could either be by the use of a wrong format pcx (check if it's 8-bit or not), by loadPCX being faulty, and/or by imageTileData being faulty.
My guess is imageTileData, but don't slap me silly for saying that if I'm wrong.
You probably should convert the graphics yourself instead of doing it in the code on the DS. Use gfx2gba which can spit out a graphics.bin and a graphics_palette.bin file. That should give proper results.
_________________
AmplituDS website

#131487 - mml - Sat Jun 16, 2007 4:22 am

I don't know if you ever solved your problem here, but I just got bitten by this exact issue myself, and since the three other threads on the issue have no solution either I figured I'd post the solution.

The problem is your mode setting:

Code:
   videoSetMode (MODE_5_2D |
              DISPLAY_SPR_1D_SIZE_64 |  /////
              DISPLAY_SPR_ACTIVE |
              DISPLAY_BG3_ACTIVE |
              DISPLAY_BG2_ACTIVE);


If you read gbatek very carefully, you'll notice that in order to address 64K of 1D vram with the OAM you need to set bits 4 and 20-21 in the display control register (this is what videoSetMode is for):

Code:
OBJ Tile Mapping (DISPCNT.4,20-21):

  Bit4  Bit20-21  Dimension Boundary Total ;Notes
  0     x         2D        32       32K   ;Same as GBA 2D Mapping
  1     0         1D        32       32K   ;Same as GBA 1D Mapping
  1     1         1D        64       64K
  1     2         1D        128      128K
  1     3         1D        256      256K  ;Engine B: 128K max


However! If you then check nds/arm9/video.h for the definitions of the macros for doing this, you'll notice the following:

Code:
#define DISPLAY_SPR_1D              (1 << 4)
...
#define DISPLAY_SPR_1D_SIZE_64      (1 << 20)


Notice that DISPLAY_SPR_1D_SIZE_64 does not set bit 4!

Thus, your mode setting above is effectively saying "I want to use 2D mode", since bit 4 isn't being set; and since bits 20-21 are apparently ignored when bit 4 isn't set (at least, that's how I interpret the x in the table above), you're also not addressing any extra memory anyway!

So the fix to make this work is simply to set both DISPLAY_SPR_1D and DISPLAY_SPR_1D_SIZE_64:

Code:
   videoSetMode (MODE_5_2D |
              DISPLAY_SPR_1D |          ////// <-- make it 1D
              DISPLAY_SPR_1D_SIZE_64 |  /////
              DISPLAY_SPR_ACTIVE |
              DISPLAY_BG3_ACTIVE |
              DISPLAY_BG2_ACTIVE);


Hopefully I've saved someone, somewhere a couple of hours of confusion :)

#131491 - spagnutty - Sat Jun 16, 2007 4:40 am

I abandoned that approach looooong ago, and opted for a functional but ugly workaround.

But thank you for the explanation! I'm very glad to hear it. It will make any future projects I do much easier.

Great job!