So, this is a subtle thing but it's driving me crazy. I know without much more, it'll likely be hard to answer, but curious if anyone has run into this before.
I am using FamiTone, and have my FamiTone update at the end of the NMI. Ironically, on my screen transitions, it seems to 'skip forward' a beat...not lag behind, but actually, when the screen loads, it is a beat or so ahead.
Anyone ever run into this?
[Subject Fairy was here]
Only guess: NMI can be triggered multiple times for each vblank if you don't read from $2002 during your NMI handler and your NMI handler finishes in less than 20 scanlines you toggle $2000.7 anywhere before the NMI flag is cleared by hardware.
Ha! Now I'm glad I asked. Added a read of $2002 during the NMI and it seems to have fixed the problem completely!
That was easy. Good instinct! haha
Fascinating---I'd never heard of this issue. I do not read $2002 in any of my vblank routines and I have yet to run into this issue (assuming it is not unique to FamiTone, I do not use it), nor had I spotted it being discussed anywhere here or on the wiki. Can anyone go into more detail on this? Makes me concerned I'm avoiding possible bugs by coincidence
Writing to $2000 operates mask on the NMI flag (via bit 7).
The only way to clear the NMI flag is by reading $2002, or waiting for the end of vblank to occur.
If you write a 0 to bit 7 of $2000, the NMI flag is masked, outputting a 0 instead of its current state.
If you write a 1 to bit 7 of $2000, the NMI flag is unmasked, outputting its current state, which can be 0 or 1.
The NMI interrupt is triggered whenever the NMI signal goes from 0 to 1, so "manually" unmasking it during vblank causes a new NMI interrupt to trigger.
You can find the exact logic that rainwarrior talks about in Visual2C02:
/NMI externally is NOT node _int (via three inverters)
node _int is node 1014 NOR node 5731
node 1014 is NOT vbl_flag
node 5731 is NOT enable_nmi
(By deMorgan's laws, that's equivalent to: /NMI is vbl_flag NAND enable_nmi )
In my game right now, at the end of vblank, I set the vram address with two writes to $2006 and then set the scroll with two writes to $2005. I don't ever read $2002. I also use $2006 sometimes when loading graphics with bg and sprites turned off (I use mapper 2 and chr-ram). When I do so---I always change my vblank routine to a "no-op" and wait for vblank to end (*edit* wait for a SHORT vblank to return BEFORE the end of the vblank period, forgive me for not being in the habit of precise language) before doing a lot of writes to VRAM. So I'm wondering, because I've been careful in every other way, have I got it set up so that $2006 is always writing to the correct hi/lo register whenever I use it? I noticed on the wiki reading $2002 says it resets the latch that determines which lo or hi byte of $2006 gets written to, guaranteeing you're starting over again. While I spent as little as time as possible studying the hardware carefully while building my first two games, I'm now genuinely interested in absorbing more details like this. I just wish I understood why I haven't run into any issues with this since it appears that reading $2002 is important to do during nmi.
Maybe this is why. The wiki (Disch's nmi article in particular) says: "Once you turn NMIs off, it is very easy to forget to turn them back on. What's worse, if you forget to read $2002 and you turn them on in the middle of VBlank, it will immediately trigger an NMI and cause your handler to run past the end of VBlank, starting all sorts of havoc."
I never turn NMI off...could this be why I haven't run into this issue? Joe do you ever turn nmi off? I don't think it's wrong to do one or the other, but it looks like the lesson here is if you ever turn it off, you need to be reading $2002 in your nmi?
rainwarrior wrote:
Writing to $2000 operates mask on the NMI flag (via bit 7).
The only way to clear the NMI flag is by reading $2002, or waiting for the end of vblank to occur.
If you write a 0 to bit 7 of $2000, the NMI flag is masked, outputting a 0 instead of its current state.
If you write a 1 to bit 7 of $2000, the NMI flag is unmasked, outputting its current state, which can be 0 or 1.
The NMI interrupt is triggered whenever the NMI signal goes from 0 to 1, so "manually" unmasking it during vblank causes a new NMI interrupt to trigger.
Are you saying precisely what I just posted above, here?
I just leave NMIs on the whole time.
GradualGames wrote:
Are you saying precisely what I just posted above, here?
Yes, if you don't turn NMIs off, you won't have this problem.
And I've said this before, but if you
don't turn your NMIs off and are doing some kind of a bulk transfers in the "main thread" (i.e. uploading a lot of CHR data, bypassing the vblank buffers), you also have to make sure
not to blindly do a $2002 read in the NMI handler, because it could happen that the NMI starts at the exact moment when you're trying to do the PPU address writes in the main thread, messing up the even/odd latch and your transfer. It's very unlikely to happen, but when it happens, you might rip out a hair or two.
thefox wrote:
GradualGames wrote:
Are you saying precisely what I just posted above, here?
Yes, if you don't turn NMIs off, you won't have this problem.
And I've said this before, but if you
don't turn your NMIs off and are doing some kind of a bulk transfers in the "main thread" (i.e. uploading a lot of CHR data, bypassing the vblank buffers), you also have to make sure
not to blindly do a $2002 read in the NMI handler, because it could happen that the NMI starts at the exact moment when you're trying to do the PPU address writes in the main thread, messing up the even/odd latch and your transfer. It's very unlikely to happen, but when it happens, you might rip out a hair or two.
In addition to (the possibility of) an nmi happening between writes to $2006 (during the main thread, with graphics off, about to upload chr data...), I also have an indirect address for the currently active vblank routine---which I swap out during the main thread when changing game state. For any situations like this where you have a 2 byte address whose writes could be interrupted by nmi, is it better to use a third byte to act as a mutex in these situations? In general, I'm being careful to wait til the start of a new frame before doing expensive operations, but I suppose it is conceivable that some of these run long enough that another nmi could occur. Given how consistent the timing and behavior is on the NES I'm not sure in practice if it is worth it to go to this length. On the other hand, I protect all my bankswitching from being messed up by nmi (I do a bankswitch for the music engine at the end of nmi) by saving the bank I'mabout to switch to before actually switching, so if nmi interrupts, it can restore the correct bank. Prior to implementing that I DID observe some crashes. Interesting that I haven't run into crashes in these other situations. Probably because in the bankswitch case---you're doing this all the time with a huge variety of execution times during gameplay. But in the game state change case---you've got much less volatile operations going on, so these issues are a lot less likely.
I also think leaving the NMI on permanently is a good strategy. When you need to do full screen updates, you can still turn rendering off and just use a variable to tell your NMI handler not to touch the PPU while you're doing it. The big advantage of this is you can still play music seamlessly during the transition.
Here's a simplified example of how mine is structured:
Code:
nmi_handler:
; prevent re-entry from an NMI while still in the NMI handler
lda nmi_lock
beq :+
rts
:
lda #1
sta nmi_lock
; update the ppu only on request from the main thread
lda ppu_update_ready
beq ppu_update_done
; ...
; do all PPU interaction here
; OAM DMA
; Nametable/palette upload
; set scroll, etc.
lda #0
sta ppu_update_ready
ppu_update_done:
jsr music_play
; unlock
lda #0
sta nmi_lock
rts
nmi:
pha
txa
pha
tya
pha
jsr nmi_handler
pla
tay
pla
tax
pla
rti
Thanks for this, rainwarrior. I've just implemented this approach in my new project. I feel like it was the last missing piece of the puzzle to feel like I was doing all the basics truly correctly. My approach prior to this was to always wait til vblank flagged that it was done, swap out for a "no-op" nmi routine (I tend to always have one indirect address in zp for the currently active nmi routine), then proceed to modify the ppu (load chr data while graphics are turned off). It works just fine, but, you have to be more careful with that approach. This way, it's like you have a mutex for the ppu. Much nicer. It even works for swapping out an indirect address for the currently active nmi routine.
The other thing I forgot to mention is that any write to
ppu_update_ready in that example should wait for the NMI thread to empty it before proceeding, presuming that all your PPU update data is single-buffered.
Code:
finish_frame:
lda #1
sta ppu_update_ready
:
lda ppu_update_ready
bne :-
rts
rainwarrior wrote:
The other thing I forgot to mention is that any write to
ppu_update_ready in that example should wait for the NMI thread to empty it before proceeding, presuming that all your PPU update data is single-buffered.
Code:
finish_frame:
lda #1
sta ppu_update_ready
:
lda ppu_update_ready
bne :-
rts
That, I've had down for a long time. I just felt uncomfortable for quite a while knowing that it was theoretically possible for nmi to interrupt something like this (during a game state transition particularly, where I need new ppu write code say for scrolling or printing strings etc.):
Code:
;my old, unprotected code that I had to be careful with
lda #<my_nmi_routine
sta my_indirect_nmi_address_in_zp
;what if NMI happens here? oops! bogus address
lda #>my_nmi_routine
sta my_indirect_nmi_address_in_zp+1
nmi:
;save regs
jsr jump_indirectly_to_the_current_nmi_routine
;...restore regs
rti
jump_indirectly_to_the_current_nmi_routine:
jmp (my_indirect_nmi_address_in_zp)
Your solution is the most elegant way I've yet seen to prevent that and similar issues. I'm using the lock for both ppu writes and my indirect nmi address.