Timing problems with IRQs and graphics
Posted: Thu Sep 08, 2011 6:04 pm
I'm trying to code a MP3 player for the DS. Moonshell-like: you get a nice UI with smooth scrolling lists and such. MP3 streaming is working, the basic UI is done (a navigable scrolling file list)
But I have a problem: calls to the MP3 player take much more than 1/60th of a second so I can't animate the UI properly. I've tried to modify the MP3 decoder to decode smaller chunks at a time so it can keep up with the UI but no luck. Probably because doing things in smaller chunks makes the cache less effective.
So I thought of leaving the MP3 code on main() and update the UI in the Vblank IRQ handler. Here's what my code looks like right now (showing only the relevant stuff):
I've made the update() and render() functions to be as small as possible. Only new items that come on screen are rendered. I've also checked that the (tiny) IRQ Mode stack does not overflow. It works flawlessly.
It measures CPU related to the 60Hz screen refresh rate: 100% would mean it's taking exactly 1/60th of a second. Usually it is at CPU 2%. When you scroll the list and it has to render some files it goes up to 30% or so.
BUT There's ONE issue: Sometimes the CPU *can* go up to 100% (for example scrolling the list really quickly). Then everything stops working.
The IRQ handler is still called every frame, but it now takes more or less 95% CPU always, even if the list is not being scrolled. It "locks up" at 95% CPU. Also, the touch screen stops working for some reason. I can still navigate the list using the arrow keys though. And of course, since now IRQ mode takes up 95% CPU there's no CPU time left for the MP3 decoder and it hangs too
This bug is present both in DeSmuME and the real DS.
I think I know why it happens: I'm calling glFlush. According to GBATek, glFlush locks up until next Vblank. If I remove the glFlush call it doesnt lock up. Probably if the CPU goes too high, glFlush is called too late and it waits for the *next* Vblank. Then the previous Vblank IRQ is called late too, which makes the next glFlush wait for the *next* vblank again
So, how can I prevent this from happening? Is there a reliable way to do this?
But I have a problem: calls to the MP3 player take much more than 1/60th of a second so I can't animate the UI properly. I've tried to modify the MP3 decoder to decode smaller chunks at a time so it can keep up with the UI but no luck. Probably because doing things in smaller chunks makes the cache less effective.
So I thought of leaving the MP3 code on main() and update the UI in the Vblank IRQ handler. Here's what my code looks like right now (showing only the relevant stuff):
Code: Select all
int inputKeysDown;
int inputKeysHeld;
int inputKeysRepeat;
touchPosition inputTouch;
stack<Scene*> scenes;
void vBlank()
{
if(scenes.size() == 0)
{
fprintf(stderr, "[IRQ] No scene found!\n");
return;
}
timerStart(2, ClockDivider_256, 0, NULL);
fprintf(stderr, "[IRQ] entering\n");
bgUpdate();
scanKeys();
touchRead(&inputTouch);
inputKeysDown = keysDown();
inputKeysHeld= keysHeld();
inputKeysRepeat = keysDownRepeat();
Scene* sc = scenes.top();
sc->tick();
sc->render();
//Draw BG image
setTexture(0);
glPolyFmt(POLY_ALPHA(31) | POLY_CULL_NONE);
GFX_COLOR = RGB15(31, 31, 31);
glBegin(GL_QUAD);
GFX_TEX_COORD = (TEXTURE_PACK(inttot16(0), inttot16(0)));
glVertex3v16(0, 0, -600 );
GFX_TEX_COORD = (TEXTURE_PACK(inttot16(256), inttot16(0)));
glVertex3v16(256, 0, -600 );
GFX_TEX_COORD = (TEXTURE_PACK(inttot16(256), inttot16(192)));
glVertex3v16(256, 192, -600 );
GFX_TEX_COORD = (TEXTURE_PACK(inttot16(0), inttot16(192)));
glVertex3v16(0, 192, -600 );
glEnd();
glFlush(GL_TRANS_MANUALSORT);
//2181 = TIMER_HZ / 256 / 60 = Number of time ticks occurring in 1/60th second
int dd = timerTick(2)*100/2181;
printf("\x1b[11;1HCPU_IRQ: %d %% ", dd);
printf("\x1b[13;1H3D Scanline Buffer: %d ", (*(vu32*) 0x04000320) );
printf("\x1b[14;1HVertex RAM: %d ", GFX_VERTEX_RAM_USAGE);
printf("\x1b[15;1HPolygon RAM: %d ", GFX_POLYGON_RAM_USAGE);
struct mallinfo inf = mallinfo();
printf("\x1b[8;1HRAM %d ", inf.uordblks);
fprintf(stderr, "[IRQ] exiting\n");
fprintf(stderr, "[IRQ] Total time %d\n", dd);
timerStop(2);
}
int main(int argc, char *argv[])
{
(...)
irqSet(IRQ_VBLANK, vBlank);
irqEnable(IRQ_VBLANK);
while(true)
{
if(globalPlayer)
globalPlayer->update();
(...)
swiWaitForVBlank();
}
return 0;
}
It measures CPU related to the 60Hz screen refresh rate: 100% would mean it's taking exactly 1/60th of a second. Usually it is at CPU 2%. When you scroll the list and it has to render some files it goes up to 30% or so.
BUT There's ONE issue: Sometimes the CPU *can* go up to 100% (for example scrolling the list really quickly). Then everything stops working.
The IRQ handler is still called every frame, but it now takes more or less 95% CPU always, even if the list is not being scrolled. It "locks up" at 95% CPU. Also, the touch screen stops working for some reason. I can still navigate the list using the arrow keys though. And of course, since now IRQ mode takes up 95% CPU there's no CPU time left for the MP3 decoder and it hangs too
This bug is present both in DeSmuME and the real DS.
I think I know why it happens: I'm calling glFlush. According to GBATek, glFlush locks up until next Vblank. If I remove the glFlush call it doesnt lock up. Probably if the CPU goes too high, glFlush is called too late and it waits for the *next* Vblank. Then the previous Vblank IRQ is called late too, which makes the next glFlush wait for the *next* vblank again
So, how can I prevent this from happening? Is there a reliable way to do this?