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 > Performance problems when drawing fullscreen

#63580 - melw - Wed Dec 14, 2005 2:50 pm

I've been lately fiddling with some simple tile layout game tests and got everything otherwise working nicely but got some performance and flickering problems. More precicely here's what I have currently:

- Using VRAM_A with MODE_FB0 for main display, other one is blank / for console debug output only
- in main() loop there's currently just scanKeys() ... not much anything done to key input
- on IRQ_VBLANK the screen is drawn to framebuffer, first with 32x32 pixel tiles (8 by 6 tiles) as a background, then various sized game objects on top of it.

Whereas the background looks solid all the time, the game objects are flickering - most at the top of the screen, least in the bottom of the screen. The screen refresh rate seems to be close to 30fps, so I believe this has something to do with drawing being just too slow / taking more time than it should.

I take some kind of double buffering would be solve this - but taking a look at existing examples I couldn't quite figure out what's the optimal screen modes and method for doing this.

Any ideas and guidance is appreciated. Thanks.

#63612 - cybereality - Thu Dec 15, 2005 12:33 am

It sounds like you need to use a double buffer. Basically you want to draw everything to the buffer first and then flip it to the screen. Drawing directly to the frame buffer is usually not a good idea (for games at least) when you are doing multiple render passes (once for the background + any character sprites). I have a double buffer demo on my site, its not for sprites, but it may help. Also, make sure you are using DMAcopy to blit the sprites cause it is a lot faster. There is also the chance that the graphics take longer to print to screen than the V_Blank period, in which case you want to make sure the frame rate is synced with the v_blank (sorry, I cant remember the syntax, but Im sure you can find the code with a little searching). Hope that helps.
_________________
// cybereality

#63659 - melw - Thu Dec 15, 2005 10:43 am

Thanks for the tips. I took in fact already a look at DoubleC's tutorials on double buffering but didn't think it'd be usable as it is because I want to have my beautiful pixels and not to do any hardware scaling. :) But on a second thought I guess using BG_BMP16_256x256 and not scaling, just showing the uppermost 256x192 area on the screen should do the trick.

Let's see if I can get this one working...

#63692 - melw - Thu Dec 15, 2005 4:38 pm

Ok, I got bit further but there's still some issues I couldn't figure out easily. Now it works in DSEmu if I don't draw too much on the screen at once. On DS the bottom screen stays blank, but the upper screen with debug data keeps updating (I can see the timer value running). If I remove both the drawScene() and swiWaitForVBlank() from main loop then the DS does also the dmaCopy from back buffer to the screen - otherwise it never goes that far.

Here's a stripped piece of code what I'm trying to do currently:

Code:

void drawImage(int index, int x, int y)
{
   REG_IME = 0;
   
   int xsize=image_xsize[index];
   int ysize=image_ysize[index];
   
   for(int yy=0;yy<image_ysize[index];yy++)
   {
      for(int xx=0;xx<image_xsize[index];xx++)
      {
         if((y+yy)>=0&&(y+yy)<Y_RES&&(x+xx)>=0&&(x+xx)<X_RES) // check clipping
         {
            uint32 iData = yy*image_xsize[index]+xx+image_offset[index];
            if(binary_images[iData]!=0) // 0 is transparent
            {
               uint16 pData = palette_data[image_palindex[index]][binary_images[iData]];
               backBuffer[(y+yy)*X_RES+x+xx] = RGB15((pData>>10)&0x1f, (pData>>5)&0x1f, (pData>>0)&0x1f);
            }
         }
      }
   }
   
   REG_IME = 1;
}

void drawScene()
{
   int xi=0;
   int yi=0;

   // scrolling background
   for(int y=0;y<Y_RES;y+=TILE_YSIZE)
   {
      for(int x=0;x<X_RES+TILE_XSIZE;x+=TILE_XSIZE)
      {
         drawImage(tile1_index+tilemap[yi*MAP_Y_RES+xi+((timer>>3)%130)], (x-((timer<<2)%TILE_XSIZE)), y);
         xi++;
      }
      yi++;
      xi=0;
   }
   
   int frame=((timer>>1)%7);
   
   drawImage(character_index+frame,char_x,char_y);
}

void on_irq()
{
   if(REG_IF & IRQ_VBLANK)
   {
      printf("timer: %04d\n", timer);
      VBLANK_INTR_WAIT_FLAGS |= IRQ_VBLANK; 
      REG_IF |= IRQ_VBLANK;
      
      timer++;
   }
}

main()
{
   powerON(POWER_ALL);

   // init video modes
   videoSetMode(MODE_5_2D | DISPLAY_BG2_ACTIVE);
   vramSetBankA(VRAM_A_MAIN_BG_0x6000000);
   vramSetBankB(VRAM_B_MAIN_BG_0x6020000);
   BG2_CR = BG_BMP16_256x256;
   
   // Set translations
   BG2_XDX = 1 << 8;
   BG2_XDY = 0;
   BG2_YDX = 0;
   BG2_YDY = 1 << 8;
   BG2_CY = 0;
   BG2_CX = 0;
   
   // console screen
   videoSetModeSub(MODE_0_2D | DISPLAY_BG0_ACTIVE);
   vramSetBankC(VRAM_C_SUB_BG_0x6200000);
   // console text output on bg0
   SUB_BG0_CR = BG_MAP_BASE(31);
   BG_PALETTE_SUB[255] = RGB15(31,31,31);
   consoleInitDefault((u16*)SCREEN_BASE_BLOCK_SUB(31), (u16*)CHAR_BASE_BLOCK_SUB(0), 16);
   
   // set the screen buffers
   frontBuffer = (u16*)(0x06000000);
   backBuffer =  (u16*)(0x06000000 + 256 * 256 * 2);
   
   // Set up the interrupt handler
   REG_IME = 0;
   IRQ_HANDLER = on_irq;
   REG_IE = IRQ_VBLANK | IRQ_KEYS;
   REG_IF = ~0;
   DISP_SR = DISP_VBLANK_IRQ;
   REG_IME = 1;
   
   while(1)
   {
      scanKeys();
      drawScene();
   
      // sync to framerate
      swiWaitForVBlank();
         
      dmaCopy(backBuffer, frontBuffer, 256*256*2);
   }
   
   return 0;
}

#63821 - melw - Fri Dec 16, 2005 4:54 pm

Ok, solved the issues myself. In case anyone runs into similar problems - here's how I got it working on both real device and emulator:

Using framebuffer, backBuffer as VRAM_A, frontBuffer as VRAM_B and then switch always after drawing between the buffers - for some reason it was the dmaCopy(backBuffer, frontBuffer) that hanged on the DS - but the following works:

Code:

if(screen) {
   videoSetMode(MODE_FB1);
   frontBuffer = VRAM_B;
   backBuffer = VRAM_A;
   screen = 0;
} else {
   videoSetMode(MODE_FB0);   
   frontBuffer = VRAM_A;
   backBuffer = VRAM_B;
   screen = 1;
}