这次说一下GB的显示系统,先从一幅Gb的内存分布图说起,请看图:
图中红色框框起来的部分就是这篇文章关注的部分,这一部分的内存地址从8000-9Fff,共8KB,这一部分是从来存储背景和游戏“精灵”的数据的。通常我们认为Gb只能显示黑、白两种颜色,但其实,Gb还能显示明、暗灰度,总共是4种颜色。这样,显示出来的每个像素点需要占用两个bit的空间。对于Gb的显示系统来说,屏幕会缓存256*256大小的图片,其中160*144大小的某个区域会显示在液晶屏上,我们算一下,要缓存的图片的大小为256*256*2=16KB。但是我们的显存只有从8000-9Fff的8KB,显然是放不下的。所以Gameboy使用了某种机制,使不够16KB的显存也能够显示出16KB大小的内容,这种机制就是再显存中一部分用来存放图片数据,一部分用来存放映射数据。图片被划分为每个8*8大小的块(tile),每个块有一个编号,而在映射数据的部分存的只是每个块的编号,这样子,每个块就有可能会被重复使用,从而达到在不足16KB显存的情况下显示16KB数据的目的。这就解释了为什么在上一篇文中,我直接把任天堂的logo数据加载成图片,显示出来的东西确怎么看都不像是一个logo,其实加载进来的数据只是一个映射数据,而不是真正的图片数据。
好,我们继续。知道了gameboy的显示机制之后再重新看一下显存中的数据分布情况,请看下表:
区域 |
作用 |
8000-87FF |
图块集1: 图块编号0-127 |
8800-8FFF |
图块集1: 图块编号128-255 |
9000-97FF |
图块集0: 图块编号0-127 |
9800-9BFF |
图块集0的映射区域 |
9C00-9FFF |
图块集1的映射区域 |
图块集1可以用来显示背景、窗口和精灵,图块集0用来显示背景和窗口。这里的窗口指的就是160*144大小的在液晶屏上显示的区域。
精灵
Gameboy可以控制40个精灵的显示,每个精灵的大小为8*8或者8*16的图片块,同时,受硬件能力限制,每个扫描线(scan line,对这个概念理解得不是很好,希望有人能帮助解释一下)只能显示10个精灵。每个精灵的图片数据存放在8000-8Fff的区域中,就是上面说的图块集1所在的区域。而精灵自身的属性数据(x和y坐标等数据)则存放在专门的精灵属性表中(Sprite Attribute Table),也叫对象属性内存OAM(Object Attribute Memory),该区域位于内存FE00-FE9F中,OAM被划分成40个4字节的块,每个块代表一个精灵。
对于精灵对象,x坐标越小则它的显示优先级越高,对于拥有同样x坐标的精灵对象,则通过它在OAM中的顺序来区分显示优先级(FE00的最高,FE04的次之,然后依次往下)。
由于最多只能有10个精灵在同一行上显示,所以,在一行有超过10个精灵的时候,显示优先级低的将会被隐藏。为了避免不使用的精灵影响正常精灵的显示,应该把这些精灵的位置设为y=0 or y>=144+16或者x=0 or x>=160+8.
下面说一下OAM中每个精灵属性数据的含义:
Byte0: y坐标值
Byte1: x坐标值
Byte2: 精灵的图块编号,对于8*16模式大小的精灵,LSB(最低有效位?)被忽略
Byte3:标志位:
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
值0 |
显示在背景和窗口的顶端 |
从OBJ0PAL取色 |
||||||
值1 |
如果背景和窗口关的颜色为1,2,3则隐藏 |
垂直跳动 |
水平跳动 |
从OBJ1PAL取色 |
颜色表:
值 |
模拟的颜色 |
0 |
[255, 255, 255] |
1 |
[192, 192, 192] |
2 |
[96, 96, 96] |
3 |
[0, 0, 0] |
好了,再了解完gameboy的显示机制之后接下来就要看一下在程序上是怎么来完成从内存到液晶屏上的显示过程了:
下表列出并说明了显示中用到的一些特殊的寄存器,特殊是因为这些寄存器是对内存中特定地址的称呼,而不是在Cpu中的寄存器:
FF40 - LCDC – 液晶屏控制器 (R/W)
Bit 7 – 是否开启液晶屏显示 (0=Off, 1=On) Bit 6 – 窗口图块映射范围选择 (0=9800-9BFF, 1=9C00-9FFF) Bit 5 – 是否开启窗口显示 (0=Off, 1=On) Bit 4 – 背景和窗口图块数据范围选择 (0=8800-97FF, 1=8000-8FFF) Bit 3 – 背景图块映射范围选择 (0=9800-9BFF, 1=9C00-9FFF) Bit 2 – 精灵尺寸选择 (0=8x8, 1=8x16) Bit 1 – 是否允许显示精灵 (0=Off, 1=On) Bit 0 – (对于黑白的GB和SGB)背景显示开关 (0=Off, 1=On) 对于CGB在CGB模式下,0=背景和窗口会失去他们的优先级,精灵们还是会显示在背景和窗口的顶端。 对于CGB在非CGB模式下,0=背景和窗口变白,只有精灵任能继续显示 |
注:对于Bit7停止LCD操作只能在v-blank中断中进行,否则会毁坏硬件(我们做模拟器开发的就不需要担心这个了J)
FF41 - STAT - LCDC Status (R/W)
Bit 6 - LYC=LY Coincidence 中断 (1=Enable) (Read/Write) Bit 5 - 模式 2 OAM 中断 (1=Enable) (Read/Write) Bit 4 - 模式 1 V-Blank 中断 (1=Enable) (Read/Write) Bit 3 - 模式 0 H-Blank 中断 (1=Enable) (Read/Write) Bit 2 - Coincidence Flag (0:LYC<>LY, 1:LYC=LY) (Read Only) Bit 1-0 - 模式标志 (模式 0-3) (Read Only) 0: 处于H-Blank中断中,Cpu能访问8000-9FFF和FE00-FE9F 1: 处于V-Blank中断中,Cpu访问同上 2: 正在搜索OAM-RAM,Cpu不能访问8000-9FFF和FE00-FE9F 3: 正在传输数据到 LCD 驱动,Cpu不能访问8000-9FFF和FE00-FE9F。CGB模式下,不能访问调色板数据FF69,FF6B. |
以下是一些典型的数值切换的过程中各个模式的值:
Mode 2 2_____2_____2_____2_____2_____2___________________2____
Mode 3 _33____33____33____33____33____33__________________3___
Mode 0 ___000___000___000___000___000___000________________000
Mode 1 ____________________________________11111111111111_____
模式标志每个周期的切换顺序为0,2,3,一个周期的时间大约为109uS,0大约停留48.6uS,2大约19uS,3大约41uS,这由vblank中断没16.6ms触发一次,模式标志被设为1的持续时间大约为1.08ms.
用时钟周期表示的话模式0大概201-207个周期,模式2大概77-83个周期,模式3大约169-175个周期,一个完整的模式切换大约456个周期,vblank持续4560个周期,一个完整的屏幕刷新大约每70224个周期发生一次
FF42 - SCY – 滚动Y坐标 (R/W)
FF43 - SCX – 滚动X坐标 (R/W)
制定256*256的背景中,哪一点作为液晶屏幕的左上角原点。两坐标的范围为0—255.
the video controller automatically wraps back to the upper (left) position in BG map when drawing exceeds the lower (right) border of the BG map area.
FF44 - LY – LCDC的Y坐标 (R)
LY指示当前那一条水平线上的显示数据被传送给LCD驱动,LY的值可能为0—153,当值为144—153之间时,GB处于vblank中断之中,当写入值时,会重置计数器
FF45 - LYC - LY 比较 (R/W)
GB不停地比较Lyc和Ly寄存器的值,当两个值完全一样时STAT寄存器的Coincidence Flag位会被设为1,然后如果开启了STAT中断的话,会请求该中断
FF4A - WY – 窗口Y坐标 (R/W)
FF4B - WX – 窗口X坐标 (R/W)
指定左上角的窗口位置,当Wx=0—166,Wy=0—143时,窗口可见
下面通过一张图来说明Scy,SCX和Wy,Wx之间的关系:
FF47 - BGP – 背景调色板数据 (R/W) – 非CGB模式,即黑白模式
该寄存器用来给背景和窗口的图块的颜色编号赋上灰度值
Bit 7-6 -Color Number 3 Bit 5-4 - Color Number 2 Bit 3-2 - Color Number 1 Bit 1-0 - Color Number 0 |
颜色值参考上面的颜色值表
FF48 - OBP0PAL – 0号对象调色板数据(R/W) -非CGB模式,即黑白模式
FF49 - OBP1PAL - 1号对象调色板数据(R/W) -非CGB模式,即黑白模式
这两个寄存器分别给0号和1号精灵调色板赋上灰度值,每个位代表的意思和BGP寄存器一样,但最低的两位是不使用的,因为00代表精灵是透明的
好,寄存器的介绍暂告一段落,现在来计算一下。不管是使用图块0还是图块1,GB的映射空间的大小都是3FFF=1024,然后GB的背景是256*256,同时背景是由8*8的图块拼起来的,所以总共需要32*32个图块。而32*32正好等于1024,由此可知通过解析图块映射区内的所有图块的编号就能够得到要显示的背景的像素数据了。
由今天的分析可知每个图块的大小是8*8个像素,占用的空间是8*8*2个bit,共16个字节。下面通过一副图来说明:
由上图,其实每个像素点的信息表示的是一个索引值,用来索引在调试版的颜色,调色板才是用来实际存放颜色信息的地方。
好了,今天的笔记的就到这吧,本来想先完成模拟Cpu部分的指令的代码再来学习显示的,不过在写代码的时候发现,如果没有显示的话都不知道实际模拟的对不对,所以就只能先学习显示部分的知识然后再去写代码了,有了输出差不多就可以判断自己的代码是不是正确了。
下一回将会对今天的知识用代码来进行实现,就是模拟gameboy的显示系统了,希望不要太难,呼,老天保佑。