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.

Coding > Sprite timing

#174816 - Kensai - Wed Jul 21, 2010 9:42 am

There is an inventory in my game which shows the items one of my party members is carring. Each item (sword, armor, etc.) is represented by a 16x16 sprite. If I press a shoulder button the inventory of the next/prev hero is displayed - which means, all sprites have to be overwritten. And here is the problem: If I overwrite the sprites, some of them appear simulanously on the screen (they simply pop up IN NO TIME - even if I decreasy the framerate to 10%) while others show up with a visible delay. I don't understand whats going on there. Shouldn't they ALL pop up simulanously OR being ALL displayed one after another? How can I solve this problem? Do I have to draw the sprites off-screen or is there another method?

Code:

Inventory 1:
 ___ ___ ___
|   |   |   |
| 1 | 1 | 1 |
|___|___|___|
|   |   |   |
| 1 | 1 | 1 |
|___|___|___|

You see this picture for a brief moment:
 ___ ___ ___
|   |   |   | 
| 2 | 2 | 2 |
|___|___|___|
|   |   |   |
| 2 | 2 | 1 |
|___|___|___|

The first five items are displayed in no time while the sixth item is delayed.

Inventory 2:
 ___ ___ ___
|   |   |   |
| 2 | 2 | 2 |
|___|___|___|
|   |   |   |
| 2 | 2 | 2 |
|___|___|___|

#174817 - Azenris - Wed Jul 21, 2010 10:24 am

Are you using waitforvsync function?

Not sure really, never had the problem myself. Maybe post alittle code in that area.
_________________
My Homebrew Games

#174818 - Kensai - Wed Jul 21, 2010 10:53 am

Yes, I use waitforvsync:

Code:
drawBackground();             // draws the background of the inventory (-> mode 4 back buffer)
vsync();                      // waits for vsync
flip_video_buffer();          // back buffer <-> front buffer

write_sprite_16(0, sprite0)   // overwrites the sprites
write_sprite_16(1, sprite1)   //
write_sprite_16(2, sprite2)   //
write_sprite_16(3, sprite3)   //
write_sprite_16(4, sprite4)   //
write_sprite_16(5, sprite5)   //

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

INLINE void write_spr_16(int spr, u16* img)
{
  int i = 128*spr;  // 8x16 = 128
  int j = i + 128;
  for(i; i<j; i++)
  {
    ObjDataMem[i] = *img++;
  }
}

///////////////////////////////////////////////////////////////////////////////

INLINE void vsync()
{
  while(REG_VCOUNT >= 160); // VDraw
  while(REG_VCOUNT <  160); // VBlank
}

///////////////////////////////////////////////////////////////////////////////
   
INLINE void flip_video_buffer()
{      
  if (REG_DISPCNT & DCNT_PAGE)
  {
    REG_DISPCNT &= ~DCNT_PAGE; // BackBuffer = Page 2
    BackBuffer  =  VRAM_PAGE2;
    FrontBuffer =  VRAM_PAGE1;
  }
 else
 {
    REG_DISPCNT |= DCNT_PAGE;  // BackBuffer = Page 1
    BackBuffer  =  VRAM_PAGE1;
    FrontBuffer =  VRAM_PAGE2;
  }
}

#174821 - Dwedit - Wed Jul 21, 2010 1:52 pm

You have posted the textbook example of how NOT to wait for Vsync. That runs the CPU at 100% usage, reducing battery life.

See SWI 0x05, VBlankIntrWait. LibGBA provides the function VBlankIntrWait() you can call. You must have some kind of interrupt handler running, which LibGBA also provides for you, also probably need the Vblank interrupt enabled.

Anyway, to avoid sprite glitches, make sure you are setting the new sprites AND the new graphics for the sprites at the same time, both during Vblank time. Getting the sprite table updated is more important, since drawing to VRAM can also be done while the screen is rendering, you will just get tearing or incorrect graphics if you're too slow.
_________________
"We are merely sprites that dance at the beck and call of our button pressing overlord."

#174822 - Kensai - Wed Jul 21, 2010 5:05 pm

Quote:
You have posted the textbook example of how NOT to wait for Vsync. That runs the CPU at 100% usage, reducing battery life.

See SWI 0x05, VBlankIntrWait. LibGBA provides the function VBlankIntrWait() you can call. You must have some kind of interrupt handler running, which LibGBA also provides for you, also probably need the Vblank interrupt enabled.


Thank you for the information. I will fix it asap.

Quote:
Anyway, to avoid sprite glitches, make sure you are setting the new sprites AND the new graphics for the sprites at the same time, both during Vblank time. Getting the sprite table updated is more important, since drawing to VRAM can also be done while the screen is rendering, you will just get tearing or incorrect graphics if you're too slow.


The sprite table doesn't have to be changed - all sprites have a fix position on the screen. All I have to do is overwriting the sprite graphics. But some sprite graphics still show up faster than others. How can this be possible? You say, that I might get these glitches if I'm too slow. But the sprite function is quite flawless - I don't think that it can be made any faster... And there aren't any other functions involved. It's just...

waitForVSync()
flipScreen()
overwriteSpriteGraphics()

And that's it!
Any suggestions?

#174825 - Dwedit - Wed Jul 21, 2010 9:34 pm

Try using DMA copy instead of the for loop. Hope you don't need DMA Channel #3 for some other purpose.

The function prototype:

static inline void dmaCopy(const void * source, void * dest, u32 size);

Size is in bytes. The transfer is 16-bit DMA, which is good for reading from a cartridge, and writing to VRAM.

DMA temporarily halts the CPU so it can just do a series of memory reads and writes as fast as possible, then wakes it up when the transfer is finished. So if you have any interrupts that can't wait for 256 bytes to be transferred, consider something else. DMA is also less useful on the Nintendo DS because of the cache, cached memory writes may not take effect until much later, so you need to flush the write cache before doing any DMA.

Where is ObjDataMem? Is it inside VRAM?


Edit: Actually, this seems silly, I don't think it's a speed issue at all. Copying 1.5k of memory shouldn't eat up all of vblank time, there might be a bug somewhere else.
_________________
"We are merely sprites that dance at the beck and call of our button pressing overlord."

#174832 - Kensai - Thu Jul 22, 2010 10:49 pm

Quote:
Where is ObjDataMem? Is it inside VRAM?


Yes, it is inside VRAM:

Code:
#define OBJ_VRAM (u16*)0x6014000
u16* ObjDataMem = OBJ_VRAM;


Quote:
Copying 1.5k of memory shouldn't eat up all of vblank time, there might be a bug somewhere else.


I use DevKitAdv. Could this be the reason? I consider switching to DevKitPro but I'm undecided. I'm not sure about the advantages but there are definitely some disadvantages. For example, you can't test your roms in MappyVM which is my favorite gba emulator.