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 > Tracking down graphics glitches

#74051 - edwdig - Thu Mar 02, 2006 12:27 am

I've got a glitch in my game that's happening randomly and very infrequently. Sometimes you can play for 10 minutes and not see it happen, other times it'll happen once a minute or so.

As far as I can tell, what's happening is my HDMA doesn't run for one frame. It's also possible that I'm somehow completely skipping an entire frame. The screen turns to garbage for what I'm assuming is 1 frame, then everything continues as if nothing happens. If you're only half paying attention to the game then you won't even notice anything happened.

I'm using HDMA to reprogram the BGxCNT registers. My backgrounds tend to use all the available tile memory. I change BGxCNT partway through the screen drawing to change the characer base block. I originally used interrupts to change it, but switched to HDMA in hopes that it would solve the bug. Didn't make a significant difference.

Using the change the background color trick, my CPU usage tends to peak at about 1/2 the CPU power, so my processing isn't spilling over into the next frame.

I'm using Krawall (non-commercial edition) for my music & sfx. As far as I can tell the sound isn't glitching when the graphics do.

I also did another test to see if I'm somehow losing VBlank interrupts. I created a VBlank handler that simply sets a variable to one. My main loop resets that variable to zero when the CPU wakes up after VBlank. Before I start the VBlank wait, I check if the variable is one and print a debugging message if so. This should tell me if a VBlank occured while my game code was running. The only time I seem to be missing VBlanks is while loading a level, which isn't an issue.

Any thoughts on how I can track this down? As far as I can tell, either a VBlank is getting eaten and never detected by anything, or HDMA just fails once every thousand times or so.

#74053 - sajiimori - Thu Mar 02, 2006 12:55 am

I'd suggest testing some extreme situations -- use a lot of CPU during vdraw, use almost no CPU during vdraw, spend a lot of time in the hblank interrupt or almost no time, and do the same for vblank.

Maybe a certain test will cause the problem to happen much more frequently, which would make it easier to troubleshoot.

Someone else might have some more concrete ideas...

#74084 - edwdig - Thu Mar 02, 2006 5:43 am

If it helps at all, this is the interrupt code I'm using. I think it's based off something I found in devkitARM, but I don't really remember anymore. Back when I used devKitAdvance I used the crt0.s that came with the Krawall examples and turned on multiple interrupts. Didn't really make a difference.

Code:

   .section   .iwram,"ax",%progbits
   .extern VSyncClear
   .code 32

   .global   IntrMain
   .global
@---------------------------------------------------------------------------------
IntrMain:
@---------------------------------------------------------------------------------
                           @ Single interrupts support
   mov      r3,   #0x4000000         @ REG_BASE
   ldr      r2,   [r3,#0x200]         @ Read   REG_IE
   and      r1,   r2,   r2,   lsr   #16      @ r1 =   IE & IF

   ldrh   r2, [r3, #-8]         @\mix up with BIOS irq flags at 3007FF8h,
   orr      r2, r2, r1            @ aka mirrored at 3FFFFF8h, this is required
   strh   r2, [r3, #-8]         @/when using the (VBlank)IntrWait functions

   add      r3,r3,#0x200

   ands   r0, r1, #1
   cmp      r0, #1
   bne      not_vblank
   ldr      r0, =VSyncClear
   str      r1, [r0]

not_vblank:
   ands   r0, r1, #16
   cmp      r0, #16
   bne      no_handler
   strh   r1, [r3, #2]
   ldr      r0,=kradInterrupt
   bx      r0

@---------------------------------------------------------------------------------
no_handler:
@---------------------------------------------------------------------------------
   strh   r1,   [r3, #2]         @ IF Clear
   mov      pc,lr

@---------------------------------------------------------------------------------
jump_intr:
@---------------------------------------------------------------------------------
   strh   r0,   [r3, #2]         @ IF Clear

   mov      r0,lr

   ldr      r0,   [r2]            @ Jump   to user   IRQ   process
   bx      r0

   .pool
   .end

#74093 - DekuTree64 - Thu Mar 02, 2006 9:57 am

Couple things about that interrupt handler...
Code:
   ands   r0, r1, #1
   cmp      r0, #1
   bne      not_vblank
   ldr      r0, =VSyncClear
   str      r1, [r0]

Note that r1 is still REG_IF & REG_IE at this point, and you're storing it in VSyncClear. Bit0 must be set since you check for it, but there could be more bits set too, if there are multiple interrupts waiting for processing at once. Might be better to store r0 in VSyncClear, since it's just been checked to be equal to 1. I doubt this causes any trouble though.

Also, it could be optimized to
Code:
   ands   r0, r1, #1
   ldrne      r0, =VSyncClear
   strne      r1, [r0]

You can do similar conditional execution for the kradInterrupt check/call too.

Code:
@---------------------------------------------------------------------------------
jump_intr:
@---------------------------------------------------------------------------------
   strh   r0,   [r3, #2]         @ IF Clear

   mov      r0,lr

   ldr      r0,   [r2]            @ Jump   to user   IRQ   process
   bx      r0

Did you leave out part of the code? I don't see anywhere that it will actually get here. That mov r0, lr is kind of pointless since r0 is overwritten right after. Also, r2 is never set to any sort of valid address to load from...


Anyway, I would suspect the kradInterrupt being the culprit, since it's running on a timer, and usually takes a long time. Seems like your VBlank miss check would catch it if it was causing a problem though. Maybe try disabling it and see if the glitch ever happens.

Also, I'd recommend starting the HDMA in a VBlank interrupt handler, rather than hoping your game always runs fast enough. I don't think that's the real problem either, but safer in general.
_________________
___________
The best optimization is to do nothing at all.
Therefore a fully optimized program doesn't exist.
-Deku

#74131 - edwdig - Thu Mar 02, 2006 6:09 pm

DekuTree64 wrote:
Couple things about that interrupt handler...
Code:
   ands   r0, r1, #1
   cmp      r0, #1
   bne      not_vblank
   ldr      r0, =VSyncClear
   str      r1, [r0]

Note that r1 is still REG_IF & REG_IE at this point, and you're storing it in VSyncClear. Bit0 must be set since you check for it, but there could be more bits set too, if there are multiple interrupts waiting for processing at once. Might be better to store r0 in VSyncClear, since it's just been checked to be equal to 1. I doubt this causes any trouble though.


I originally used r1 before I considered the possibility of multiple interrupts occuring at once. Doesn't really matter though, as my C code simply does "if (VSyncClear)" or "if (!VSyncClear)".

DekuTree64 wrote:

Also, it could be optimized to
Code:
   ands   r0, r1, #1
   ldrne      r0, =VSyncClear
   strne      r1, [r0]

You can do similar conditional execution for the kradInterrupt check/call too.


Just tried that out. Got tons of messages about missed VBlank's that way.

DekuTree64 wrote:

Code:
@---------------------------------------------------------------------------------
jump_intr:
@---------------------------------------------------------------------------------
   strh   r0,   [r3, #2]         @ IF Clear

   mov      r0,lr

   ldr      r0,   [r2]            @ Jump   to user   IRQ   process
   bx      r0

Did you leave out part of the code? I don't see anywhere that it will actually get here. That mov r0, lr is kind of pointless since r0 is overwritten right after. Also, r2 is never set to any sort of valid address to load from...


It's a block of code that I should've removed. The original code I based this off used a table of function pointers for interrupt handlers. I removed that as I don't need that level of complexity. Guess I left a couple stray lines of code around by mistake.

DekuTree64 wrote:

Anyway, I would suspect the kradInterrupt being the culprit, since it's running on a timer, and usually takes a long time. Seems like your VBlank miss check would catch it if it was causing a problem though. Maybe try disabling it and see if the glitch ever happens.


Just did a few playthroughs with Krawall off. Didn't see any glitches. I kinda figured the problem had to do with kradInterrupt, but now the issue is how do I solve the problem? Multiple interrupts? Didn't seem to help before, but I guess it's worth another shot.

Quote:
Also, I'd recommend starting the HDMA in a VBlank interrupt handler, rather than hoping your game always runs fast enough. I don't think that's the real problem either, but safer in general.


Sounds like a decent idea. May as well try it out.

#74445 - edwdig - Sun Mar 05, 2006 6:44 am

I'm still baffled here. What exactly happens if an interrupt occurs while I'm servicing another and interrupts are off? Does the second interrupt get lost, or does it fire when the first interrupt handler finishes?

#74493 - FluBBa - Sun Mar 05, 2006 12:32 pm

edwdig wrote:

Code:

not_vblank:
   ands   r0, r1, #16
   cmp      r0, #16
   bne      no_handler
   strh   r1, [r3, #2]
   ldr      r0,=kradInterrupt
   bx      r0

According to your code if you have several interrups pending when you enter the ISR all of them will be aknowledged at the same time, meaning only the music player will get serviced. This might only happen once in 10 ten minutes or so.

Do something like this instead.
Code:

not_vblank:
   ands   r0, r1, #16
   beq      no_handler
   strh   r0, [r3, #2]
   ldr      r0,=kradInterrupt
   bx      r0

_________________
I probably suck, my not is a programmer.

#74532 - edwdig - Sun Mar 05, 2006 7:08 pm

FluBBa wrote:

According to your code if you have several interrups pending when you enter the ISR all of them will be aknowledged at the same time, meaning only the music player will get serviced. This might only happen once in 10 ten minutes or so.


The only interrupts I care about are VBlank and Timer1. This code deals with VBlank, then Timer1 at the end, so that isn't really an issue.

But, it originally was the problem. I noticed a few days ago that it was only dealing with the first interrupt it detected, and at the time Timer1 had priority over VBlank.

Solving that didn't solve the problem at first because one of my tests involved moving the placement of the Krawall worker call. As an experiment I tried calling it after waiting forVBlank instead of before. That changed the problem, and made the worker calls sometimes run into the start of the next frame, making the HDMA setup not work.

Two different approaches worked to fix it. One was to move the Krawall worker call back before the VBlank wait after ensuring that the inerrupt handler handles all pending interrupts in one pass. The other is to set up the HDMA during the VBlank handler. In the end I'm going to put both fixes in.

Thanks for the comments everyone.

I finally did track it down. What you just said was the case with my earlier code. It seems that most of the interrupt handling code out there doesn't deal well with two interrupts firing at the same time. I fixed that issue shortly before posting this. However, that didn't solve the issue because one of my earlier tests involved moving the Krawall worker calls to immediately after waiting for VBlank instead of before. I didn't realize that change was still there. In that case, it was possible that the HDMA setup wouldn't occur until after the next frame started drawing.