Loading the next screen

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Loading the next screen
by on (#16770)
Hello all.

I've been trying to implement using the Start button to switch screens, to simulate going from a title screen, to the actual game area. I'm wondering if anyone knows exactly what would cause the second screen to just be a jumbled mess? Mind you that I use the same nametable to load the second screen. When I used the other to preload that second screen, it still gets jumbled after pressing Start. Here's the ROM if you would like to see what I mean:

http://roth.zhxhome.net/junk/NCAT.NES

If anyone could let me know what could be causing it, then I can check out my code and see if there's something similar to what is described. Thanks.

by on (#16771)
The next screen seems like a lot of new data. Are you disabling rendering before drawing the second screen? If by any chance you are trying to draw it during rendering time, you're sure to get garbage on the screen.

As for the start button, you should do that trick that will only detect keys that have changed since the last read, because when you hold the start button down for a while, the next screen is reloaded an insane ammount of times. The trick goes something like this (supposing you use a byte to store the status of all 8 possible buttons): EOR the byte holding the old states with the one holding the new states. Whatever buttons changed will have 1's, the ones that remain the same will be 0's. AND that to the new byte, so that what CHANGED AND IS PRESSED NOW will be 1's, and everything else will be 0's. This way you'll only detect the moment when the button is pressed (transition), wich is the desired behaviour of the start button.

by on (#16774)
As for reloading an entire nametable, you pretty much have 2 options - either disable rendering and load it all at once, or load it a little bit at a time (during VBLANKs) into an alternate nametable and then switch it in when you're done.
From what I've seen, though, most games don't disable the screen to reload nametables - they'll turn the palette black and then queue up a screen's worth of data to be updated during VBLANKs.
You may find it useful to code a general-purpose I/O queue for dispatching VRAM updates during VBLANK and splitting them into sufficiently small chunks so as to avoid overflowing into rendering. I have some code that does exactly this (for Drip, an Amiga game I'm porting* to the NES) - if you're interested, I'll post it here (it's reasonably small, but fairly versatile).


(* not so much "porting", moreso "completely rewriting it from scratch to work in mostly the same way")

by on (#16777)
tokumaru wrote:
As for the start button, you should do that trick that will only detect keys that have changed since the last read, because when you hold the start button down for a while, the next screen is reloaded an insane ammount of times. The trick goes something like this (supposing you use a byte to store the status of all 8 possible buttons): EOR the byte holding the old states with the one holding the new states. Whatever buttons changed will have 1's, the ones that remain the same will be 0's. AND that to the new byte, so that what CHANGED AND IS PRESSED NOW will be 1's, and everything else will be 0's. This way you'll only detect the moment when the button is pressed (transition), wich is the desired behaviour of the start button.


Thanks for the tip on that. I noticed when I held down Start that it just stayed in place, then upon release loaded the garbage. I think I've seen some code like what you described before. I'll check out a few sources and see what's going on in them.


Queitust wrote:
As for reloading an entire nametable, you pretty much have 2 options - either disable rendering and load it all at once, or load it a little bit at a time (during VBLANKs) into an alternate nametable and then switch it in when you're done.
From what I've seen, though, most games don't disable the screen to reload nametables - they'll turn the palette black and then queue up a screen's worth of data to be updated during VBLANKs.
You may find it useful to code a general-purpose I/O queue for dispatching VRAM updates during VBLANK and splitting them into sufficiently small chunks so as to avoid overflowing into rendering. I have some code that does exactly this (for Drip, an Amiga game I'm porting* to the NES) - if you're interested, I'll post it here (it's reasonably small, but fairly versatile).


I tried disabling, then running the code, then enabling. It did indeed draw the screen MUCH better, but the upper-left corner was still garbage. I'll play with the code a little more and see if I can get the code shorter, as it seems like I must be stepping on something I shouldn't have been... and sure, I'd like to see the code for that you have. It's always nice to see how someone implements something.

Thanks for the replies :)

by on (#16778)
Top left corner being garbage means that you need to either turn sprite display off or put all the unused sprites offscreen (y attribute value between $EF and $FF).

by on (#16780)
Tepples, you are certainly the man. I made a seperate subroutine for turning on the PPU, but without the sprites on... this made it work perfect :) Thanks. Now to get to the controller polling. Thanks again all.

by on (#17284)
Another option for garbage sprites is to turn on sprite clipping in the leftmost 8 pixels and then put all the sprites at X location 0.

by on (#17286)
commodorejohn wrote:
Another option for garbage sprites is to turn on sprite clipping in the leftmost 8 pixels and then put all the sprites at X location 0.

You won't get garbage sprites like this, sure, but remember that the NES can only display 8 sprites on a scanline. Those sprites hidden in the left 8 pixels still count for this limit, so you'd be lowering your displayed sprite count if you hid them like this. If many of them occupy the same scanlines, you may even be blanking the sprites for those entire scanlines, depending on sprite priorities.

Always hide unused sprites at the bottom of the screen, where it doesn't matter if they occupy the same scanlines.

by on (#17301)
I previously used to just set all their tiles #s to a blank tile. It probably is better to set the Y positions below the screen though, that possibly would've hidden a (fairly rare) sprite display bug that shows up in Munchie Attack.

by on (#17320)
Yes, left clipping isn't the way to go, especially that in some case you'd want to not have the clipping enabled.
Putting all sprite vertical range of $f0 or above will NEVER have them to be even considered by the PPU, making them disabled. All other tricks may hide the sprites to the user, but those are still tricks and technically, the sprites will be displayed.

by on (#17391)
A ways up, I said I would post my "VRAM update queue" code. I suppose I can do that now.

One limitation is that it does not support "vertical" writing (i.e. using the "increment VRAM address by 32" option), but that's mainly because I don't use it.

Code:
;Variables - these ones are best located in zeropage
ioptr:      .res 2
addxy:      .res 1

splitiobank:   .res 1
splitiolen:   .res 2
splitiofrom:   .res 2
splitioto:   .res 2
splitiocur:   .res 2

io_nextbank:   .res 1
io_nextlen:   .res 1
io_nextfrom:   .res 2
io_nextto:   .res 2

;More variables - these can go elsewhere in RAM
io_bank:   .res 32
io_len:      .res 32
io_from:   .res 64
io_to:      .res 64
io_reqi:   .res 1
io_reqo:   .res 1

splitio_tmp:   .res 1

;Macros

.define   PLO(ptr) ptr+0
.define   PHI(ptr) ptr+1

.macro   clr_pointer   dest
   LDA #$00
   STA dest+0
   STA dest+1
.endmacro

.macro   copy_pointer   src, dest
   LDA PLO src
   STA PLO dest
   LDA PHI src
   STA PHI dest
.endmacro

.macro   load_pointer_x   src, dest
   LDA PLO src,X
   STA PLO dest
   LDA PHI src,X
   STA PHI dest
.endmacro

.macro   save_pointer_x   src, dest
   LDA PLO src
   STA PLO dest,X
   LDA PHI src
   STA PHI dest,X
.endmacro

;Code

;*** Waits for a bulk I/O request to clear.
.proc waitio
   PHA
:   LDA io_reqi
   CMP io_reqo
   BNE :-
   PLA
   RTS
.endproc

;*** Queues a bulk VRAM update - as big as you want. This is the main routine you use.
;splitiofrom - where you want to copy memory from
;splitiolen - the length of the block you want to copy
;splitiobank - PRG bank in which the data resides (optional)
;splitioto - the VRAM address you want to copy the data to
.proc splitio
   clr_pointer splitiocur
:   SEC
   LDA PLO splitiolen
   SBC PLO splitiocur
   STA splitio_tmp
   LDA PHI splitiolen
   SBC PHI splitiocur
   BNE :+
   LDA splitio_tmp
   CMP #$40
   BCC :++
:   LDA #$40
:   STA io_nextlen
   LDA splitiobank
   STA io_nextbank
   copy_pointer splitiofrom, io_nextfrom
   copy_pointer splitioto, io_nextto
   JSR iorequest

   LDA #$40
   LDX PHI splitiofrom
   LDY PLO splitiofrom
   JSR add_xy_a
   STX PHI splitiofrom
   STY PLO splitiofrom

   LDX PHI splitioto
   LDY PLO splitioto
   JSR add_xy_a
   STX PHI splitioto
   STY PLO splitioto

   LDX PHI splitiocur
   LDY PLO splitiocur
   JSR add_xy_a
   STX PHI splitiocur
   STY PLO splitiocur

   LDA PHI splitiocur
   CMP PHI splitiolen
   BCC :---
   LDA PLO splitiocur
   CMP PLO splitiolen
   BCC :---
   RTS

add_xy_a:
   STA addxy
   TYA
   CLC
   ADC addxy
   TAY
   TXA
   ADC #$00
   TAX
   LDA addxy
   RTS
.endproc

;*** Resets the I/O queue, in case you need to do something like that
.proc ioreset
   LDA #$00
   STA io_reqi
   STA io_reqo
   RTS
.endproc

;*** Used by splitio, queues a single 64-byte VRAM transfer
.proc iorequest
   PHA
   TXA
   PHA
   LDX io_reqi
   LDA io_nextlen
   STA io_len,X
   LDA io_nextbank
   STA io_bank,X
   TXA
   ASL A
   TAX
   save_pointer_x io_nextfrom, io_from
   save_pointer_x io_nextto, io_to
   INX
   INX
   TXA
   LSR A
   AND #$1F
   STA io_reqi
   PLA
   TAX
   PLA
   RTS
.endproc

;*** Called during VBLANK as part of your NMI routine, dispatches one VRAM transfer
.proc iodispatch
   LDY io_reqo
   CPY io_reqi
   BEQ :++
   TYA
   ASL A
   TAX
   load_pointer_x io_from, ioptr
   LDA PHI io_to,X
   STA $2006
   LDA PLO io_to,X
   STA $2006
   LDA io_bank,Y
   STA MAP_PRG      ;this is your mapper's PRG banking register
   LDA io_len,Y
   TAX
   LDY #$00
:   LDA (ioptr),Y
   STA $2007
   INY
   DEX
   BNE :-
   LDY io_reqo
   INY
   TYA
   AND #$1F
   STA io_reqo
:   RTS
.endproc