Why not read controllers in NMI?

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.

Moderator: Moderators

nocash
Posts: 1405
Joined: Fri Feb 24, 2012 12:09 pm
Contact:

Re: Why not read controllers in NMI?

Post by nocash »

On a bigger computer, one should handle user input in irq/nmi handler and write new keystrokes to a keyboard queue. Else, it would be pretty frustating if the software would ignore all keystrokes or button clicks that had occurred while the main program was busy with things like command-execution, disk-loading or file-decompression.
On the NES, it doesn't really matter - mostly because there are no slow mechanical disk drives, and smoothly working games are per design fast and responsive enough to handle user input in main program.
In general, I would say that it can be better to handle user input on irq/nmi level (but, out of laziness, I tend to do it in main program).

For the argument about state changing unexpectedly during game logic, almost everybody has already pointed out that that could be solved by using a local copy, so that's a non-issue. When not using a local copy, I am sure that one could implement the same problem with and without nmi-based reading...

Code: Select all

if read_buttons<>0
  start_game
elseif read_buttons=0
  do_not_start_game
else
  program_has_failed
homepage - patreon - you can think of a bit as a bottle that is either half full or half empty
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Why not read controllers in NMI?

Post by tepples »

nocash wrote:On a bigger computer, one should handle user input in irq/nmi handler and write new keystrokes to a keyboard queue.
[...]
For the argument about state changing unexpectedly during game logic, almost everybody has already pointed out that that could be solved by using a local copy, so that's a non-issue.
Synchronization while moving an event from such a queue into such a local copy might prove tricky, which is a big part of why I started the thread in the first place. Apparently on some other platforms with IRQ but no NMI, synchronization is done by disabling all interrupts, reading the tail element, moving the tail, and reenabling interrupts. This causes the interrupts to fire later, once they are reenabled.
nocash
Posts: 1405
Joined: Fri Feb 24, 2012 12:09 pm
Contact:

Re: Why not read controllers in NMI?

Post by nocash »

For keyboard queues, I would use a write-pointer (updated by NMI handler), and a read-pointer (updated by main program). That should work without needing to disable NMI's (unless when using 16bit pointers on an 8bit cpu). But a queue is needed only if the main program is too slow to sense all press/release events at any time - but does still require not to miss those events.

A simple game could get away with the raw button state (without queue, and without distinguishing between clicks and doubleclicks). Just reading the raw state into accumulator should be problem: "a=[nmi_buttons]", or copy it to a separate variable "[buttons]=[nmi_buttons]". The only problem would be reading [nmi_buttons] multiple times (once to process fire button, and once again if the program had forgotten if it had processed the fire button (code working that way would be a bit dubious)).

But yeah, NMI handling needs to be more robust, and the main program needs to be aware that NMI handler can change things in background. In so far it's easier and perhaps more fool-proof to read buttons without NMI.
homepage - patreon - you can think of a bit as a bottle that is either half full or half empty
User avatar
za909
Posts: 249
Joined: Fri Jan 24, 2014 9:05 am
Location: Mijn hart woont al in Nederland

Re: Why not read controllers in NMI?

Post by za909 »

Controller reads in NMI seemed like a natural thing for me to do. Since I use the DMC interrupt for scroll splits, there is no reason to have the DMC run during VBlank, so I'm free to stop it, read the controller in NMI in a consistent number of cycles this way (no need for DMC glitch fix) and restart the DMC at the end of VBlank again for timing (when sprite0 is cleared).
Pokun
Posts: 2681
Joined: Tue May 28, 2013 5:49 am
Location: Hokkaido, Japan

Re: Why not read controllers in NMI?

Post by Pokun »

FrankenGraphics wrote:
The DMC fix is very advanced, and I'm not sure it's fully PAL compatible either.
The theory behind is in-depth but the implementation is plug and play, provided there's nothing in your game that makes controller reads right after OAMDMA directly unsuitable.

the PAL version doesn't have the dmc bug (it was fixed), so it should work either way you do it, rahsennors' cycle alignment fix or not. There might be problems with several emulators, though - the even/odd observation was only highlighted a few years ago and emulators might not have the bug correctly implemented. Then again, you need games that use the dmc fix to create incentive to fix emulation inaccuracies.
Problems with testing it on emulators is one reason I find this solution very peculiar and only works in some situations. Doesn't it require the OMA-DMA to come last in vblank? PAL requires OAM-DMA to execute early in vblank, forcing you to use two different rendering routines for NTSC and PAL compatibility. And it seems there are limitations of how the actual controller reading routine may look like which would further complicate the matter. The wiki says all the reads must be spaced an even number of cycles apart.
supercat
Posts: 161
Joined: Thu Apr 18, 2019 9:13 am

Re: Why not read controllers in NMI?

Post by supercat »

za909 wrote:Controller reads in NMI seemed like a natural thing for me to do. Since I use the DMC interrupt for scroll splits, there is no reason to have the DMC run during VBlank, so I'm free to stop it, read the controller in NMI in a consistent number of cycles this way (no need for DMC glitch fix) and restart the DMC at the end of VBlank again for timing (when sprite0 is cleared).
As I've posted elsewhere, using the DMC for scroll splits is a good reason to leave it running all the time, but also a good reason to do the controller reading within the DMC interrupt handler.
User avatar
FrankenGraphics
Formerly WheelInventor
Posts: 2064
Joined: Thu Apr 14, 2016 2:55 am
Location: Gothenburg, Sweden
Contact:

Re: Why not read controllers in NMI?

Post by FrankenGraphics »

pokun wrote:Doesn't it require the OMA-DMA to come last in vblank?
It's not a requirement, as far as i remember. There's the inconvenience that you you get the order
OAMDMA, controller reads, other graphics updates, music.
rather than
OAMDMA, other graphics updates, controller reads, music.

for an update order that works on both systems.

So, mostly you're trading a bit of gain in faster execution for the loss of putting things in the most convenient order; in regards to what *needs* to be updated within the vblank (graphics, chiefly).
This would be a sizeable chunk of cycles saved if you're reading 2 controllers (or more).

and it seems there are limitations of how the actual controller reading routine may look like
hmm... to what ends do you want to alter the example code? Removing 1 of the 2 controllers is simple enough; it still aligns correctly.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Why not read controllers in NMI?

Post by tepples »

FrankenGraphics wrote:There's the inconvenience that you you get the order
OAMDMA, controller reads, other graphics updates, music.
rather than
OAMDMA, other graphics updates, controller reads, music.

for an update order that works on both systems.
I was thinking this:
  1. if (PAL NES) { OAM DMA }
  2. VRAM updates
  3. if (not PAL NES) { OAM DMA }
  4. controller reading if requested
  5. audio
User avatar
FrankenGraphics
Formerly WheelInventor
Posts: 2064
Joined: Thu Apr 14, 2016 2:55 am
Location: Gothenburg, Sweden
Contact:

Re: Why not read controllers in NMI?

Post by FrankenGraphics »

Yeah, as long as those vram updates are consistent or otherwise guaranteed to check out on an odd cycle so that the example code (or something equivalent) is aligned, that would work fine.

But you might have conditionals and varying lenghts of vram updates. Then it's not so simple anymore. (One might account for some compensation on branches taken/not taken using the same trick as the controller reading example, ie use indexed mode but with an index of 0 to add a cycle. Also, there's more need to watch out for the page boundary adding an off cycle).
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Why not read controllers in NMI?

Post by tepples »

In the not PAL NES case, the OAM updates immediately precede controller reading. In the PAL NES case, this defect in the DMA unit was fixed in the 2A07.
User avatar
rainwarrior
Posts: 8732
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Why not read controllers in NMI?

Post by rainwarrior »

tepples wrote:
FrankenGraphics wrote:There's the inconvenience that you you get the order
OAMDMA, controller reads, other graphics updates, music.
rather than
OAMDMA, other graphics updates, controller reads, music.

for an update order that works on both systems.
I was thinking this:
  1. if (PAL NES) { OAM DMA }
  2. VRAM updates
  3. if (not PAL NES) { OAM DMA }
  4. controller reading if requested
  5. audio
??? Why would you do this? There is no reason to have two separate orderings. PAL doesn't start its forced OAM refresh until 21 scanlines in. If an order works for NTSC's budget, it will still work for PAL.

"OAM DMA first" is just a rule of thumb, not any kind of requirement.
User avatar
FrankenGraphics
Formerly WheelInventor
Posts: 2064
Joined: Thu Apr 14, 2016 2:55 am
Location: Gothenburg, Sweden
Contact:

Re: Why not read controllers in NMI?

Post by FrankenGraphics »

tepples wrote:In the not PAL NES case, the OAM updates immediately precede controller reading. In the PAL NES case, this defect in the DMA unit was fixed in the 2A07.
lol, i forgot this little detail only a few posts after saying so :lol:
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Why not read controllers in NMI?

Post by tepples »

rainwarrior wrote:
tepples wrote:I was thinking this:
  1. if (PAL NES) { OAM DMA }
  2. VRAM updates
  3. if (not PAL NES) { OAM DMA }
  4. controller reading if requested
  5. audio
??? Why would you do this? There is no reason to have two separate orderings. PAL doesn't start its forced OAM refresh until 21 scanlines in. If an order works for NTSC's budget, it will still work for PAL.
These 21 PAL lines are as long as 19.7 NTSC lines. The difference matters for games that are right on the bubble of being able to fit all updates into vblank.

But I think I've found the key part of an answer to the previous question: Because a non-maskable interrupt complicates locking an input queue, unlike other platforms where vblank is maskable.
supercat
Posts: 161
Joined: Thu Apr 18, 2019 9:13 am

Re: Why not read controllers in NMI?

Post by supercat »

tepples wrote:But I think I've found the key part of an answer to the previous question: Because a non-maskable interrupt complicates locking an input queue, unlike other platforms where vblank is maskable.
Code which would be ill-prepared to handle an NMI may clear bit 7 of $2000 to prevent it from firing on schedule, such that if vblank happens when the system is unready the handler will either start late within the current vblank or else wait until the next one.

Further, an NMI handler can start with something like:

Code: Select all

neverMind:
    inc elapsed_frames
    rti
NMI:
    lsr nmi_lock
    bcc neverMind
    sta nmi_A
    stx nmi_X

    ...
    inc elapsed_frames ; May as well do this late in the NMI
    ldx nmi_X
    lda nmi_A
    inc nmi_lock ; Note that nmi_lock will be zero during the handler, and nmi_A is not live here!
    rti
and then enable the main NMI handler by writing 1 into nmi_lock. Any vblank that triggers an nmi before code writes 1 to nmi_lock will burn 27 cycles and update the elapsed_frames counter but have no other side-effects.
lidnariq
Posts: 11432
Joined: Sun Apr 13, 2008 11:12 am

Re: Why not read controllers in NMI?

Post by lidnariq »

supercat wrote:Code which would be ill-prepared to handle an NMI may clear bit 7 of $2000 to prevent it from firing on schedule, such that if vblank happens when the system is unready the handler will either start late within the current vblank or else wait until the next one.
Writing to $2000 during rendering can cause a shoot-through glitch, incorrectly clearing the 256s bit of X scrolling for one scanline. This only happens on some subpixel alignments, and only if the write collides with the correct pixel, but without timed code it's hard to be assured of missing that. As a result, if you're using 4-screen or horizontal layout of nametables, it's best to avoid using the NMI enabled bit to selectively mask NMIs at runtime in the NES.
Post Reply