It is currently Mon May 20, 2019 8:17 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 10 posts ] 
Author Message
PostPosted: Wed Apr 10, 2019 9:31 am 
Offline

Joined: Sun Jun 30, 2013 7:59 am
Posts: 37
I remember reading at some point about the possibility of having several NMI handlers for different situations. How many of you utilise this? Those of you who do/have, why did you decided to utilise them?

Even just starting out, I'm starting to feel like this_flag and that_flag as a means of communicating between the main and NMI threads could start to get rather cumbersome. Maybe I'm looking for a solution in the wrong place, but it seems like multiple NMI handlers could help with that somewhat. I had in mind a jump table based on a variable that is set in main; a bit to indicate the PPU should be turned off/back on, a bit to indicate the palette should be changed, something akin to that. Sort of feel like it would be preferable to have several NMI handlers for different situations/purposes, than to have one big NMI with lots of branch conditions that ends up all spaghetti-like. Of course, you'd probably struggle to fit every possible condition in 8 bits, but I thought of this as more of a supplement to the flags, I suppose.

Probably way off mark here, but I had to ask. Thanks :)

P.S. (Would it be preferable to have a general thread of my questions instead of new ones? Not that I plan on posting more right away, but I'm making a more concerted effort to to be prepared this time before attempting to code a game proper)


Top
 Profile  
 
PostPosted: Wed Apr 10, 2019 9:37 am 
Offline

Joined: Tue Feb 07, 2017 2:03 am
Posts: 711
Yes.

Sadly the NES has the Vectors in ROM. So you make some code that you copy to RAM, JMP $XXXX then you can just change the address it jumps to to change your handler. You could set up a variable and then do an "ON X" type jump if you wanted to.. bit safer but uses more memory and clocks.


Top
 Profile  
 
PostPosted: Wed Apr 10, 2019 9:42 am 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 2505
Location: DIGDUG
You can have the NMI vector point to a JMP (indirect) and have the main program change that pointer as needed.

Or you can have the NMI just a stub that only does this...

NMI:
Inc nmi_flag
rti

And have the main program handle where to branch after a loop looking for nmi_flag to change.

Also, any mapper with swappable banks can change the NMI handler depending in what bank is in place.

_________________
nesdoug.com -- blog/tutorial on programming for the NES


Top
 Profile  
 
PostPosted: Wed Apr 10, 2019 9:46 am 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 2505
Location: DIGDUG
I think the main thing that needs to change is scrolling changes... perhaps from horizontal to vertical, and two versions that have an optimized routine for each.

_________________
nesdoug.com -- blog/tutorial on programming for the NES


Top
 Profile  
 
PostPosted: Wed Apr 10, 2019 9:48 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11345
Location: Rio de Janeiro - Brazil
I used to work with multiple NMI handlers... My NMI vector just pointed to a JMP $XXXX) instruction in RAM, and I'd change the address as needed - temporarily changing the JMP ($4C) into an RTI ($40), to avoid crashes.

Now I prefer to have a "master" NMI, which does make some decisions based on flags (update VRAM or not, enable/disable rendering, etc.), but the bulk of the customization comes from calling specialized VRAM update routines. With this master NMI handler I avoid repeating common tasks such as setting the scroll, doing a sprite DMA, calling the music engine, etc. across multiple handlers, but I can still do more specialized/optimized video updates because the first part of the NMI handler just calls whatever routines are in the "video update stack", a part of the hardware stack that I reserve for video updates.


Top
 Profile  
 
PostPosted: Wed Apr 10, 2019 9:53 am 
Offline

Joined: Sun Jun 30, 2013 7:59 am
Posts: 37
tokumaru wrote:
I used to work with multiple NMI handlers... My NMI vector just pointed to a JMP $XXXX) instruction in RAM, and I'd change the address as needed - temporarily changing the JMP ($4C) into an RTI ($40), to avoid crashes.
Please, could you elaborate on this? I'm afraid it went over my head...
Would you change it to a RTI in the event that you didn't want the NMI handler to do anything, and just return directly to main? Say, if you were just spinning waiting for time to pass? Thanks.

tokumaru wrote:
Now I prefer to have a "master" NMI, which does make some decisions based on flags (update VRAM or not, enable/disable rendering, etc.), but the bulk of the customization comes from calling specialized VRAM update routines. With this master NMI handler I avoid repeating common tasks such as setting the scroll, doing a sprite DMA, calling the music engine, etc. across multiple handlers, but I can still do more specialized/optimized video updates because the first part of the NMI handler just calls whatever routines are in the "video update stack", a part of the hardware stack that I reserve for video updates.
So, you have a "master" NMI which does all of the generic things an NMI does (controlled via flags), to save repeating them over multiple handlers and you have a stack which contains updates in the form of actual code to run, not just graphical data?

P.S. I believe it was one of your posts that first introduced me to the idea in the first place, so it's nice to get your updated viewpoint on the matter :)


Top
 Profile  
 
PostPosted: Wed Apr 10, 2019 10:12 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11345
Location: Rio de Janeiro - Brazil
That is mostly to avoid crashes in case an NMI fires while you're changing the address: an address is two bytes, so if an NMI fires after you change the first byte but before you change the second one, the CPU will jump to an invalid location and crash. You can avoid this by disbling NMIs via register $2000 when changing the address, but that is slightly slower (you need to load the other $2000 settings from RAM) and has a few gotchas (visual glitches if done at certain times), so it's best to change the JMP into an RTI instead, and not mess with the PPU at all. The opcode for JMP absolute is $4C, while the opcode for RTI is $40, so you can do the change in a single write and there's no way the CPU will try to run invalid code. The RTI in RAM basically disables NMIs, so you could use it for the purposes you mentioned too.

If you use a JMP indirect instruction in ROM instead, like has also been suggested, crashes are also possible while the address is being changed, and since the instruction is in ROM, the only way to do this safely is to disable NMIs via $2000, which has its own problems, like I said above. IMO, the other method (JMP absolute in RAM) is better and faster (3 cycles vs. 5), and only costs one more byte of RAM.


Top
 Profile  
 
PostPosted: Wed Apr 10, 2019 1:15 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 7459
Location: Canada
I have a one byte variable to signal the NMI that there is work to be done. The value of the variable tells it what needs to be done. Here's an example set of different modes I might have:
  • 0 = do nothing
  • 1 = turn on rendering, upload palette and OAM
  • 2 = turn off rendering
  • 3 = 1 + 32 bytes horizontal
  • 4 = 1 + 64 bytes horizontal
  • 5 = 1 + 30 bytes vertical
  • 6 = 1 + 30 bytes vertical + 30 bytes vertical on next column
  • 7 = 1 + 4 bytes to replace a single tile + 1 byte of attribute replacement
  • 8 = 1 + arbitrary number of 3 byte packets of address + byte to write

(Probably all of these modes also execute the music playback routine after doing the PPU stuff.)

So, the NMI handler checks this byte and takes an appropriate action. If needed I could check for the case that takes longest to execute first, so that the time spent checking against the enumerated cases doesn't eat into its vblank time. (The few extra cycles of dispatch time isn't a problem for the ones that have shorter execution. They can spare it.)

The NMI handler sets this variable back to 0 after the upload is completed. The main thread is only allowed to set it to a non-zero value (signals work to be done), and the NMI handler is only allowed to return it to zero (signals work complete). Once the main thread writes this value, it will not try to write it again until it reads back as 0. This makes synchronization easy to achieve.

Typically the main thread finishes preparing data for a frame, sets the NMI handler control variable (non-zero), then enters a loop ti wait for it to return to 0 before preceding.


Some example code, the version of this paradigm I used for Lizard: main.s: nmi_handler

The main thread interactions are a small collection of helper subroutines: main.s: render_frame, render_row, render_double...

There is some extra stuff going on in that example, but the control variable is called "nmi_ready", and you can follow that to see how it is used.


Top
 Profile  
 
PostPosted: Wed Apr 10, 2019 9:10 pm 
Offline

Joined: Sun Jun 30, 2013 7:59 am
Posts: 37
That's exactly the kind of information I needed for this to click! Thanks so much, that helps a lot.


Top
 Profile  
 
PostPosted: Thu Apr 11, 2019 9:53 am 
Offline
User avatar

Joined: Fri Nov 24, 2017 2:40 pm
Posts: 153
My NMI handler is pretty generic too. I build a command buffer of functions to run (copy data to PPU in various ways, set the increment direction, etc). I use the [url]popslide[/url] to call the functions and pass data to them. It ends up being pretty simple and compact.

Other than that, it just checks a flag to see if the CPU has a frame ready, sets the mask/ctrl registers, executes the command buffer, copies OAM, and (always) calls the sound driver. All of the flexibility is through the command buffer functions.

https://github.com/slembcke/critical-ma ... /nmi.s#L47


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

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 3 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