摘要
一、2C02概述
二、PPU内存映射
三、PPU寄存器
四、调色板
五、图案表
六、名称表/属性表
七、精灵
八、滚动
九、电视标准
十、参考资料
本文介绍了NES游戏机上PPU的工作原理以及背景和精灵图像的显示方法,本文参考《Nintendo Entertainment System Documentation(任天堂娱乐系统文档)》,如需对PPU进一步的了解可以查阅该文档。在查资料的时候也发现了一篇写的很好的文章,我附在了文章最后的参考资料里。
在6502的基础上,理光也提供了2C02作为PPU(Picture Processing Unit 图形处理单元),相当于现在的显卡。PPU的寄存器主要位于CPU内存的I/O寄存器部分,地址为$2000-$2007和$4014。另外,还有一些特殊的寄存器用于屏幕滚动。(“$”在文中代表16进制的意思)。
PPU有自己的内存,称为VRAM(Video RAM)。与CPU一样,PPU也可以寻址64 KB的内存,尽管它只有16 KB的物理RAM。PPU的内存映射如下图所示。左边部分显示了一个简化的版本,这是由右边部分详细说明的。由于物理地址空间和逻辑地址空间的不同,任何超过$3FFF的地址都被包装起来,使逻辑内存位置$4000-SFFFF实际上成为位置$0000-$3FFF的镜像。
可以通过使用CPU内存中的I/O寄存器$2006和$2007来完成对PPU内存的读写。这通常是在帧末的V-Blank期间完成的,因为它影响在绘制屏幕时使用的地址,因此会破坏显示的内容。然而,这个效果可以用来产生分割屏幕效果。
由于PPU内存使用16位地址,但是I/O寄存器只有8位,所以需要两次写入$2006来设置所需的地址。数据可以从$2007读取或写入。每次写入到$2007之后,地址将按照$2000的第2位的要求增加1或32。$2007的第一次读取是无效的,数据将在下一次读取时被缓冲并返回。这不适用于调色板。
PPU还有一个单独的256字节的内存区域,即Sprite RAM (Sprite RAM),用于存储Sprite(精灵)属性。精灵本身可以在模式表中找到。(精灵指的是游戏中的角色或者怪物)。
CPU和其他设备之间的通信通过内存映射I/0寄存器进行。PPU使用的寄存器位于主存中,价格为$2000-$2007,另一个寄存器用于直接内存访问,地址为$4014。请记住,位置$2000-$2007在$2008-$3FFF区域中每8个字节镜像一次。CPU可以通过写入$2000和$2001来控制PPU的动作,分别称为PPU控制寄存器1和PPU控制寄存器2,这两个寄存器是只写的。$2000的第7位可以用来禁用NMIs。请记住,这种类型的中断是在出现V-Blank时产生的,并且不受状态寄存器的中断禁用标志的影响。由于NES同时支持8x8和8x16的精灵,所以清除这个位将防止NMI在V-Blank上发生,设置$2000的第5位将切换到8x16的精灵。每次I/O发生后,PPU内存中要读写的下一个地址将增加。通过设置$2000的第2位的值来调整要增加的值。如果该位是清除的,地址增加1(水平),否则增加32(垂直)。使用$2001,背景可以通过清除第3位隐藏,同样,精灵可以通过清除第4位隐藏。
PPU状态寄存器位于$2002并且是只读的,寄存器被PPU用来向CPU报告它的状态。程序将频繁地让CPU去读取这个地址以确定PPU的状态。第7位由PPU设置,以指示V-Blank正在发生。第6位和第7位与精灵有关,将在后面介绍。第4位表示PPU是否愿意接受对VRAM的写操作,如果该位清除,则写操作会被忽略。当从$2002读取数据时,第7位被重置为0,就像$2005和$2006一样。
直接内存访问(DMA)
在设备之间传输大量数据时,通过处理器传输数据是低效的。例如,将数据从CPU内存传输到精灵内存需要以下步骤:
(1)将所需的SPR-RAM地址加载到CPU。
(2)写入所需的SPR-RAM地址到$2003。
(3)加载字节到CPU。
(4)将字节写入$2004。
当填充精灵内存的内容时,这项技术需要重复256次。直接内存访问(DMA)是一种允许更有效地将数据从CPU内存复制到精灵内存的技术。使用DMA,整个精灵内存可以通过使用一条指令来填充,写入到$4014。CPU内存中的起始地址由操作数指定,写操作数乘以$100。从这个地址开始的256字节被直接复制到sprite内存中,而不需要进一步的CPU介入。当DMA发生时,内存总线正在使用中,从而阻止CPU访问内存,从而阻止它访问更多的指令。这被称为周期窃取,CPU必须等待DMA传输完成。在NES上,DMA相当于512个周期(大约4.5扫描行),之后CPU可以恢复。这比通过CPU手动复制要少得多。
NES有一个包含52种颜色的调色板,尽管实际上有64个区域。然而,并不是所有这些都可以在给定的时间显示。NES使用两个调色板,每个有16个条目,图像调色板($3F00-$3F0F)和精灵调色板($3F10-$3F1F)。图像调色板显示当前背景块可用的颜色。精灵调色板显示精灵当前可用的颜色。这些调色板不存储实际的颜色值,而是系统调色板中的颜色索引。由于只需要64个唯一值,所以可以忽略第6和第7位。
调色板条目$3F00是背景色,用于透明。使用镜像使调色板中的每四个字节是$3F00的一个副本。因此,$3F04、$3F08 $3FOC、$3F10、$3F14、$3F18和$3F1C只是$3F00的副本。每个调色板的颜色是13,而不是16。因此,在任何时候,屏幕上的颜色总数是52种颜色中的25种。两个调色板也镜像到$3F20-$3FFF。
NES有两个图案表,分别为$0000和$1000。图案表存储可以在屏幕上绘制的8x8像素的块。许多游戏将图案表存储在磁带的CHR-ROM中,但是,没有CHR-ROM的游戏将使用RAM作为图案表,并在执行期间填充它们。图案表存储标识该像素使用的图像或精灵调色板索引所需的4位数字中最不重要的两位,比如00b是调色板条目0,01b是1,10b是2,11b是3。
上图显示了图案表的工作方式。字符“A”是最后的结果,显示在底部。该字符是通过从左上角和右上角各取一个比特来构建一个像素,从而生成一个2比特的颜色。颜色的其他两位来自属性表。显示的颜色不是真正的NES调色板值。
名称表本质上是一个编号矩阵,指向存储在图案表中的编号。名称表是32x30块,由于每个块是8x8像素,所以整个名称表是256x240像素。
每个名称表都有一个相关的属性表。属性表包含颜色块的上两位。属性表中的每个字节表示4x4组块,因此属性表是这些组的8x8表。每个4x4组进一步划分为4个2x2的正方形,如下图所示。8x8块的编号是$0-$F。字节的布局为33221100,其中每两位指定对应正方形的最有效的两个颜色位。
NES只有2KB来存储名称表和属性表,允许它分别存储两个。然而,它最多可以解决其中的四个问题。镜像是用来实现这一点的。以下描述了四种镜像类型,它们使用逻辑名称表的缩写(可以寻址的):L1($2000)、L2($2400)、L3($2800)和L4 ($2C00)。
(1)水平镜像将L1和L2映射到第一个物理名称表,将L3和L4映射到第二个物理名称表,如下图所示。
(2)垂直镜像将L1和L3映射到第一个物理名称表,将L2和L4映射到第二个物理名称表,如下图所示。
(3)单屏幕镜像将所有四个逻辑名称表指向同一个物理名称表,如下图所示。
(4)四屏镜像在磁带本身中使用额外的2KB RAM,允许逻辑名称表映射到各个独立的物理名称表,如下图所示。
精灵是要画在屏幕上的角色。精灵可以是8x8像素,也可以是8x16像素。大多数角色是由多个精灵组成的。精灵图案数据存储在模式表中,而精灵属性存储在ram中。最多有64个精灵,每个精灵在ram中使用4个字节。字节的工作方式如下:
字节0:存储精灵的y坐标。
字节1:图案表中精灵的索引号。
字节3:存储精灵x坐标。
字节2:存储精灵的属性表。
(1)Bits 0 -1——颜色的最重要的两位。
(2)Bit 5——表示这个精灵是否比背景拥有显示的优先权。
(3)Bit 6——表示是否水平翻转精灵。
(4)Bit 7——表示是否垂直翻转精灵。
8x16精灵根据索引号使用不同的模式表。如果索引号是偶数,则精灵数据位于第一个模式表中的$0000。否则,它在第二个模式表中为$1000。
可以一次读取或写入一个精灵,方法是先将所需的地址写入$2003,然后读取或写入$2004。或者,整个SPR-RAM可以通过一个DMA操作在$4014处写入。
精灵的优先级是基于他们在SPR-RAM中的位置。第一个精灵称为sprite 0,具有更高的优先级。在每一行,系统会计算出哪些精灵在哪一行,然后画出它们,最低优先级优先,以确保高优先级的精灵画在最上面。每个扫描线只允许8个精灵,系统通过设置$2002的I/O寄存器第5位来指示何时达到这个数字。
用于滚动的一个常用技术涉及到确定sprite 0是否重叠了一个不透明的背景像素。如果系统正在绘制sprite 0,并且其中任何不透明的像素与不透明的背景像素处于相同的位置,那么系统将在$2002的第6位中设置sprite 0 hit标志。因此,如果背景块只包含透明像素,那么sprite 0 hit标志将不会被设置。下图显示了sprite 0检测。左边的图像显示背景,中间的图像显示精灵,右边的图像显示两者的合成。颜色0表示透明度,圈内的像素表示设置了sprite 0 hit标志的位置。
精灵通常比单个字符大,因此由多个字符组合而成。例如,如下图显示了马里奥角色是如何由8个独立的8x8字符组成的。
背景可以水平或垂直滚动,滚动使用单独的名称表。在任何给定的时间,屏幕上的背景要么直接取自一个名称表,要么是两个名称表的组合。如下面两张图所示,第一张图显示了两个名称表的内容(另外两个当然是镜像),第二张图显示了屏幕上显示的合成图像,包括精灵。
最后的图像从第一个名称表开始,一直延伸到第二个名称表。第一张图显示了两个名称表之间的灰线分隔。两条蓝线表示屏幕上显示的区域。在屏幕的左边是已经显示的部分,现在已经从屏幕上滚动了。在屏幕的右侧是系统当前正在用前面的名称表填充名称的地方,当“马里奥”继续前进时,名称表将显示在屏幕上。正如被切成两半的云所展示的,并不是所有的区域都被系统填满了。有些游戏只允许在一个方向上移动,而有些则允许在两个方向上滚动。这是任天堂描述如下:
“PPU一次只能显示960个字符,但它实际存储的字符是这个数字的两倍。在单向滚动中,新字符不断地替换滚动后面的旧字符。这就是为什么在像《超级马里奥兄弟》这样的游戏中,屏幕只能向一个方向滚动。然而,在《大都会》中,玩家可以在两个方向上滚动,并不断地向滚动方向添加新角色。”
水平和垂直滚动的大致情况如下图所示,这里显示的名称表A是由$2000的0-1位指定的,B是后面的名称表(这取决于镜像技术)。这不适用于允许同时水平和垂直滚动的游戏,背景图像将跨越名称表。
滚动的工作方式在[8]中进行了描述,并在这里进行了总结。系统维护一个16位的VRAM地址寄存器,其值设置为$2006。寄存器的布局如下:
Bits 0-11——将名称表中的地址存储为$2000的偏移量。BIts 0-4是x-scroll(指示X方向上的滚动),并随着线条的绘制而递增。当这个值从31增加时,它将清0并交换Bit 10。Bits 5-9是y-scroll(指示Y方向上的滚动)并在行尾递增。当从29开始增加时,它将清0并交换Bit 11。如果写入到$2007的值设置为大于29,那么当它达到31时,它将清0,但是第11位不受影响。
Bits 12-14:位是平铺y偏移量。
由于x-scroll和y-scroll表示平铺数字,因此允许32个平铺在屏幕上(256个像素),30个平铺在屏幕上(240个像素),总共960个平铺。
还有第二个临时VRAM地址寄存器,也是16位长。最后是3位平铺的x偏移量。这些数据通过写入寄存器和绘制帧来更新。
NES连接电视向用户显示游戏。NTSC(国家电视标准委员会)是北美、南美大部分地区和亚洲部分地区使用的标准。PAL(相位交替线)是欧洲、亚洲大部、非洲和大洋洲所采用的标准。下表显示了NES的NTSC版本和PAL版本的区别。
NTSC | PAL | |
帧/秒 | 60 | 50 |
时间/帧(毫秒) | 16.67 | 20 |
扫描线/帧(其中为V-Blank) | 262(20) | 312(70) |
CPU周期/扫描线 | 113.33 | 106.56 |
分辨率 | 256x224 | 256x240 |
CPU速度 | 1.79MHz | 1.66MHz |
电视屏幕上显示的图像是由高速电子流在屏幕上从左到右移动,画出每个像素。一行像素作为扫描线。在扫描线的末端,电子束必须移动到下一行并返回到左边才能继续。完成此操作所需的时间称为水平空白周期(H-Blank)。
在绘制屏幕一次后,电子束必须回到左上角,准备开始下一帧。执行此操作所需的时间称为垂直空白周期(V-Blank)。当进入V-Blank周期时,PPU通过设置I/0寄存器$2002的第7位来表明这一点。这个位将在CPU下一次读取$2002时重置。
在NES的NTSC版本中,屏幕上有240行扫描线(尽管顶部和底部的8行被切断了),它需要额外的3行扫描线的CPU周期输入V-Blank。在绘制下一帧之前,V-Blank周期又需要20行扫描线。
文章:
《说说小霸王学习机的PPU》http://blog.sina.com.cn/s/blog_50658d150102xc4b.html
《Nintendo Entertainment System Documentation(任天堂娱乐系统文档)》
资料链接:
https://pan.baidu.com/s/1QhbK6ADAHqe1mHkKGALH6A 提取码:rr69