02. What's a v-blank?
Last updated
Last updated
Writing to the screen while the picture is on.
If you used the vram_adr() or vram_put() functions while the screen is ON, there is 92% chance that you will write garbage on the screen and misalign the scroll.
Why this happens, basically, the PPU can only do 1 thing at a time, and while the screen is running, it is busy reading data from the VRAM and sending it to output to your TV, 92% of the time.
It goes line by line, pixel by pixel, calculating which color to write for each dot. Here’s a nice slow motion camera watching SMB1 (at about 2:14).
Once it reaches the bottom, it waits a short period. That’s called the vertical blank period (v-blank). This is the only time the PPU is not busy, and we can safely write tiles to the screen during this time (not too many).
Also, we have turned NMI interrupts on (this bit in register 2000, 1xxx xxxx, somewhere in the startup code in crt0.s.). At the beginning of v-blank, the PPU generates a signal (NMI) which halts CPU execution, and it jumps to the nmi code in neslib.s, and executes that during the v-blank period.
We know that it will go to the nmi code (asm, in neslib.s) during this period, so we know that it is safe to write to the PPU during this time (well, a few bytes). We can use this to our advantage, because if we are playing a game, and you turn the screen off, write to the screen, then turn it back on…the screen will flicker black during that time, which is a bit annoying. So, we want to keep the screen on, and we want to write to the PPU during v-blank.
So, while the PPU is busy drawing the screen, we will write to a buffer. Then when we call ppu_wait_nmi() it will set a flag that says “our data is ready to transfer” and it will sit and wait until v-blank. The nmi code will transfer it to the PPU automatically.
Before all that, you need to set_vram_update(address of data), to pass the address of our data or buffer to the neslib.
I have made some examples of data that the automated system can read. You can either send it 1 byte, or a contiguous set of data (tiles).
SINGLE BYTE
address high byte
address low byte
data (tile #)
EOF
MSB(NTADR_A(18,5)),
LSB(NTADR_A(18,5)),
‘B’,
NT_UPD_EOF
.
CONTIGUOUS DATA address high byte + update horizontal address low byte # of bytes data EOF
MSB(NTADR_A(10,14))|NT_UPD_HORZ,
LSB(NTADR_A(10,14)),
12, // length of write
‘H’,
‘E’,
‘L’,
‘L’,
‘O’,
‘ ‘,
‘W’,
‘O’,
‘R’,
‘L’,
‘D’,
‘!’,
NT_UPD_EOF
Note, optional, update vertical, replace NT_UPD_HORZ with NT_UPD_VERT, it will draw top to bottom instead of left to right. Left to right wraps to the next line. Top to bottom does not wrap, and you probably don’t want to go past the bottom tile of a screen.
You can stack multiple writes into one frame, if you strip the EOF between them. See hello2.c below. An empty buffer would be just the EOF (= 0xff). The system needs to see a 0xff or it will keep pushing tiles infinitely.
There is a limit as to how many bytes you can buffer…
About 31 single bytes, or 74 contiguous bytes, or mixed, somewhere in between. But this is fuzzy, you should err on less than this. If you never adjust the palette, you can get more (maybe 40 single, 97 contiguous) safely per frame.
Note, the same bytes will transfer to the PPU over and over, every frame, until the buffer changes. The user won’t see it, but it’s a waste of CPU time.
You can turn it off with
set_vram_update (NULL)
.
https://github.com/nesdoug/02_Hello2/blob/master/hello2.c
https://github.com/nesdoug/02_Hello2
I’ve noticed that nearly nobody is using this function, nor a VRAM buffer. Most people are using vram_put() or something similar.
I think it’s because it’s awkward to construct a VRAM update on the fly. So, I wrote a whole support library to make this a piece of cake.
Next time…