nametable questions
Moderator: Moderators
-
Celius
- Posts: 2159
- Joined: Sun Jun 05, 2005 2:04 pm
- Location: Minneapolis, Minnesota, United States
- Contact:
Oh, try just remove "lda #3 sta $4014" really quick and see if the flickering continues then. If it doesn't, that means that sprite DMA, which I'm pretty sure takes 513 cycles to execute, and then the extra ~1950 to execute the other update code would combine to equal an amount that would cause a spill out of Vblank.
Ok cool, that does prevent the flickering. So I guess I actually don't really need a sprite on screen for the rom I'm trying to make (sort of a live visual type deal), but I am wondering, how do I draw an entire screen full of background tiles instead of just a few rows. I'm already working with 128 bits (?) of background information for only a few lines and isn't there a limit when I'm doing this:
Code: Select all
lda #$20
sta $2006 ; $2020-$23C0 = name table 0 (32x25 tiles)
sta $2006
ldx #$00
loadNames: ; load background name table (bkg12.map)
lda ourMap, x
inx
sta $2007 ; $2007 = PPU memory data
cpx #4*32
bne loadNames-
Celius
- Posts: 2159
- Joined: Sun Jun 05, 2005 2:04 pm
- Location: Minneapolis, Minnesota, United States
- Contact:
If you have a 1k (1024 bytes) name table arrangement in a file (Like your files, just for the whole name table), in order to load it you have to use indirect addressing. So you'd put the address of the start of the background data file into say $50 and $51, where $50 contains the low byte and $51 contains the high byte of the address. Then you start with Y = 0, and you do "lda ($50),y sta $2007" and "iny" in a loop until y wraps back to 0, at which point you add 1 to $51. And you'll loop 3 more times after that. You basically copy 4 256 byte sections to the name table. But if you do this, you're going to want to shut off the screen entirely for a frame or so, because this is probably not going to fit into 2200 cycles (in fact, that's impossible for that to happen).
Thanks again Celius. This has been really helpful. But this all leads me to a question. Isn't it better to perform as few operations during an interrupt as possible? I understand that what I am doing at the moment is taking less than 2200 cycles and therefore will complete without flickering during a VBlank, but wouldn't it generally be better to keep as much out of the VBlank routine as possible? In your last response you suggested that I could not redraw a whole background image within 2200 cycles, and therefore would need to turn the screen off and on around that code. If I do this, should I take all that controller stuff out of the VBlank routine and put it back in my Infinite loop?
Seperating the infinite loop from the NMI is unimportant in simple programs, but to handle slowdown effectively, it can be useful to keep them each doing seperate jobs. The approach I'd use would be something like this:
In Infinite loop:
---------------------
1) Update joypad data
2) Do game logic (collision detection, AI, etc)
3) Draw sprites and stuff to shadow OAM
4) Prepare an organized list of PPU writes in a buffer so that you can quickly dump stuff to $2007 next vblank.
5) Set a "can draw" flag
6) Set a "waiting for VBl" flag
7) Wait for "waiting for VBl" flag to clear
8) Repeat from step 1
In NMI:
---------------------
1) Set up CPU cycle based IRQ if needed for raster effects (if mapper 069 or something -- probably not applicable most of the time).
2) Do Sprite DMA (only if "can draw" flag is set)
3) Scan the prepared drawing buffer and do all desired $2007 writes (step 4 in infinite loop) -- (only if "can draw" flag is set)
4) Clear the "can draw" flag to indicate drawing is done
5) Set scroll
6) Set up PPU based IRQ if needed for raster effects (if mapper 004 or something)
7) Just to music routine to keep music/sound effects playing
8) Clear "waiting for vbl" flag
9) RTI
This setup keeps the logic and drawing code seperated so that if NMI interrupts your game logic, you can still do necessary raster effects like splitting the screen, as well as keep music playing without disrupting game logic.
Also the "can draw" flag prevents your NMI handler from drawing half-completed stuff if the NMI happens in the middle of your preparation.
So this setup gracefully handles slowdown and stuff without causing weird graphical glitches.
EDIT:
it dawns on me that you can use the "waiting for vblank" flag as the "can draw" flag -- so you only need one flag. *shrug*
In Infinite loop:
---------------------
1) Update joypad data
2) Do game logic (collision detection, AI, etc)
3) Draw sprites and stuff to shadow OAM
4) Prepare an organized list of PPU writes in a buffer so that you can quickly dump stuff to $2007 next vblank.
5) Set a "can draw" flag
6) Set a "waiting for VBl" flag
7) Wait for "waiting for VBl" flag to clear
8) Repeat from step 1
In NMI:
---------------------
1) Set up CPU cycle based IRQ if needed for raster effects (if mapper 069 or something -- probably not applicable most of the time).
2) Do Sprite DMA (only if "can draw" flag is set)
3) Scan the prepared drawing buffer and do all desired $2007 writes (step 4 in infinite loop) -- (only if "can draw" flag is set)
4) Clear the "can draw" flag to indicate drawing is done
5) Set scroll
6) Set up PPU based IRQ if needed for raster effects (if mapper 004 or something)
7) Just to music routine to keep music/sound effects playing
8) Clear "waiting for vbl" flag
9) RTI
This setup keeps the logic and drawing code seperated so that if NMI interrupts your game logic, you can still do necessary raster effects like splitting the screen, as well as keep music playing without disrupting game logic.
Also the "can draw" flag prevents your NMI handler from drawing half-completed stuff if the NMI happens in the middle of your preparation.
So this setup gracefully handles slowdown and stuff without causing weird graphical glitches.
EDIT:
it dawns on me that you can use the "waiting for vblank" flag as the "can draw" flag -- so you only need one flag. *shrug*
wow, thanks for the great explanations. i'm still a bit hung up on indirect addressing. I understand how the actual addressing works I think, but how do I store my background data in a specific location to access by indirect addressing? For example, I have this included in my code at the moment, how do I store it in a specific location?
Is this table ready to be stored somewhere/accesses? Or do I need to save this info in some other way? Again, I'm a beginner, so sorry if this is pretty simple.
Code: Select all
ourMap2:.db 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0
.db 17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0
.db 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2
.db 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18
.db 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0
.db 17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0
.db 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2
.db 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18
.db 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0
.db 17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0
.db 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2
.db 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18
.db 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0
.db 17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0
.db 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2
.db 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18
.db 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0
.db 17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0
.db 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2
.db 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18
.db 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0
.db 17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0
.db 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2
.db 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18
.db 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0
.db 17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0
.db 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2
.db 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18
.db 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0
.db 17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0,17,18, 0, 0 Your data should be immediately accessable via the ourMap2 label. What you need to do is copy the address this label represents (aka create a "pointer" to the label) and put that pointer somewhere in zero page memory. From there you can use indirect addressing on that pointer.
I don't know the NESASM syntax for it (assuming that's what you're using -- I haven't really been paying attention), but here's how you'd do that in ca65:
iirc, nesasm code for this is probably something like:
I don't know the NESASM syntax for it (assuming that's what you're using -- I haven't really been paying attention), but here's how you'd do that in ca65:
Code: Select all
; assuming 'ptr' is a 2-byte variable in zero page
lda #<ourMap2 ; put low byte of ourMap2 address in A
sta ptr ; write as low byte of our pointer
lda #>ourMap2 ; get high byte
sta ptr+1 ; write as high byte of pointer
ldy #0 ; zero Y for indexing
lda (ptr),Y ; do an indirect read from the address our pointer points to
; plus Y. Since ptr points to 'ourMap2', and since Y=0, this
; will read from ourMap2+0
; you can increase Y to go to the next byte, then increase ptr+1 once
; Y wraps to point to the next 256-byte block
Code: Select all
lda #LO(ourMap2)
sta <ptr
lda #HI(ourMap2)
sta <ptr+1
ldy #0
lda [ptr],Y
I didn't follow the thread completely but the proper syntax for nesasm is:
Code: Select all
lda #LOW(ourMap2)
sta ptr
lda #HIGH(ourMap2)
sta ptr + 1