It is currently Mon Oct 23, 2017 6:45 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 5 posts ] 
Author Message
 Post subject: Structuring Main and NMI
PostPosted: Tue Jan 24, 2017 8:46 am 
Offline

Joined: Tue May 28, 2013 5:49 am
Posts: 808
Location: Sweden
I have some questions regarding general structuring of the main thread and the NMI handler.

My current setup is mainly a combination of what is recommended in The frame and NMIs and other advices I got from this forum. Game logic in the main loop of the RESET handler and graphic updates in the NMI handler, buffered.

This is how it looks like right now.
RESET handler:
Code:
RESET:
;(init code)
;(prepare first screen and turn on rendering)

main:

  lda #$01
  sta draw_flag             ;allow NMI to draw, prevents incomplete buffering
 
  jsr nmi_wait              ;wait for nmi to limit logic to a fixed frame rate
 
  jsr con_read_2p           ;read inputs

state:                      ;check state and load the corresponding engine
  lda machine_state         ;biggest engine is checked first to speed it up
 
  cmp #STATE_GAME           ;the main Game state
  bne +
  jmp state_game
+
  cmp #STATE_PAUSE          ;Pause Screen state
  bne +
  jmp state_pause
+
  cmp #STATE_TITLE          ;Title Screen state
  bne +
  jmp state_title
+
  cmp #STATE_GAMEOVER       ;Game Over Screen state
  bne +
  jmp state_gameover
+
state_end:

  jmp main


NMI handler:
Code:
NMI:
  pha
  txa
  pha
  tya
  pha                ;backup A, B and Y to the stack

  bit $2002          ;clear vblank flag, reset $2005/$2006 flipflop
 
  lda nmi_lock       ;check if NMI is locked
  beq +              ;if not, enter NMI
  jmp nmi_end        ;exit NMI if locked
+
  inc nmi_lock       ;lock NMI (prevents NMI re-entry)
;-------------------------------------------------------------------------------
;Graphic Updates

  lda draw_flag        ;see if OAM/VRAM buffers are ready
  beq draw_end         ;if not, skip OAM and VRAM updates
 
obj_update:            ;update sprites using OAM-DMA
  lda #$00             ;ensure OAM address $00 is selected as
  sta $2003            ;destination start address
  lda #>shadow_oam     ;set the RAM page to be used as source address
  sta $4014            ;and invoke OAM-DMA

bg_update:             ;update part of nametable using a BG buffer
  lda bg_flag          ;see if there are any background updates
  beq @exit:           ;if not, skip ahead
  jsr bg_buffer_draw   ;else draw
  dec bg_flag          ;clear background flag and exit
@exit:

cg_update:             ;update palettes
  lda cg_flag          ;see if there are any palette updates
  beq @exit:           ;if not, skip ahead
  lda $2002            ;reset flipflop
  lda #$3F
  sta $2006
  lda #$00
  sta $2006            ;palette starts at VRAM $3F00
  ldx #$00
@loop:
  lda shadow_cg,x      ;load data from palette buffer
  sta $2007
  inx
  cpx #$20             ;loop counter for all 32 palette entries
  bne @loop
  dec cg_flag          ;clear palette flag and exit
@exit:

draw_end:
  lda #$00
  sta draw_flag        ;clear OAM/VRAM update flag

ppu_settings_update:
  lda $2002            ;reset flipflop
  lda shadow_2000
  sta $2000
  lda shadow_2001
  sta $2001            ;update PPU setting registers from buffer

scroll_update:
  lda #$00
  sta $2006
  sta $2006            ;clear PPU registers (messes with scrolling)
  lda scroll_x
  sta $2005
  lda scroll_y
  sta $2005            ;update scroll position from scroll buffer


graphic_end:

;Other Updates

  jsr sound_play       ;update sound routine

;-------------------------------------------------------------------------------
  dec nmi_lock        ;unlock NMI again
nmi_end:

  inc frame_counter

  pla
  tya
  pla
  txa
  pla                 ;restore A, X and Y from the stack
  rti

Questions:

1)
The draw flag prevents OAM and VRAM updates to happen during lag frames when the OAM and VRAM buffers aren't completly filled yet, but it doesn't prevent PPU setting ($2000/$2001) updates or scroll updates. I'm not sure if it should?

2)
Also in another thread it was recommended to have a render flag that prevents graphic updates when rendering is off. If I understand it correctly it should skip all graphic updates including PPU settings and scroll updates. I tried doing that (by using a render_flag that if set jumps to the "graphic_end" label), but that ended up breaking my between screens drawing routines that draws the entire screen between screen transisions.

3)
Is there anything else that I'm doing badly, or could be generally improved?


Top
 Profile  
 
PostPosted: Tue Jan 24, 2017 9:47 am 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 1787
Location: DIGDUG
I have some thoughts.

BG updates and palette updates could be run by one system. You may not want to update the entire palette each time, but only 1 or 3 bytes. Which makes hardcoding $3f00 in not great.

Anyway, something like...(fill buffer with)
# of bytes
PPU Address
Data
# of bytes
PPU Address
Data
Etc, until # of bytes is zero, exit

And, I moved my frame counter outside the NMI, because I was timing enemy movements through it, but when I hit "pause" it would continue to tick upward, causing odd enemy movements if pause was repeatedly hit.

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


Top
 Profile  
 
PostPosted: Tue Jan 24, 2017 9:51 am 
Online
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10067
Location: Rio de Janeiro - Brazil
Pokun wrote:
The draw flag prevents OAM and VRAM updates to happen during lag frames when the OAM and VRAM buffers aren't completly filled yet

But you're setting the flag at the beginning of the frame, when no processing has been done yet? I don't get it.

Quote:
but it doesn't prevent PPU setting ($2000/$2001) updates or scroll updates. I'm not sure if it should?

In a game that scrolls, the camera is an object that has to be updated like any other, and if the NMI happens before the frame's finished processing, you might have an inconsistent camera state (absolute or relative to other elements), and using those scroll values might result in glitches (or possibly even crashes if it causes a sprite 0 hit to be missed due to unsynchronized scroll and OAM updates). If you don't have raster effects, you can get away with not changing the scroll at all during lag frames because last frame's values will be used automatically by the PPU, but if you have a status bar or anything that needs to be updated every frame, you should double buffer the raster effects' parameters and update a "hidden" copy, and only when the frame has finished processing you "commit" the buffered values. And to be perfectly safe, the commit should happen in the NMI, so that it can't be interrupted by an NMI that'd end up using partially updated raster effects.

Quote:
Also in another thread it was recommended to have a render flag that prevents graphic updates when rendering is off. If I understand it correctly it should skip all graphic updates including PPU settings and scroll updates. I tried doing that (by using a render_flag that if set jumps to the "graphic_end" label), but that ended up breaking my between screens drawing routines that draws the entire screen between screen transisions.

Now that's weird, because the purpose of leaving the PPU alone when rendering is off is precisely to not interfere with the transition code that's drawing stuff to the PPU. Maybe you were delaying the $2001 write that was supposed to disable rendering but because of the flag the write ended up never happening, meaning the transition happened with the screen still on? In my own engine, the only register I update regardless of whether rendering is on or off is $2001, so that the PPU can be turned on and off according to the "rendering" flag. AFAIK, $2001 writes don't have any side effects that could interfere with VRAM updates.

Quote:
Is there anything else that I'm doing badly, or could be generally improved?

1- "draw_flag" should be set at the END of processing, otherwise it's not doing anything;
2- Don't bit $2002 unconditionally in the NMI, because it might be interrupting the $2006 writes of a background update;
3- Writing $00 to $2006 twice at the end of the NMI will force the use of NT 0, overriding the NT chosen through $2000;
4- If you have raster effects, double buffer the scroll, if you don't, don't update the scroll during lag frames;
5- Updating $2000 and $2001 during lag frames could use inconsistent values if you don't double buffer them;

When designing interrupt routines you always have to think about what they might be interrupting, and if the tasks they do could result in problems for the interrupted code. You also have to carefully think of the life cycle of all flags and buffers, and simulate (do it in your head of write it down, whatever works for you) a few different scenarios - normal frames, lag frames, transitions, and so on. Don't write code because a forum member or a tutorial told you it's the best practice, take the time to understand how all these flags and conditionals can make your code more resilient.


Top
 Profile  
 
PostPosted: Tue Jan 24, 2017 12:21 pm 
Offline

Joined: Tue May 28, 2013 5:49 am
Posts: 808
Location: Sweden
Thanks both of you for your tips.

dougeff wrote:
BG updates and palette updates could be run by one system. You may not want to update the entire palette each time, but only 1 or 3 bytes. Which makes hardcoding $3f00 in not great.

Anyway, something like...(fill buffer with)
# of bytes
PPU Address
Data
# of bytes
PPU Address
Data
Etc, until # of bytes is zero, exit

And, I moved my frame counter outside the NMI, because I was timing enemy movements through it, but when I hit "pause" it would continue to tick upward, causing odd enemy movements if pause was repeatedly hit.

My BG buffer works kind of like that. I made the palette buffer simple since there are only 32 palette entries, but if vblank time becomes scarce I'll definitely design a more flexible palette update routine.
I only use the frame counter for displaying the number of frames at the moment so I thought it would be best if it was running at 60 Hz. I guess you could have a separate counter that counts logical frames in your game state handler.

tokumaru wrote:
Pokun wrote:
The draw flag prevents OAM and VRAM updates to happen during lag frames when the OAM and VRAM buffers aren't completly filled yet

But you're setting the flag at the beginning of the frame, when no processing has been done yet? I don't get it.

What's the difference between setting it at the end an infinite loop or at the beginning of it? I actually initially had it at the end, but then moved it up so that it's a bit closer to the NMI wait, and It works as expected. The only potential problem I see is that no logic has processed the very first loop after reset, so that frame could have incorrect data written to the PPU. But I initialize all buffers in my init code so that's not a problem either.

Quote:
2- Don't bit $2002 unconditionally in the NMI, because it might be interrupting the $2006 writes of a background update

Ok I moved that instruction within the draw and render flag conditions so it's not executed during lag frames or when rendering is off.

Quote:
3- Writing $00 to $2006 twice at the end of the NMI will force the use of NT 0, overriding the NT chosen through $2000

This is new to me. Currently I have no scrolling at all so I'm always using nametable 0. But I learned somewher that writing 0 to $2006 twice is necessary before setting the scroll coordinates or else previous PPU writes will affect scroll. The wiki mentions something about it producing "interesting raster effects" if writing to this during screen refresh, is this what you are refering to?

Quote:
4- If you have raster effects, double buffer the scroll, if you don't, don't update the scroll during lag frames;
5- Updating $2000 and $2001 during lag frames could use inconsistent values if you don't double buffer them;

OK since I have no raster effects I made both of these conditional based on the render flag (makes them not update when rendering is off) and the draw flag (makes them not update during lag frames). Both render_flag and draw_flag now jumps to graphic_end instead of draw_end to achieve this. But I still have the problem with the render flag breaking my screen transition drawing routines:
tokumaru wrote:
Pokun wrote:
Also in another thread it was recommended to have a render flag that prevents graphic updates when rendering is off. If I understand it correctly it should skip all graphic updates including PPU settings and scroll updates. I tried doing that (by using a render_flag that if set jumps to the "graphic_end" label), but that ended up breaking my between screens drawing routines that draws the entire screen between screen transisions.

Now that's weird, because the purpose of leaving the PPU alone when rendering is off is precisely to not interfere with the transition code that's drawing stuff to the PPU. Maybe you were delaying the $2001 write that was supposed to disable rendering but because of the flag the write ended up never happening, meaning the transition happened with the screen still on? In my own engine, the only register I update regardless of whether rendering is on or off is $2001, so that the PPU can be turned on and off according to the "rendering" flag. AFAIK, $2001 writes don't have any side effects that could interfere with VRAM updates.

I turn on and off using two subroutines that should make sure that it's done correctly:
Code:
ppu_on:
;Turns on rendering.
  lda #$01
  sta render_flag           ;set rendering flag
  lda #PPU_ON
  sta shadow_2001           ;turn on rendering in buffer
  jsr nmi_wait              ;wait for an NMI to enable rendering
  rts
;-------------------------------------------------------------------------------
ppu_off:
;Turns off rendering.
  lda #$00
  sta shadow_2001           ;turn off rendering in buffer
  jsr nmi_wait              ;wait for an NMI to disable rendering
  lda #$00
  sta render_flag           ;clear rendering flag
  rts

Yet it doesn't work as expected. The screen is drawn only partly during transitions...


Top
 Profile  
 
PostPosted: Tue Jan 24, 2017 2:01 pm 
Online
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10067
Location: Rio de Janeiro - Brazil
Pokun wrote:
What's the difference between setting it at the end an infinite loop or at the beginning of it?

Oh, you're right. The way your loop is structured, it doesn't really matter. What threw me off was the order in which you do things: first you wait for the NMI and then you run the game logic. I guess this is reminiscent from when you had the game logic inside the NMI handler? To me it makes more sense to run the game logic first, then wait for the NMI, because during the first iteration of the game loop there's nothing for the NMI to do if it goes first. There's no problem in doing it your way though, as long as all the flags are correctly setup to prevent the very first NMI from doing anything it shouldn't with stuff that hasn't been initialized by the game logic yet.

Quote:
The only potential problem I see is that no logic has processed the very first loop after reset, so that frame could have incorrect data written to the PPU. But I initialize all buffers in my init code so that's not a problem either.

Exactly.

Quote:
But I learned somewher that writing 0 to $2006 twice is necessary before setting the scroll coordinates or else previous PPU writes will affect scroll.

$2006 writes do affect the scroll, but writing to $2000 and $2005 fully resets the scroll, undoing any wrong that $2006 might have done. Writing $00 to $2006 was a trend started by people who weren't sure about what they're were doing and that was the only way they found to stabilize the scroll, but it's completely unnecessary if you have proper $2000 and $2005 writes to set the scroll. $2006 only needs to be used for scrolling if you're doing mid-screen vertical scroll changes.

Quote:
The wiki mentions something about it producing "interesting raster effects" if writing to this during screen refresh, is this what you are refering to?

The "interesting raster effects" would be the mid-screen scroll changes.

Quote:
OK since I have no raster effects I made both of these conditional based on the render flag (makes them not update when rendering is off) and the draw flag (makes them not update during lag frames). Both render_flag and draw_flag now jumps to graphic_end instead of draw_end to achieve this.

Sounds good, as long as there are no raster effects.

Quote:
But I still have the problem with the render flag breaking my screen transition drawing routines:

It's hard for me to figure this out without seeing the whole picture (specially now that you edited things), but my guess is still that the $2001 write that was supposed to disable rendering is being skipped somehow. When you call the "ppu_off" routine, what's the state of the "draw_flag"? Won't it prevent $2001 writes from happening? My only suggestion is that you trace this code by triggering a breakpoint for "ppu_off" and seeing what happens when the NMI fires, if rendering is indeed disabled of if there's anything preventing that from happening.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 5 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