Best practice when updating entire nametable

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

casprog
Posts: 70
Joined: Fri Oct 28, 2016 12:37 pm

Best practice when updating entire nametable

Post by casprog »

Hi all,

I understand that nametable changes may only occur within vblank / NMI, since that is when the PPU is free, and that there is not enough time to update the entire nametable during one vblank.

What I've done in the past is disable rendering *and* NMI together, then start my updating within NMI immediately afterwards. This seemed to work OK for a simple game going from a splash screen to the game screen.

For example: If nametable needs to change I disable NMI and rendering (probably don't need to disable rendering since I'm already in NMI?), and I update the entire nametable immediately afterwards while still in the last NMI call, then I re-enable NMI and rendering at the end of the current NMI call. So in theory I'm assuming that even though it takes a while to update, no new NMI calls will be made during the process.

Generally, if updating the entire nametable, what is the best practice around it and where should the updating code be?

Thanks
User avatar
rainwarrior
Posts: 8732
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Best practice when updating entire nametable

Post by rainwarrior »

In many cases I prefer to leave the NMI running even if rendering is off, not touching the PPU, but still playing music.

You should only turn rendering on or off in your NMI handler during vblank, otherwise you will get a partially corrupt frame.

Once rendering is off, you can do it anywhere, the timing doesn't matter*. It's probably a lot more convenient to do this in your "main" thread rather than trying to operate within your NMI (which should be more oriented toward partial vblank-sized updates). Also, like I mentioned above, if you have an NMI routine that lets you skip the PPU and just keep updating sound, you don't get those unpleasant music skips when you move from room to room.

* Exception: writing palettes will cause colour to appear in the middle of the screen, even if rendering is off. Palettes should only be uploaded during vblank/NMI. Sprites also should only be uploaded during vblank, but for a different reason (the RAM "decays" over time and will corrupt if you don't OAM DMA immediately before the picture resumes).
User avatar
Dwedit
Posts: 4924
Joined: Fri Nov 19, 2004 7:35 pm
Contact:

Re: Best practice when updating entire nametable

Post by Dwedit »

Keep NMI enabled, just use a flag to tell it not to do any PPU stuff.
That way you can keep Music playing for example.
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!
casprog
Posts: 70
Joined: Fri Oct 28, 2016 12:37 pm

Re: Best practice when updating entire nametable

Post by casprog »

So the "main" thread would be like my Forever loop right? (for lack of a better word). The JMP that just keeps going until interrupts occur?

It sounds like if the nametable needs to change I can disable rendering in NMI and set a flag which the main thread can check. It updates the nametable and updates a flag so NMI knows to turn rendering back on? Should the disabling and enabling of rendering be located in any specific section of NMI? Like at the head or tail of the interrupt?

Thanks for the tip on palettes, I did not know that. (and music).
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: Best practice when updating entire nametable

Post by koitsu »

OP: there is a Wiki page on this subject. It speaks in "general tone" for development, so that the concept/implementation can be understood:

https://wiki.nesdev.com/w/index.php/The_frame_and_NMIs

Section "When to Turn Off PPU, NMIs" may be relevant to your interests (don't let the section title mislead you), but the entire page seems highly relevant to what's being discussed.
User avatar
pubby
Posts: 583
Joined: Thu Mar 31, 2016 11:15 am

Re: Best practice when updating entire nametable

Post by pubby »

I've always implemented NMI handlers as an indirect jump, like this:

Code: Select all

nmi_handler:
    jmp (nmi_ptr)
To change the behavior of the handler, you change the code 'nmi_ptr' points to. I find this cleaner to use than flags.

The second useful thing to have is a subroutine that blocks until an NMI occurs. You likely already have code to do this in your main loop, but it's even better to make it a subroutine:

Code: Select all

wait_for_nmi:
    lda nmi_counter
waitLoop:
    cmp nmi_counter
    beq waitLoop
    rts
When you want to disable rendering and/or change 'nmi_ptr', you first call wait_for_nmi and then do your thing:

Code: Select all

jsr wait_for_nmi
lda #0
sta PPUMASK
lda #.lobyte(some_handler)
sta nmi_ptr+0
lda #.hibyte(some_handler)
sta nmi_ptr+1
You can use this subroutine all over the place. I like to use it for implementing fade-ins and fade-outs, for example.
casprog
Posts: 70
Joined: Fri Oct 28, 2016 12:37 pm

Re: Best practice when updating entire nametable

Post by casprog »

Thanks koitsu, that was a good read and I'm glad I read that before trying to separate game logic and draw logic.

pubby, so would you put all the NMI logic at whatever the pointer points to? So the callback for the interrupt is just a JMP?

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

Re: Best practice when updating entire nametable

Post by tokumaru »

casprog wrote:So the "main" thread would be like my Forever loop right? (for lack of a better word). The JMP that just keeps going until interrupts occur?
The more "robust" kind of program structure doesn't have an empty forever loop, it has a main thread that takes care of game logic and buffering video updates, and the NMI thread, which carries out the PPU updates, plays audio and does anything else that absolutely must run at 60Hz (e.g. raster effects).
It sounds like if the nametable needs to change I can disable rendering in NMI and set a flag which the main thread can check.
It's actually the main thread that's supposed to signal stuff to the NMI thread. The main thread can't do PPU stuff whenever it wants, so it has to signal the intent to do those things so the NMI can effectively do them when vblank hits. The main thread goes "I want to turn rendering off", and waits for the NMI. When vblank hits, the NMI thread then obeys, and doesn't do any other PPU operations from that point on in order to not interfere with PPU operations that the main thread might be doing, such as updating an entire name table, but can keep doing other timely tasks without "freezing" the program. When the main thread is done, it signals the NMI thread to turn referring back on.
Should the disabling and enabling of rendering be located in any specific section of NMI? Like at the head or tail of the interrupt?
As long as it's during vblank, it doesn't really matter. Whatever works best with your logic.
User avatar
pubby
Posts: 583
Joined: Thu Mar 31, 2016 11:15 am

Re: Best practice when updating entire nametable

Post by pubby »

casprog wrote:pubby, so would you put all the NMI logic at whatever the pointer points to? So the callback for the interrupt is just a JMP?
Yeah. And to change the NMI behavior you'd change the pointer.
User avatar
slembcke
Posts: 172
Joined: Fri Nov 24, 2017 2:40 pm
Location: Minnesota

Re: Best practice when updating entire nametable

Post by slembcke »

Good to know about switching the PPU during the frame. I've just been ignoring the glitch for now, but was wondering if it would go away if I did it during vblank.

Side note:
If you are using a jump pointer, make sure that you change the pointer early in the frame. You would crash the CPU if the NMI was triggered while changing the pointer.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Best practice when updating entire nametable

Post by tepples »

A four-write sequence should avoid a crash:
1. Change JMP to RTI
2-3. Change low and high bytes of destination address
4. Change back to JMP
User avatar
rainwarrior
Posts: 8732
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Best practice when updating entire nametable

Post by rainwarrior »

I had assumed pubby's 0 to PPUMASK ($2001) was actually supposed to be PPUCTRL ($2000) to disable the NMI and make it safe to change the pointer. Though looking back, the safety is actually provided by waiting for vblank right before doing it. So pubby's given technique was already safe because that jsr wait_for_nmi means NMI won't fire on the next few (thousand) instructions.

For tepples' generic "change your NMI jump pointer whenever you like" (do we really need this capability?) suggestion, I would probably have used these "writes" instead:
1. Disable NMI ($2000)
2-3. change pointer
4. Enable NMI ($2000)

i.e. directly address the cause of the problem (NMI might happen while you're changing the pointer) instead of doing something even more advanced to transmute the problem into a skipped frame. (...and I think we've neglected to mention that tepples' method additionally requires relocating your NMI to RAM? That's another non-trivial advanced technique to learn about. I'm gonna assume that tepples chose the self modifying method to avoid another even more obscure and complicated issue... but that's another unstated premise. :P)


But now we've gotten to the point where we're talking about how to safely write self-modifying NMI handler code with someone who is asking for beginner advice, and I'm kinda thinking better advice would be: don't do it this way right now.

Yes you can write an NMI that starts out with an indirection, but it's one method of a billion equally valid ones for controlling what your NMI does, and the fiddly details of it are a little bit complicated. I don't recommend it if you're at the stage where you're asking OP's question.


Here's another way that I think has less complicated ramifications:

Just use a variable (here called skip_ppu to tell your NMI to stop messing with the PPU), set it to 1 whenever you need to do your nametable updates in the main thread, and back to 0 when you're ready for it to start doing its thing again:

Code: Select all

nmi:
	pha ... etc.
	lda skip_ppu
	bne skip
	... ; PPU stuff
skip:
	... ; music
	... pla etc.
	rti
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Best practice when updating entire nametable

Post by tokumaru »

$2000 writes mid-frame are discouraged because they can cause the "SMB glitch", which is when one scanline is rendered with the wrong name table setting.
casprog
Posts: 70
Joined: Fri Oct 28, 2016 12:37 pm

Re: Best practice when updating entire nametable

Post by casprog »

I don't recommend it if you're at the stage where you're asking OP's question
I completely agree but I already got myself into trouble lol So I took Pubby's idea and applied it to my main game logic. It seems to be working OK. Upon start press I swap the intro nametable with a character select nametable and change the pointer to point to another game state handler. I think I will leave the NMI alone and not use pointers with that (will try to keep it generic).
You should only turn rendering on or off in your NMI handler during vblank, otherwise you will get a partially corrupt frame.
Would it be OK to turn it off as I'm doing here outside of NMI as long as I know vblank hit? In the article koitsu shared I read about setting flags and acting upon them during the next NMI, which I can do but I'm just curious.

Code: Select all

Main:
	JSR Read_controllers
	JMP [game_logic_ptr]	
game_logic_end:	
	JMP Main

Code: Select all

Game_logic_intro:
	LDA ctrl_1_state
	AND #%00010000 ; check for start button
	BEQ gli_controls_done
	; start pressed
	; wait for vblank
	JSR Wait_vblank
	; disable ppu
	LDA #%00000000
	STA $2001
	; update nametable
	LDA #LOW(select_nametable)
	STA nametable_ptr
	LDA #HIGH(select_nametable)
	STA nametable_ptr + 1
	JSR Load_nametable
	; wait for vblank
	JSR Wait_vblank
	; turn ppu back on
	LDA #%00011110
	STA $2001
	; change game logic pointer
	LDA #LOW(Game_logic_select)
	STA game_logic_ptr
	LDA #HIGH(Game_logic_select)
	STA game_logic_ptr + 1
gli_controls_done:
	JMP game_logic_end
User avatar
rainwarrior
Posts: 8732
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Best practice when updating entire nametable

Post by rainwarrior »

casprog wrote:Would it be OK to turn it off as I'm doing here outside of NMI as long as I know vblank hit? In the article koitsu shared I read about setting flags and acting upon them during the next NMI, which I can do but I'm just curious.
Yes, if you know your code is executing withing vblank, it is an OK time to turn on/off rendering without any visual problem. The exact mechanism of your "Wait_vblank" routine might be important. If it's waiting on your NMI routine to increment a counter, you might need to ensure that the NMI actually returns quickly enough to still be in vblank. (e.g. if your NMI routine also plays music, this may not be guaranteed.)

...which is why my general recommendation was to do the turning off directly in the NMI handler. It's usually much easier to ensure that things happening in that routine occur within vblank than stuff in the main thread.


The basic concept I recommend is:

Buffer everything you want to send to the PPU (the mask value to be written to $2001, the scroll position $2000/$2005/$2005, OAM to be uploaded by DMA, palettes, any nametable updates, etc.), and then when you're done buffering the next frame, you set a flag variable to tell the NMI it's ready and then enter a loop that waits for the NMI handler to use it.

The NMI handler itself would check that flag at the beginning, and if it's not set, just do music. If it is set, it knows the main thread is waiting for it and should apply everything in the buffers, then unset that flag (letting the wait loop know it's finished).


(This is orthogonal with pubby's update method, the JMP indirection is just a way to have different modes of behaviour for the nmi handler, which you could do still within this buffering scheme.)
Post Reply