02、什么是v-blank?
向屏幕写入图片
如果在屏幕开启的时候使用 vram_adr() 或 vram_put() 函数,
有92%的可能会使写入屏幕的图片花屏或试滚动变得不对齐。
为什么会发生这种情况,基本上,PPU一次只能做一件事,而且当屏幕正在运行时,有92%的时间PPU正忙于从VRAM读取数据并将其发送到屏幕。
(翻译注:这货就是一根筋,只能干一件事儿,干多了就出错。92%是按照时间比算出来的,也就是说PPU主要工作是绘制图片,然后停下来很短的时间让用户更新VRAM以便重新绘制屏幕显示内容)
它逐行逐点的计算每个点要写入的颜色。(英文原版有油管视频,使用高速摄像机拍摄超级玛丽1的屏幕显示)
(视频放慢到逐行扫描的时候,可以看到屏幕从左向右,然后下一行从左到右快速绘制屏幕显示的点)
(就好像是我们看荧光棒那样,快速挥动就变成了一条线,有能力的可以翻墙过去看看)
视频地址:https://www.youtube.com/watch?v=3BJU2drrtCM
一旦到达底部,它会等待一段时间。这称为垂直空白期(v-blank)。这是PPU不忙的唯一时间,我们可以在此期间安全地将贴图写入屏幕(这个时间很短)。
此外,我们已打开NMI中断(寄存器2000中的这一位,1xxx xxxx,crt0.s.中启动代码中的某处有这个代码)。在v-blank开始时,PPU生成一个停止CPU执行的信号(NMI),它跳转到neslib.s中的nmi代码,并在v-blank期间执行。
我们知道它会在这段时间内转到nmi代码(asm,在neslib.s中),因此我们知道在此期间写入PPU是安全的(好吧,几个字节)。我们可以利用这个时间段,因为如果我们正在玩游戏,你关闭屏幕,写入屏幕,然后重新打开...屏幕将在此期间闪烁黑色,这有点烦人。因此,我们希望保持屏幕开启,并且我们希望在v-blank期间将数据写入PPU。
因此,当PPU忙于绘制屏幕时,我们将写入缓冲区。然后,当我们调用 ppu_wait_nmi(),它将设置一个标记,表示“我们的数据已准备好传输”,它将一直等到v-blank。nmi代码会自动将其传输到PPU。
在此之前,您需要 set_vram_update(address of data), 将我们的数据或缓冲区的地址传递给neslib。
我已经做了一些自动化系统可以读取的数据示例。您可以发送1个字节,也可以发送一组连续的数据(贴图)。
--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
(上面这2段我是真不知道怎么翻译了,原样放在这里了)
注意,可选,垂直更新,用NT_UPD_VERT替换NT_UPD_HORZ,它将从上到下而不是从左到右绘制。从左到右包裹到下一行。从上到下不会换行,您可能不希望超过屏幕的底部图块。
如果能去除大量多余的EOF,则可以在一帧中更新更多的贴图。见下面的hello2.c。空缓冲区只是EOF(= 0xff)。系统需要看到一个0xff,否则它将继续无限地推送瓦片。
(翻译解释:简单的说就是你可以大量的调用单个贴图显示,但是作者推荐的是一次把一堆东西排好序用一个函数直接连续输出。比如你要输出hello,就不要一个字母一个字母的去指定位置显示,然后再给个EOF表示写完了,这样只能减少你一帧能更新显示的内容)
可以缓冲多少字节?
大约31个单字节,或74个连续字节,或混合,介于两者之间。这样说有点模糊,你可以做一些尝试,就能准确的了解这个了。
如果您从不调整调色板,则每帧可以安全地获得更多(可能是40个单独,97个连续)
注意,相同的字节将一遍又一遍地传输到PPU,直到缓冲区发生变化。用户对此无感,但是CPU来说有点浪费时间。
您可以使用下面的函数将其关闭
set_vram_update (NULL)
https://github.com/nesdoug/02_Hello2/blob/master/hello2.c
https://github.com/nesdoug/02_Hello2
我注意到几乎没有人使用这个功能,也没有使用VRAM缓冲区。大多数人都在使用 vram_put() 或类似的东西。
我认为在运行中构建VRAM是件很笨拙的事儿,所以我打算写一个完整的支持库,让这个变得简单。