Question about NMI / displaying new background

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

User avatar
NeverCameBack
Posts: 31
Joined: Mon Feb 24, 2020 12:22 am

Re: Question about NMI / displaying new background

Post by NeverCameBack » Mon Mar 23, 2020 12:10 pm

I may be confused about the order of things happening still:

1- Enable NMI and specify to load background from .chr table #0
2- Infinite loop

3- NMI hits
4- Check for screen update
If update, disable NMI and rendering, send 1024 bytes to $2007, then unlatch screen update request
5- Controller check
6- Re-enable NMI and rendering

7- Wait for sprite 0 flag to be zero (in case it was previously holding a value of 1)
8- Wait for sprite 0 flag to be 1
9- Immediately write $%10010000 to $2000 - this should switch the PPU's mapping of the background to .chr file 1 - Midway through rendering the screen, so long that sprite 0 is placed in the middle of the screen.

10 - RTI takes you back to the rung before the NMI interrupt, in my case: "infinite loop"

It seems to me that after the NMI hits, we have a limited amount of time before the rendering takes place.
IF we can run through steps 3 to 7 before the rendering takes place, then I think this should work.. Do you know if that sounds correct?

I'm getting top and bottom of screen rendered from .chr table #0, however when I hold "A" it flashes between that and a screen where the bottom half is rendered from .chr table #1, and the top half is all white (background color here). I'm currently trying to figure it out...

Update:
I had partial success. In the image below the woman's face is from .chr table 0, and her chest is from table 1.
Obviously the problem is that both images are showing on the bottom half of the screen.
Sprite 0 has a Y coordinate of #$B0 (which is where the .chr table switch is taking place).

I put the two white squares in the image to know that both top and bottom are being rendered from the first 430 background bytes.

My problem now is that of course I want the top half of the screen to be rendered too. At which point I'll move sprite 0 to somewhere near vertical #$80, and have a full screen image made of out .chr tables 0 and 1.

I apologize for the lewd image - she is wearing a shirt. This is from an old comic called Warlock 5. I'm just using the picture for practice.
PartialImage.PNG
Attachments
background3.asm
(17.34 KiB) Downloaded 19 times

User avatar
tokumaru
Posts: 11691
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Question about NMI / displaying new background

Post by tokumaru » Mon Mar 23, 2020 3:51 pm

NeverCameBack wrote:
Mon Mar 23, 2020 12:10 pm
1- Enable NMI and specify to load background from .chr table #0
2- Infinite loop
1- Enable NMI (don't enable rendering now!)
2- Infinite loop

Enabling NMIs is the VERY LAST thing before the infinite loop. After you enable NMIs, an NMI can fire at any time, and you absolutely don't want to have any code interrupted by the NMI handler. You probably want to read $2002 before enabling NMIs too, to avoid having the first NMI fire mid-vblank (without having the entire vblank time available, the PPU updates could spill into the visible frame, resulting in problems like screen "jumps" - not very serious - or VRAM corruption and rendering glitches - way more serious).

Also, don't enable rendering at this point. If you do, you'll risk getting screen "jumps" and corrupted graphics. Just keep rendering disabled and let the NMI handler enable it at the correct time. And since you're not enabling rendering here, there's also no need to set any rendering parameters yet (e.g. background CHR table).
3- NMI hits
4- Check for screen update
If update, disable NMI and rendering, send 1024 bytes to $2007, then unlatch screen update request
5- Controller check
6- Re-enable NMI and rendering
There are a few problem here. Enabling rendering and NMIs is something you must do at specific times in order to avoid graphical glitches (temporary and/or permanent). If you enable rendering after switching screens, that'll most likely happen mid-frame, causing a screen "jump". Better just end this instance of the NMI handler and let the next one do everything at the correct times.

3- NMI hits
4- If screen update:
4.1- Unlatch update request (copy NewScreen to CurrentScreen)
4.2- Disable NMI and rendering
4.3- Send 1024 bytes to $2007
4.4- read $2002 and enable NMIs
4.5- terminate NMI
5- Incremental PPU updates (sprite DMA, palettes, scroll seam, etc.)
6- Set rendering parameters (CHR and SPR tables, scroll, etc.) and enable rendering
7- Controller check (all game logic, actually)

Note that game logic comes last. You're basing your code off of the Nerdy Nights tutorials, aren't you? Putting the game logic where they did is completely wrong, and that sadly confuses a lot of newbies. Vblank time is really short (less than 8% of an entire frame), so you should only use that time for things that can ONLY be done at that time (i.e. PPU updates). If you do other stuff (game logic, sound playback, etc.) you'll only be able to use 8% of the CPU power of the console, which may be OK for beginner tutorials but is pitiful for an actual game.
7- Wait for sprite 0 flag to be zero (in case it was previously holding a value of 1)
8- Wait for sprite 0 flag to be 1
9- Immediately write $%10010000 to $2000 - this should switch the PPU's mapping of the background to .chr file 1 - Midway through rendering the screen, so long that sprite 0 is placed in the middle of the screen.
Right. Don't forget that an opaque sprite pixel must overlap an opaque background pixel.
10 - RTI takes you back to the rung before the NMI interrupt, in my case: "infinite loop"
Yes. And since you're not doing anything there, the RTI effectively acts as a "wait for the next vblank" command.
It seems to me that after the NMI hits, we have a limited amount of time before the rendering takes place.
Yup.
IF we can run through steps 3 to 7 before the rendering takes place, then I think this should work.. Do you know if that sounds correct?
There's no way you can draw an entire background in the available vblank time. And you shouldn't be handling the game logic during vblank time either, like I said before.
I'm getting top and bottom of screen rendered from .chr table #0, however when I hold "A" it flashes between that and a screen where the bottom half is rendered from .chr table #1, and the top half is all white (background color here). I'm currently trying to figure it out...
The white portion is probably the time when the new screen is being written to VRAM. Once that's done, rendering is enabled mid-frame (remember that I said above how to avoid this?), causing a partial frame to be rendered. I don't know why your sprite 0 hit isn't working properly, though.

EDIT: I noticed you're using FCEUX for testing. Since you're still getting the hang of the whole timing stuff, I suggest you also try your programs on emulators more accurate then FCEUX, such as Mesen, Nintendulator or Nestopia. FCEUX is not particularly faithful to the timing constraints of the real console and can often cause an incorrect program to look correct.

User avatar
NeverCameBack
Posts: 31
Joined: Mon Feb 24, 2020 12:22 am

Re: Question about NMI / displaying new background

Post by NeverCameBack » Mon Mar 23, 2020 5:12 pm

I think my problem now is understanding:
- When NMI is turned off, the PPU is still going full blast, 60 FPS, however my CPU will not be interrupted for each NMI.
- When rendering is turned off, the PPU won't do anything having to do with the background. The screen will go blank.
- The PPU can only receive data from $2007 during VBLANK, which directly follows the NMI interrupt.
- We wait for the NMI interrupt to turn rendering off, so that we catch the PPU in a VBlank state, and don't interrupt it in the middle of rendering.
- By turning rendering off, the PPU is guaranteed to not be busy, and is ready to receive data via $2007 and store them into it's RAM. Then when you turn rendering back on, the PPU is already all loaded up and ready to render the data that it's holding in it's RAM.

About the order of things:
1- Enable NMI (don't enable rendering now!)
2- Infinite loop
3- NMI hits
4- If screen update:
4.1- Unlatch update request (copy NewScreen to CurrentScreen)
I forgot to mention, when I unlatch the update request, that is when I get two vertical copies of .chr table #0.
When I keep the screen update variable latched, meaning the write to $2007 will run happen each NMI; That is when I can at least see the bottom half of my screen showing tiles from both chr tables 0 and 1.
4.2- Disable NMI and rendering
4.3- Send 1024 bytes to $2007
4.4- read $2002 and enable NMIs
4.5- terminate NMI
It looks like 4.4 should be the final time that I re-enable the NMI.
When you say terminate NMI right after - do you mean as in using an RTI? Meaning for my program, go back to the forever loop and wait for the next NMI?

If the NMI is turned back on at this point, is there any guarantee that we will make it down to #s 5,6,7, etc. before being interrupted by another NMI? Or is it that there will be plenty of time, because the period between two NMIs is made up of 8% VBlank time, and 92% rendering time?
5- Incremental PPU updates (sprite DMA, palettes, scroll seam, etc.)
6- Set rendering parameters (CHR and SPR tables, scroll, etc.) and enable rendering
7- Controller check (all game logic, actually)
8- Wait for sprite 0 flag to be 1
9- Immediately write $%10010000 to $2000 - this should switch the PPU's mapping of the background to .chr file 1 - Midway through rendering the screen, so long that sprite 0 is placed in the middle of the screen.
10 - RTI takes you back to the rung before the NMI interrupt, in my case: "infinite loop"
[/quote]

I'm sure at this point that I'm detecting the sprite 0 flag. I partially have happening what I want to happen.. however I haven't been able to solve the problem with the top half of the screen not displaying yet. I'll keep working at it.

IN CASE you or anyone some day wants to try and fix it before I get it.. I attached the zip file.
When running the NES file, pressing A is the trigger to "make the full background show up".

I'm stumped but will keep working at it. Maybe I need to try a different emulator like you said. I'll do that. And yes, I started with Nerdy Nights, I should probably go back and re-do the basic lessons to fill in any gaps in my knowledge from it.
Background_Multi.zip
(72.01 KiB) Downloaded 23 times

User avatar
tokumaru
Posts: 11691
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Question about NMI / displaying new background

Post by tokumaru » Mon Mar 23, 2020 7:01 pm

NeverCameBack wrote:
Mon Mar 23, 2020 5:12 pm
When NMI is turned off, the PPU is still going full blast, 60 FPS, however my CPU will not be interrupted for each NMI.
Yup.
- When rendering is turned off, the PPU won't do anything having to do with the background. The screen will go blank.
Yes, but it's still outputting a valid video signal to the TV, the difference is that the entire picture is just a single color. Vblank still exists even when NMIs are off, but they'll fire if they're on.
- The PPU can only receive data from $2007 during VBLANK, which directly follows the NMI interrupt.
If rendering is on, yeah. If rendering is off, you can write to VRAM at any time.
- We wait for the NMI interrupt to turn rendering off, so that we catch the PPU in a VBlank state, and don't interrupt it in the middle of rendering.
You only need to explicitly turn rendering off if you expect the update that follows to take longer than vblank (as is the case with a full background update), but for small updates (e.g. sprite DMA, palettes, scroll seam, etc.) guaranteed to finish before vblank does you don't need to turn rendering off.
- By turning rendering off, the PPU is guaranteed to not be busy, and is ready to receive data via $2007 and store them into it's RAM. Then when you turn rendering back on, the PPU is already all loaded up and ready to render the data that it's holding in it's RAM.
Yes.
I forgot to mention, when I unlatch the update request, that is when I get two vertical copies of .chr table #0.
When I keep the screen update variable latched, meaning the write to $2007 will run happen each NMI; That is when I can at least see the bottom half of my screen showing tiles from both chr tables 0 and 1.
You were enabling rendering mid-screen in several instances, and that's particularly bad for sprite 0 hits. When rendering starts mid-frame, the image is misaligned, and that can interfere with the opaque pixel overlap required for sprite 0 hits to work. Try to fix that (always read $2002 before enabling NMIs!) and see if that helps.
It looks like 4.4 should be the final time that I re-enable the NMI.
I don't think it's right to say that, since there could be another background switch at a later time and NMIs would go off and on again.
When you say terminate NMI right after - do you mean as in using an RTI? Meaning for my program, go back to the forever loop and wait for the next NMI?
Yes. Since uploading a new background takes so much time, you don't really know how the CPU is aligned with the PPU when that task finishes. You can't enable rendering or do other PPU updates because you don't know if you're inside vblank (or how much time vblank time is left), and you can't process the game logic because there might not be enough time for that before the next vblank starts, and if an NMI fires in the middle of your game logic that'd probably crash the program. The most sensible thing to do after a big operation like that is to keep rendering off, stop everything and let the CPU and PPU synchronize naturally on the next NMI.
If the NMI is turned back on at this point, is there any guarantee that we will make it down to #s 5,6,7, etc. before being interrupted by another NMI?
By reading $2002 before enabling NMIs you avoid the case when an NMI fires immediately due to the PPU being in the middle of vblank. That way, even if the PPU is in the middle of vblank when you enable NMIs, an NMI will only fire at the beginning of the NEXT vblank, so you can be sure that your NMI handler has all of the vblank time available. As long as you don't put to much stuff in the "frame updates" (sprite DMA, palettes, scrolling seam, etc.) section, all of that is guaranteed to run during vblank.
Or is it that there will be plenty of time, because the period between two NMIs is made up of 8% VBlank time, and 92% rendering time?
Then when the frame updates are done, the scroll is set and rendering is enabled, you have the remaining 92% of the CPU to run all the game logic and prepare the data for the frame updates of the next vblank.

Now, as programs get more complex it is possible that the remaining CPU time is not enough to compete all the game logic (it may happen if many enemies are on screen at once, for example), and if that's a possibility in your game you will have to implement some kind of safeguard preventing the NMI from firing one over the other and locking/crashing the program. The typical solution is to have a "FrameComplete" flag that you set when everything is ready for the next vblank, and then your NMI handler can check this flag right away and decide whether to behave normally or only do the most critical stuff (handle music, status bars, etc.) and skip the updates, creating what's commonly called a "lag frame". This is what causes slowdowns in games.
I started with Nerdy Nights, I should probably go back and re-do the basic lessons to fill in any gaps in my knowledge from it.
I don't think Nerdy Nights is a particularly good tutorial, because it begins with some misinformation (e.g. wrong comments) and a terrible program structure (game logic inside vblank, giving you only 8% of the CPU time), but I think it improves with the latter lessons. Unfortunately I don't have anything better to suggest, so the best I can do is help you fix what's wing with it here, like I've been doing.

User avatar
tokumaru
Posts: 11691
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Question about NMI / displaying new background

Post by tokumaru » Mon Mar 23, 2020 7:54 pm

NeverCameBack wrote:
Mon Mar 23, 2020 5:12 pm
IN CASE you or anyone some day wants to try and fix it before I get it.. I attached the zip file.
I don't have the time to personally fix it and test it (and you probably would benefit less from that than from fixing it yourself), but I can point out a few things I noticed aren't right:

1- BackgroundNum is not initialized. The first time the NMI handler runs, it tests this variable without you having prepared it to trigger a background update. Since the memory was cleared to 0 on reset, no background will be displayed until you press A and this variable is changed to 1. You must indicate you need background 1 to be loaded somewhere in your reset code, before the first NMI fires.

2- You don't clear BackgroundNum after the new screen is drawn. According to your logic, whenever BackgroundNum is 1, you draw a new background (any other value doesn't trigger an update). But once the new background is drawn this variable is left at 1, so the same background will be drawn over and over and over. You have to set that variable back to 0 after drawing the screen so it's only drawn once (per button press, that is). Another thing you can do to improve this is make it so it skips an update when the BackgroundNum is 0, and use values 1 to 255 to load different screens.

3- You're doing a sprite DMA before everything in your NMI handler. This doesn't have any bad side effects, it's just pointless to do a sprite DMA if the next thing you'll do is change the entire screen, and before that new screen is displayed, another sprite DMA will happen anyway. My point is: there's two things your NMI handler can do - update the current screen or load a new one. Updating the sprites is part of updating the current screen, you don't need to do it if you're drawing a new one. I suggest putting the BackgroundNum first, and moving the sprite DMA to the NoBackground section.

4- The game logic (controller reading) is sandwiched by the PPU update code. This is what will result in you only having 8% of the CPU time for your entire program. Before the game logic you need to finish all the PPU stuff. In your case, that's the sprite DMA, the PPU configuration ($2000 and $2001) and the scroll ($2005). Then you can have a whole lot of game logic before you start worrying about running out of CPU time.

5- Your sprite 0 hit check is really weird... Why all those bit shifts? Maybe you're not familiar with bitwise operations (you really should be if you're planning on working with assembly!) but you can test any bit in the accumulator by ANDing the value with a bit mask containing the bit you want to test:

Code: Select all

WaitForNoHit:
  LDA $2002
  AND #%01000000
  BNE WaitForNoHit
WaitForHit:
  LDA $2002
  AND #%01000000
  BEQ WaitForHit
But in this particular code you can do better: the BIT instruction, which is an AND that doesn't change the accumulator, also copies bits 7 and 6 to CPU flags N (negative) and V (overflow). This means that we can very quickly check bits 7 and 6 of any value in memory or memory-mapped registers, and since the sprite 0 hit flag is in bit 6, we're in luck:

Code: Select all

WaitForNoHit:
  BIT $2002 ;copies the sprite 0 hit flag to CPU flag V
  BVS WaitForNoHit ;go test again if flag is set
WaitForHit:
  BIT $2002 ;copies the sprite 0 hit flag to CPU flag V
  BVC WaitForHit ;go test again if flag is clear
6- You're resetting WaitForZero after switching patterns only once. Like I explained before, there's no hard link between name table entries and a specific pattern table. Each frame, the PPU will use whatever pattern tables are specified AT THE TIME OF RENDERING. This means that you need to do the pattern switch EVERY FRAME. During vblank, set the background to fetch from one pattern table, then, after the sprite 0 hit, set it to fetch from the other pattern table. Every frame. You only stop if you load a new background that doesn't need the pattern switch.

User avatar
NeverCameBack
Posts: 31
Joined: Mon Feb 24, 2020 12:22 am

Re: Question about NMI / displaying new background

Post by NeverCameBack » Mon Mar 23, 2020 8:37 pm

My last question: What do I owe you for all the help?
Seriously though, I never got as much help from someone over the internet, and to everyone else who chimed in too. I appreciate it... and will pass it on if I can.

Your #6 is mainly what the problem was - I didn't realize that the PPU needed to be instructed every frame. It makes total sense now, but I wasn't seeing it. I guess I was giving the PPU too much credit in thinking that it would just keep putting the same exact thing on the screen 60 FPS without being told to switch mapping to the other .chr table.

Those LSR and ASL shifts I did just to isolate the 6th bit of $2002. I was going to do an AND on checking if sprite 0 was = 1, but to check if it was zero first, the shifts were the first thing I thought of. In my day job working with PLCs, I've come across some programs that use bit-wise operations, but I mainly use "ladder logic".

So I have my full image. I showed my wife, and she is less than thrilled with my progress on the bottom half of the image.
Just curious, but what kind of games have you worked on, and do you have anything in the works currently?

User avatar
tokumaru
Posts: 11691
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Question about NMI / displaying new background

Post by tokumaru » Mon Mar 23, 2020 9:34 pm

NeverCameBack wrote:
Mon Mar 23, 2020 8:37 pm
My last question: What do I owe you for all the help?
I help people here hoping that they'll stick around and help others in the future. If you can do that, that'll be great! Some members simply go away once they learn what they wanted. I hope your username isn't foreshadowing something like this!
Your #6 is mainly what the problem was - I didn't realize that the PPU needed to be instructed every frame. It makes total sense now, but I wasn't seeing it. I guess I was giving the PPU too much credit in thinking that it would just keep putting the same exact thing on the screen 60 FPS without being told to switch mapping to the other .chr table.
The way these old systems work isn't very intuitive, but once you get the hang of it everything makes perfect sense!
Those LSR and ASL shifts I did just to isolate the 6th bit of $2002. I was going to do an AND on checking if sprite 0 was = 1, but to check if it was zero first, the shifts were the first thing I thought of. In my day job working with PLCs, I've come across some programs that use bit-wise operations, but I mainly use "ladder logic".
If you're just getting started with 6502 you don't have to worry too much about writing optimal code. It's just that CPUxPPU synchronization is one of the most timing-sensitive parts of NES programming, so I had to say something. Here's why: You're waiting for a PPU event (sprite 0 hit) that happens with pixel precision, and this event could happen at any point during your polling loop, since the CPU and the PPU are completely out of sync. The longer the loop is, the larger the timing error for the detection of that event is. Your loop was about 27 CPU cycles, and since each CPU cycles is equivalent to 3 rendered pixels, you could have a delay between 0 and 81 pixels when reacting to the sprite 0 hit. Since the delay changes each frame due to the random misalignment, that could translate to very noticeable jittering on that specific scanline. With the improved 7-cycle loop, the error will be at most 21 pixels, and that's much easier to hide in hblank.
So I have my full image. I showed my wife, and she is less than thrilled with my progress on the bottom half of the image.
I didn't see the full image yet. Is the bottom part really that controversial?
Just curious, but what kind of games have you worked on, and do you have anything in the works currently?
My main accomplishment is a raycasting engine I haven't updated in over 10 years! Well, I have worked on it a bit during the past couple of years, but haven't created a new NES ROM yet.

User avatar
NeverCameBack
Posts: 31
Joined: Mon Feb 24, 2020 12:22 am

Re: Question about NMI / displaying new background

Post by NeverCameBack » Mon Mar 23, 2020 10:26 pm

I didn't see the full image yet. Is the bottom part really that controversial?
Not too bad, but this comic was certainly illustrated for a mature audience. The woman is wearing a night gown, BUT you can basically see some boobs.

What led me here is that I took an atari programming course on Udemy - They don't have an NES one. The instructor has a ray casting course too, using C. Do you have a prototype that would work on an emulator or real NES hardware? I'd love to make a Hexen mod someday... Especially a Hexen 64 mod.

My next goal is to "master" the .chr bank switching, to get more graphics into a single game. Maybe I could make a short graphic novel type game with multiple paths. I imagine that I'll run into some limitations on the size constraints. I had RGB modded my NES earlier this year, so my aim is to make something to play on real hardware.

User avatar
tokumaru
Posts: 11691
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Question about NMI / displaying new background

Post by tokumaru » Tue Mar 24, 2020 12:13 am

NeverCameBack wrote:
Mon Mar 23, 2020 10:26 pm
Do you have a prototype that would work on an emulator or real NES hardware?
The old ROM is still available at the link in the video's description (BTW, thanks, Memblers!). I don't have anything newer than that working on the NES.
I'd love to make a Hexen mod someday... Especially a Hexen 64 mod.
I don't see how you'd do that under so many restrictions, but OK! :lol:
Maybe I could make a short graphic novel type game with multiple paths.
That's cool.
I imagine that I'll run into some limitations on the size constraints.
With typical NES hardware from back in the day it'll be hard to go past 512KB or so, which would be enough for a few dozen images the way you're making them. You could go with CHR-RAM instead of CHR-ROM and take advantage of compression, but that complicates the programming. Another option with modern hardware is to stream the data off of an SD card, which can provide gigabytes of storage, but that would only work on a flashcart or a custom cartridge.

Post Reply