It is currently Sat Sep 21, 2019 7:30 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 11 posts ] 
Author Message
PostPosted: Fri Apr 05, 2019 6:43 pm 
Offline

Joined: Sun Jun 30, 2013 7:59 am
Posts: 41
I've been reading about the NMI/vblank/frames, and I came across this quote from https://wiki.nesdev.com/w/index.php/The_frame_and_NMIs :

"Now, the drawing code I'm talking about so far is stuff that is to be drawn while rendering is enabled. In your program, you can disable rendering (aka, "turn off the PPU") via $2001 (PPUMASK). If rendering is disabled, it is perfectly sane to perform drawing code at any time (even during rendering time). This, however, does not stop that clock hand from moving. Even when 'disabled'. The PPU is still moving in and out of VBlank, and NMIs will still be generated (if enabled)."

Now, I imagine at the start of your game, you're going to have NMIs and rendering disabled. Then, before enabling them, you'd set up what needs to be drawn for the first screen before the game starts proper. Then, you'd enable NMI and rendering, allowing the first screen to be drawn, and start processing the game.

However, I'm a rather confused about something regarding the above quote. After setting up the first screen, if we then enable NMI and rendering, doesn't that mean the "minute hand" could be anywhere? Say, as soon as you enable NMI and rendering after the first screen is setup, couldn't the NMI fire in the middle of handling the input/what to draw for the next screen? I'm really struggling to explain this, as you can no doubt tell, but it's bugging me to no end. Is this why you'd have some kind of "next screen ready" variable, which says whether there is another screen ready to be drawn?

Apologies for the haphazard nature of the question. I'm attempting to start trying to figure this out again after a hiatus, and I was struggling before the hiatus :lol: Trying to get an intuitive understanding of how the NMI interweaves and communicates with the main thread is something I'm finding very difficult, but I want to try and understand that as best I can first.

Thanks.

P.S. sorry for any terminology mix-ups between NMI, vblank, frame and such. Still getting my bearings.


Top
 Profile  
 
PostPosted: Fri Apr 05, 2019 6:52 pm 
Offline

Joined: Sun Apr 13, 2008 11:12 am
Posts: 8568
Location: Seattle
CrowleyBluegrass wrote:
After setting up the first screen, if we then enable NMI and rendering, doesn't that mean the "minute hand" could be anywhere?
[...]
Say, as soon as you enable NMI and rendering after the first screen is setup, couldn't the NMI fire in the middle of handling the input/what to draw for the next screen?
[...]
Is this why you'd have some kind of "next screen ready" variable, which says whether there is another screen ready to be drawn?
To all three of your questions: Yup.

The PPU does provide the CPU the ability to ask if an NMI would occur, so this doesn't require that you use the "NMI just sets a flag and returns" programming convention.


Top
 Profile  
 
PostPosted: Fri Apr 05, 2019 7:30 pm 
Offline
User avatar

Joined: Thu Mar 31, 2016 11:15 am
Posts: 529
The NMI and game loop are typically kept in sync with a variable or two. Usually it works like this:

Code:
nmi_handler:
  inc nmi_counter
  rti


game_loop:
  lda nmi_counter
wait:
  cmp nmi_counter
  beq wait
  ; Put your game loop code here.
  jmp game_loop


Top
 Profile  
 
PostPosted: Fri Apr 05, 2019 7:55 pm 
Offline

Joined: Sun Jun 30, 2013 7:59 am
Posts: 41
Thank you both for the clarification :)

I guess what's tripping me up is the idea of having the first screen setup already. I had in mind something like:
1. Fill buffer consisting of very first screen (e.g. title screen)
2. Enable NMI
3. Wait for NMI to increment flag
6. NMI happened, first screen drawn from buffer.
7. We now enter Main loop, which starts processing input, filling buffer and setting draw_ready flags. At the end of the main code, we wait in a loop for NMI to increment flag
7. In NMI, increment counter. Check for draw_ready flag and update screen if set. RTI.
8. goto 7

Does that sound about about right? Apologies for putting this in writing instead of code, but I'd really like to try and get as much theoretical understanding of this as possible first before making all of the necessary mistakes ;)

Thanks again.


Top
 Profile  
 
PostPosted: Fri Apr 05, 2019 8:08 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11412
Location: Rio de Janeiro - Brazil
pubby wrote:
Code:
nmi_handler:
  inc nmi_counter
  rti


game_loop:
  lda nmi_counter
wait:
  cmp nmi_counter
  beq wait
  ; Put your game loop code here.
  jmp game_loop

Most games do not use this kind of minimalist NMI handler, though. It's more common to have the NMI handler still perform a few critical tasks even during lag frames (i.e. when vblank starts but the CPU hasn't finished processing the frame's logic), such as playing music or doing raster effects. If that's not done, the music will slow down whenever there are lag frames, and raster effects like status bars will glitch (causing flicker or jitter).


Top
 Profile  
 
PostPosted: Fri Apr 05, 2019 8:40 pm 
Offline
User avatar

Joined: Thu Mar 31, 2016 11:15 am
Posts: 529
CrowleyBluegrass wrote:
I guess what's tripping me up is the idea of having the first screen setup already. I had in mind something like:
1. Fill buffer consisting of very first screen (e.g. title screen)
2. Enable NMI
3. Wait for NMI to increment flag
6. NMI happened, first screen drawn from buffer.
7. We now enter Main loop, which starts processing input, filling buffer and setting draw_ready flags. At the end of the main code, we wait in a loop for NMI to increment flag
7. In NMI, increment counter. Check for draw_ready flag and update screen if set. RTI.
8. goto 7

Does that sound about about right? Apologies for putting this in writing instead of code, but I'd really like to try and get as much theoretical understanding of this as possible first before making all of the necessary mistakes ;)

That sounds about right.

Note that you don't necessarily need a 'draw_ready' flag. It's only necessary if lag frames can mess up your rendering.


Top
 Profile  
 
PostPosted: Fri Apr 05, 2019 11:56 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 7582
Location: Canada
pubby's suggestion is the simplest way, though it's not my preferred way either (for the reasons tokumaru outlaid)

In general I try to:
  • Turn NMI on once at the start of the program and never turn it off.
  • Use a variable to tell NMI when it is allowed to send updates to the PPU.
  • Never turn rendering on and off ($2001) outside of NMI. If I want to turn off rendering to do some bulk nametable loads or something, I will use a variable to signal that the NMI handler should turn it off, then wait for that NMI to execute and finish before proceeding.
  • Always run music updates in the NMI, so they can never slow down.

Except in very special situations, I usually avoid turning NMI off at all. Aside from being able to continue playing music during screen transitions, it avoids some problems, like if you happened to enable NMI in the middle of vblank, you'd trigger the interrupt immediately and your NMI handler will run without having enough time to finish its update before vblank ends. There's a few other tricky things like that which make me prefer to leave it on.


Top
 Profile  
 
PostPosted: Sat Apr 06, 2019 5:55 am 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 21595
Location: NE Indiana, USA (NTSC)
rainwarrior wrote:
Always run music updates in the NMI, so they can never slow down.

How do you approach saving and restoring the state of the mapper in order to switch the music code and data, particularly ones with nontrivial state such as MMC1? Or locking the music state to avoid a data race when the NMI interrupts a command that starts a song or a sound effect?

_________________
Pin Eight | Twitter | GitHub | Patreon


Top
 Profile  
 
PostPosted: Sat Apr 06, 2019 6:46 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11412
Location: Rio de Janeiro - Brazil
tepples wrote:
How do you approach saving and restoring the state of the mapper in order to switch the music code and data, particularly ones with nontrivial state such as MMC1?

The MMC1 is pure evil, so I avoid it like the plague, but I suppose there are a couple of ways to deal with that:

1- if you have a FrameCounter variable that you increment in your NMI handler, you can use that to detect when a serial MMC1 write is interrupted by the NMI handler (i.e. the value of FrameCounter at the end of the serial write doesn't match the value from before the serial write began), and you can repeat the write.

2- use a variable to signal when serial mapper writes begin and end, so that the NMI handler can detect if it interrupted one, in which case it can perform the intended mapper operation (the desired state must be stored in variables), and manipulate the return address to skip the rest of the interrupted mapper write. This is a bit too convoluted IMO... Personally, if probably go with the first option.

Quote:
Or locking the music state to avoid a data race when the NMI interrupts a command that starts a song or a sound effect?

I guess you can communicate with the sound engine via a message system, rather than immediately changing its state. You'd only commit messages that are fully formed, preventing the engine from dealing with inconsistent data. The sound engine would only modify its state in its "update" routine (called from the NMI handler), at which point it'd process all the requests to start/end songs/effects, and everything else the main code can possibly request it to do.


Top
 Profile  
 
PostPosted: Sat Apr 06, 2019 10:27 am 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 7582
Location: Canada
tepples wrote:
rainwarrior wrote:
Always run music updates in the NMI, so they can never slow down.

How do you approach saving and restoring the state of the mapper in order to switch the music code and data, particularly ones with nontrivial state such as MMC1? Or locking the music state to avoid a data race when the NMI interrupts a command that starts a song or a sound effect?

I think this is getting off topic, but...

There are a number of ways to solve that problem, but there's a good one at: Wiki: Programming MMC1: Interrupts

As far as "locking the state", why do you need anything more than a single byte (automatically atomic) to signal the start of a song or sound effect? There is no additional state to lock here.


Top
 Profile  
 
PostPosted: Sat Apr 06, 2019 11:01 am 
Offline

Joined: Fri Feb 24, 2012 12:09 pm
Posts: 1009
The NMI request flag is in 2002h.bit7. That flag gets set at begin of vblank, and gets cleared at end of vblank, and also gets cleared when mnually reading 2002h. NMI enable is in 2000h.bit7. And NMIs get thrown when the condition "request AND enable" changes from 0 to 1.

That brings up several corner cases, up to including firing more than one NMI per vblank (when repeatedly disabling/enabling NMIs during vblank). Basically, one should read 2002h before enabling NMIs (to acknowledge any old NMI flag, that ensures that the next NMI will occur at BEGIN of next vblank).

And thereafter, yes, NMIs should be kept permanently enabled (unless you are intentionally doing uncommon things and have a very good reason for needing to disable NMIs).


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 11 posts ] 

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 2 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group