It is currently Wed Jun 26, 2019 4:54 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 50 posts ]  Go to page Previous  1, 2, 3, 4  Next
Author Message
PostPosted: Sun May 19, 2019 9:42 am 
Offline

Joined: Fri Feb 24, 2012 12:09 pm
Posts: 918
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:
if read_buttons<>0
  start_game
elseif read_buttons=0
  do_not_start_game
else
  program_has_failed

_________________
homepage - patreon


Top
 Profile  
 
PostPosted: Sun May 19, 2019 9:49 am 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 21460
Location: NE Indiana, USA (NTSC)
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.

_________________
Pin Eight | Twitter | GitHub | Patreon


Top
 Profile  
 
PostPosted: Sun May 19, 2019 6:56 pm 
Offline

Joined: Fri Feb 24, 2012 12:09 pm
Posts: 918
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


Top
 Profile  
 
PostPosted: Mon May 20, 2019 1:05 am 
Offline
User avatar

Joined: Fri Jan 24, 2014 9:05 am
Posts: 192
Location: Hungary
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).


Top
 Profile  
 
PostPosted: Mon May 20, 2019 4:29 am 
Offline

Joined: Tue May 28, 2013 5:49 am
Posts: 1175
Location: Hokkaido, Japan
FrankenGraphics wrote:
Quote:
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.


Top
 Profile  
 
PostPosted: Mon May 20, 2019 6:35 am 
Offline

Joined: Thu Apr 18, 2019 9:13 am
Posts: 144
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.


Top
 Profile  
 
PostPosted: Mon May 20, 2019 7:32 am 
Offline
Formerly WheelInventor
User avatar

Joined: Thu Apr 14, 2016 2:55 am
Posts: 2024
Location: Gothenburg, Sweden
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).


Quote:
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.

_________________
http://www.frankengraphics.com - personal NES blog


Top
 Profile  
 
PostPosted: Mon May 20, 2019 7:58 am 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 21460
Location: NE Indiana, USA (NTSC)
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

_________________
Pin Eight | Twitter | GitHub | Patreon


Top
 Profile  
 
PostPosted: Mon May 20, 2019 8:18 am 
Offline
Formerly WheelInventor
User avatar

Joined: Thu Apr 14, 2016 2:55 am
Posts: 2024
Location: Gothenburg, Sweden
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).

_________________
http://www.frankengraphics.com - personal NES blog


Top
 Profile  
 
PostPosted: Mon May 20, 2019 8:23 am 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 21460
Location: NE Indiana, USA (NTSC)
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.

_________________
Pin Eight | Twitter | GitHub | Patreon


Top
 Profile  
 
PostPosted: Mon May 20, 2019 11:15 am 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 7511
Location: Canada
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.


Top
 Profile  
 
PostPosted: Mon May 20, 2019 11:20 am 
Offline
Formerly WheelInventor
User avatar

Joined: Thu Apr 14, 2016 2:55 am
Posts: 2024
Location: Gothenburg, Sweden
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:

_________________
http://www.frankengraphics.com - personal NES blog


Top
 Profile  
 
PostPosted: Mon May 20, 2019 11:49 am 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 21460
Location: NE Indiana, USA (NTSC)
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.

_________________
Pin Eight | Twitter | GitHub | Patreon


Top
 Profile  
 
PostPosted: Mon May 20, 2019 1:07 pm 
Offline

Joined: Thu Apr 18, 2019 9:13 am
Posts: 144
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:
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.


Top
 Profile  
 
PostPosted: Mon May 20, 2019 1:16 pm 
Online

Joined: Sun Apr 13, 2008 11:12 am
Posts: 8393
Location: Seattle
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.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 50 posts ]  Go to page Previous  1, 2, 3, 4  Next

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