Neo.Geo系统视频硬件结构模拟,Neo.Geo / MVS Video Hardware Architecture for Emulation ver. 2.0,原作者 by Neoragex2002。
1、Hardware Specification
2、Overview of Video Architecture
在MVS基板的屏幕布局中,其屏幕X/Y位置采用的是笛卡儿坐标系,即Y坐标值是从屏幕上方至下方递减的。其基本视频结构可以分为两大层次(两大平面),最上层是8×8 Text Tile Plane,底层是16×16 Character Tile Plane。其中的8×8瓦砾块布局平面较为简单,整个屏幕被划分成尺寸为8×8象素的格点,一共横40个 × 纵32个格子,每个8×8瓦砾块以格点定位方式绘制。而16×16瓦砾块布局平面则相对复杂,整个平面由若干个条纹链(Strip Chain)构成,一个条纹链又由数个垂直条纹(Tile Strip,条纹对应的显存存储区域称为Tile Bank或者是Tile Block)水平并列构成,而一个条纹又由若干16×16瓦砾块垂直排列构成;事实上,为了理解方便起见,可以直接将其理解为:整个16×16瓦砾块布局平面由许多16×16瓦砾条纹构成,而所谓的条纹链,则只不过是编号连续的等长条纹水平相邻并列构成的一个方块图案(Tilemap),例如图中就存在5个条纹链,其中条纹3/4/5构成了一个tilemap,条纹6/7和8/9/10构成了另两个tilemap,其余两个条纹链均只由一个条纹构成;5个条纹链构成了5级Z-order。
再次提醒,特别注意Neo.Geo系统中对各视频单元的称呼。在Neo.Geo系统中,Tile Bank、Tile Block和Tile Strip均指“条纹”,而Sprite、Strip Chain、Strip Link则指代“条纹链”,为避免与其它系统如CPS1/2中的Sprite/Bank等泛用概念相混淆,下文将统一使用“条纹/Strip”和“条纹链/Strip Chain”这两个Neo.Geo系统所专有的术语。
可以看出,Neo.Geo基板的视频结构较Capcom的CPS1/2真是简单得多,例如在CPS1系统中就存在着2个背景层、1个活动精灵层和1个文字层,在MVS系统中,除8×8文字图层以外,不存在背景层、活动精灵层的划分,无论是背景层还是前台活动精灵层,一律采用可重叠的条纹链/条纹实现。这种方式虽然简单,但是很灵活,如CPS1系统中仅能通过2层背景卷轴实现远近景纵深效果,而在MVS中,只要不超过最大条纹数目,便可轻易实现n重卷轴,同时就人物图案大小而言,MVS能够实现比CPS1更大的人物图案(资深玩家肯定记得龙虎之拳2里面的人物和街霸2里面的人物哪个物理尺寸大了),当然,凡事有利弊,更灵活的视频结构带来的也是更为复杂烦琐的游戏视频设计和实现,不过值得庆幸的是尝试作为模拟器开发者的我们并不需要吃这些苦头了,遭殃的只是基板游戏开发者:D 总结一句:MVS系统的视频结构我感觉比CPS1要灵活,组合可能更多,功能更强,但是貌似结构更简单。
3、16×16 Tiles Plane,在本节中,一般无特殊指明场合下,Tile一词均指16×16的瓦砾块。
3.1、2D Rendering Capability
作为一块最为典型的2D Game Board,MVS基板的渲染能力可以从两个层面来看待:瓦砾块渲染能力和条纹渲染能力。由于8×8 Tile及8×8 Text Tile Plane基本上是直接绘制,因此,2D渲染能力主要是针对16×16 Tile及其平面的绘制而言,这一点下文讨论8×8 Tile平面时将会提到。
对于瓦砾块渲染能力而言,主要包括水平翻转X-Flip、垂直翻转Y-Flip和自动翻转功能(Automatic Animation)三种。前面两个很容易理解,略过。最后面这个概念貌似很新鲜,其实就是游戏开发者可以为某Tile设置一个自动翻转标志,在显示该Tile的时候就可以依据帧计数在若干个Tile之间轮流自动切换显示,就像网页中的一个小Gif动画图片一样,而无需人为编程干预,在很多FTG游戏背景中采用了这种机制以简化活动背景开发。条纹渲染能力则相对复杂一点。主要包括 X/Y坐标定位、X-Zoom水平缩放和Y-Zoom垂直缩放三种。下面专门分五个小节叙述。
3.2、X/Y Location of Strip Chain
在MVS系统中,显示坐标定位并不是针对逐个Tile进行的,而是针对逐个条纹进行的,由于一个条纹中包含了若干个瓦砾块,而这些瓦砾块又永远都是垂直向下顺序排列的,因此,仅只需要知道某条纹的左上角X/Y坐标、条纹拥有的瓦砾块总个数和其中每一个瓦砾块的编号,便可唯一确定其中每一个Tile的位置。
再回过头来说说什么是条纹链。将若干个“连续”的条纹水平排列构成一个矩形Tilemap,这个矩形的Tilemap就叫做一个条纹链。条纹链中所有条纹的Y坐标均是相同的,而相邻条纹的X坐标则相差一个瓦砾块的象素宽度(无水平缩放情况下16 pixels,X-Zoom情况下1-15个pixels宽)。这里的“连续”不仅仅指的是屏幕上位置排列相邻,而且还涉及到显存当中对应条纹数据存放地址的连续性,这将在下文显存具体数据结构一节中谈到。在MVS显存中,并没有存储每一个的条纹X/Y坐标,而是仅存储了每一个条纹链首条纹的左上角X/Y坐标,原因也很简单——因为没有必要:条纹链中的任意一个条纹的Y坐标与链首条纹的Y坐标相同,而任一条纹的X坐标等于链首条纹的X坐标再加上该条纹以左处于同一条纹链中的所有条纹的水平象素宽度(X-Zoom等级值+1)之和。
3.3、X-Zoom of Tile Strip
缩放功能是MVS 2D渲染中最具特色的功能。MVS中的缩放功能均是针对水平、垂直两个方向而言的,其操作是相互独立的。事实上MVS并不具备放大功能(Zoom-in),它只能实现条纹沿水平方向或者是垂直方向的缩小功能(Zoom-out)。X-Zoom水平缩放是专门针对单个条纹实施的2D渲染特性,其功能的实现也很有趣,其一共分为16(0x0-0xf)个缩小级别,缩小功能是通过垂直抽线方式实现的。如下图所示,所有条纹中每一行的象素宽度都是16 pixels,其中的每个点分别用0-F表示,举个例子,当X-Zoom水平缩放级别为15时,这16个点均显示,而当X-Zoom等级为5时,一行仅显示6个点,6个点的位置分别为2/4/6/8/a/c/e,这6个点均相邻显示,也就是说,当水平缩放级别为n级时,经X-Zoom处理之后单个条纹的象素宽度将由16变为n+1。对于条纹当中的每一行象素均使用如上垂直抽线法则,便可实现整个条纹的水平缩放。其实也可以将X-Zoom理解成为16×16 瓦砾块所具有的2D渲染能力。
3.4、Y-Zoom of Strip Chain
Y-Zoom垂直缩放特指针对整个条纹链垂直方向的缩放操作。与水平缩放类似的,MVS也只能实现垂直方向的条纹链缩小渲染,尽管Y-Zoom操作是针对整个条纹链进行的,但是在概念上也可以理解成为针对链中的每一个并列排放的条纹分别进行。同时,条纹垂直缩放同样具有自己的缩小级别,共256级,可以看出,垂直缩放级别数比水平缩放级别数要多得多,因此实现方式要比水平缩放功能复杂得多,其具体机制一言以蔽之,就是通过查表方式进行水平抽线实现垂直缩放。
在MVS系统的Bios当中,存在着一个名为000-lo.lo(亦名为ng-lo.rom,CRC:e09e253c)的文件,长度为256×256字节。该rom被称为Zoom Rom,内部存储了256张Y-Zoom子表,对应256个Y-Zoom级别,每张子表共有256个子项,对应着水平抽线,每子项占用1字节,其高4位nibble代表着当前条纹Tile Bank中对应的Tile索引号,而低4位nibble则对应着该16×16 Tile中的水平象素行索引号。举个具体的例子,下图的第一个表是编号为04(从0开始编号)的Y-Zoom子表,对应着垂直缩放级别04(从0开始编号)。
在MVS系统中,一个条纹所对应的Tile Bank最多只能存储0x20个Tile数据,也就是说单一条纹最多只能包含0x20个Tile,即最多拥有0x20×0x10=512根宽度为16 pixels的水平线,但是Y-Zoom子表中却只有256个子项,其仅仅只直接给出了前256根水平抽线的缩放数据。当条纹拥有Tile数超过0x10个时Y-Zoom实现变得较为复杂,我们首先讨论个数低于0x10(即条纹象素行<256行)的情况。
如上图所示,根据第4号Y-Zoom子表,在Y-Zoom等级为04、无X-Zoom的情况下,一个16列×256行的条纹被实际缩小成16列×5行,即当垂直缩放等级为n时,经Y-Zoom处理之后单个条纹的象素高度(有效水平抽线数)将为n+1;其中,输出条纹图案的第0行象素抽取自该条纹Tile Bank中第0个tile的第8行(0x400h – 0x08),而输出条纹图案的第1行象素抽取自第4个tile的第8行(0x401h – 0x48),etc. 子项值为0xff代表着第15个tile中的第15行。
此处应当注意两个问题:一个是关于透明象素行的问题,一个tile可以是透明的,所以其中的每行象素也均是透明的,通常在一般情况下,Tile Bank中的第15个tile为一个透明的tile,因此子项值0xff一般代表着一根透明的也就是无需绘制的“虚”抽线;另外一个是等比垂直缩放的问题,Y-Zoom等级值的真实含义应该理解成为——无论条纹的原始象素高度h为多少,在经过Y-Zoom等级为n的垂直缩放操作之后,其实际象素高度均(近似)缩小成为原象素高度的(n+1)/256。
再来看看上面的第255号Y-Zoom子表,对应着最高的垂直缩放等级255,从表中可见,当垂直缩放等级为255时,如果条纹的原始象素高度为256,那么将不进行任何水平抽线操作,经过Y-Zoom之后输出的实际象素高度依然为256,也就是不进行任何缩小操作,直接依次显示条纹对应的Tile Bank中前16个Tile的每一行。
3.5、Expanded Y-Zoom of Strip Chain
上一节提到单一条纹最多可以容纳0x20个Tile,而只讨论了MVS中当条纹拥有少于16个tile时Y-Zoom功能的标准实现,那么当条纹拥有16个以上的tile时,Y-Zoom又是如何工作的呢?MVS专门针对大于0x10个 Tile的Y-Zoom实现进行了相应扩展,先讨论其初步扩展情况,其示意图如上图所示。
图中包括4根水平轴线,其中第3根轴线tile_no表示条纹中从上至下tile的编号(对应着Tile Bank中存储Tile数据的递增序,从0到0x1f共0x20个);第1根轴线draw_lines表示条纹未垂直缩放之前的水平线线数,即象素行数(从上至下,从0至511共512行);而红色的线条则表示经过等级为5的Y-Zoom操作之后,最终抽取出来的水平线。可以看到,0x20个Tile的水平抽线图谱首先是一种左右对称结构,其对称轴恰好是第255-256行水平线,也就是说,首先根据第n号Y-Zoom子表得到前0x10个Tile的水平抽线图谱,然后将其翻转至draw_lines轴的右半轴,两半图谱一拼即可初步得到整个条纹(共0x20个Tile)的抽线谱,根据得到的抽线谱依次抽取红线所在位置所代表的水平象素行,然后再将其从上至下垂直排列起来便可得到第n级垂直缩放后的Y-Zoom条纹图案。
在上述对称结构理解的基础之上,再来看看余下的两个轴线:第2根轴线画出了条纹水平线数draw_lines与其对应的Y-Zoom子表中表项号zoom_lines之间的映像关系,例如,在绘制第0xf(0x100/0x1f4)行垂直缩放输出时,应该查找对应Y-Zoom子表中的第0xf(0xff/0xb)个表项,可以看出,draw_lines与zoom_lines的映射关系为:zoom_lines=(draw_lines & 0xff) ^ 0xff。
而剩下来的第4根轴线yoffs则代表了Y-Zoom子表项中的低4位nibble,即tile内行偏移值。由于是对称抽线谱结构,因此,在Y-Zoom子表项号zoom_lines对称的基础之上,凡属是编号大于0xf的tile,其所对应的tile内行偏移值应该是原查表值的16补,图中轴线上的一格是16个象素,在yoffs轴线上能够清晰地看出这一点,如draw_lines=0xec时,有zoom_lines=0xec,其tile_no=0xe,yoffs=0xc;另一方面,在右边的对称抽线位置,draw_lines=0x113(0xec ^ 0x1ff),有zoom_lines=0xec,但是其tile_no=0x11(0xe ^ 0x1f),而yoffs=0x1(0xe ^ 0xf);综上,可以得到yoffs与zoom_lines的映射关系为:
当tile_no小于0x10时,yoffs=YZoom[zoom_lines] & 0xf;当tile_no大于0xf且小于等于0x20时,yoffs=(YZoom[zoom_lines] & 0xf) ^ 0xf。
至此,大于0x10个tile的条纹Y-Zoom初步扩展实现分析完毕。
3.6、Fullmode Y-Zoom of Strip Chain
上一节分析了大于0x10个Tile的条纹Y-Zoom的初步扩展,但事情还远没有结束。Full Mode是Y-Zoom垂直缩放的一种特殊的扩展方式,其实现过程是MVS系统2D渲染能力中最复杂的,而且很有点考想象力:) 我们一步步来看。首先我们从一个具体的实例入手,仍然以Y-Zoom等级为4的子表为例,如下图所示,这是未经过扩展的第4号Y-Zoom子表,上文曾提到,当某条纹Tile Bank中只有16个Tile时,在没有垂直缩放的情况下,该条纹实际象素高度应为16×16=256;下图等级为4的Y-Zoom子表数据说明,在垂直缩放等级为4 (n)的情况下,该条纹被压缩得只剩下5(n+1)根水平线了,这4根水平象素行分别抽取自第0个Tile的第8行、第4个Tile的第8行、第8个Tile的第8行、……和第12个Tile的第8行。
以上是MVS中当条纹拥有的Tile数少于16个时Y-Zoom功能的简单实现,然而,一个条纹最多可以包含0x20个Tile,为了支持条纹Tile个数超过16个以上的情况,MVS首先需要对Y-Zoom标准实现进行初步扩展,办法就是将原始的Y-Zoom子表扩展至512个子项,如下图所示。
上图是经过了初步扩展的第4号Y-Zoom子表和其所对应的对称抽线谱,注意到其长度已经被扩展至了512个子项,而且呈对称结构。在初步扩展的对称Y-Zoom子表中,浅蓝底色框出了原有的子项数据区域,其子项的数据解释如上文所述,高4位nibble代表着Tile索引号,低4位nibble则代表Tile中所抽取的水平象素行编号,没什么特别的。但是其中以深蓝底色框出的代表着对称结构的特殊子项数据区域却值得尤为注意,在深蓝底色框出的特殊子项数据区域中,每个子项的高4位nibble并不直接等于当前条纹Tile Bank中的Tile编号,其与Tile编号的映射关系为:假定高4位nibble值为x,则其代表的当前条纹Tile Bank中的Tile编号为x^0x1f(即0x10+[0xf-x]);而且其子项的低4位nibble含义也并不直接等于Tile中的水平行偏移,其映射关系为:假定低4位nibble值为y,则其映射的Tile内水平象素行偏移为y^0xf。这些数据解释与上一节对称抽线谱的相关描述是一致的,以上关于深蓝底色子项数据区域与浅蓝底色子项数据区域的数据解释请结合对称抽线谱理解,再强调一次,请特别注意两者的区别。
接下来讨论最麻烦也是最关键的实现环节——Full Mode情况下的Y-Zoom实现。在MVS中,一般用来构成背景层条纹链的条纹所包含的Tile个数均等于0x20(或是大于0x20,均以0x20计,因为Tile Bank中最多只能存放0x20个Tile数据),即这些条纹所对应的Tile Bank中存满了0x20个Tile数据。针对这种特殊条纹的垂直缩放操作被称之为Full Mode of Y-Zoom操作,也就是说,当我们针对Tile个数大于等于0x20的条纹进行Y-Zoom操作时,必须使用Full Mode扩展方式。Full Mode实际上是垂直缩放操作的一种特殊的扩展形式,其具备着与众不同的诡异特征——周期对称抽线谱。
那么MVS系统的Full Mode扩展方式究竟是怎样实现的呢,细节先不表,考验观察力和想象力的时刻到了,请直接看下图,在上面初步扩展子表的基础上,下图进一步给出了Y-Zoom等级为4的Full Mode扩展子表,从这个代表着最终扩展结果的Y-Zoom子表中你能观察出Full-Mode的扩展规律吗?
规律其实不算复杂,但是也挺繁琐,在Full Mode扩展Y-Zoom子表中,前256个子项从第0个子项开始呈递增周期扩展,一个扩展周期占用2×(4+1)=10个子项,每个扩展周期中呈前后对称形式;而后256个子项则从第511个子项开始呈递减周期扩展,每个周期中又呈前后对称扩展。图中右边的箭头表示条纹内水平象素行的绘制顺序:某条纹包括了0x20个Tile,因此我们绘制该条纹时共需绘制512条水平象素行,首先根据扩展子表从上至下地(Y坐标递增)绘制出前256根水平象素行,然后再根据扩展子表从下至上(Y坐标递减序)绘制后半部分的256根水平线。Full Mode扩展子表当中的浅蓝底色子项与深蓝底色子项的数据解释与上文所述的一般扩展方式相同(特别注意两者的差异!)。
上图为一个Y-Zoom等级为4的Full Mode具体实例,其扩展方式完全可以类推至Y-Zoom等级为n的情况,在这种一般情况下,一个扩展周期占用2×(n+1)个子项,而每个周期中也呈现出相应的对称扩展趋势。
3.7、Video Memory Layout
下面将详细阐述16×16 Tile层所涉及的显存结构,其主要涉及4部分显存区域,可结合上文以增进对MVS显示硬件结构功能的理解。以下地址均指相对于显存首部的偏移而非68K地址空间的绝对寻址位置:
(1)0x00000 - 0xdfff:条纹数据区(Tile Bank数据区),划分成每0x80字节一块(一个Tile Bank),每一块中存储某一条纹所涉及的所有Tile数据;每一个0x80字节块由0x20个DWORD构成,每个双字存储了一个Tile相关的渲染信息;当MVS绘制条纹图案时,同一Tile Bank中(同一条纹中)的每个双字Tile将依地址递增次序从上至下绘制。其中双字的格式如下(Big Endian字节序,从低位到高位):
其中,第4字节的第4比特至第6比特分别代表着瓦砾块图案编号(共16位)从低到高的扩展MSB位,即,加0x10000(若第4 bit为1) / 加0x20000(若第5 bit为1) / 加0x40000(若第6bit为1)。值得指出的是,当C roms中存在的Tile总个数并没有达到需要使用这些MSB位的时候,这些MSB位应当被忽略掉,例如,Magician Lord,这个游戏所拥有的Tile总数最多不超过0x10000个,用16个bit来表示其Tile编号足矣,在这种场合之下,3个MSB位都应该被忽略掉。
(2)0x10000 – 0x103ff:条纹配置区域A,划分成0x200个WORD,最多可以容纳0x200个(实际只需384个即可)条纹的相关显示信息;其中每个字的格式如下(Big Endian字节序):
3、0x10400 – 0x107ff:条纹配置区域B,划分成0x200个WORD,可容纳0x200个条纹显示信息(实际只需384个);其格式如下(Big Endian字节序):
其中值得注意的是Bit 6条纹链标志,如果其为1,则当前条纹(当前Tile Bank)将被直接绘制在与前一条纹(前一Bank)相邻接的右方,同时两者Y坐标值取值相同,共同构成整个条纹链中的一个局部环节。
此外,关于其中低6位所代表的Tile个数这个域(简称Tilenum),还需要特别说明一下:经验证(侍魂4),Tilenum只代表着对应条纹链的应当绘制的实际水平线数(tilenum×0x10),而并不意味着在其对应的Tile Bank中只存在着Tilenum个有效的瓦砾块数据,举个例子,某条纹所对应的Tilenum为6,这说明在绘制该条纹时,在无Y-Zoom的情况下,一共要画6×16条水平象素行,但是这并不意味着该条纹所对应的Tile Bank中只存在着6个有效的瓦砾块双字数据,由于还需要结合Y-Zoom水平缩放特性来考虑,因此该条纹所对应的Tile Bank中完全有可能存放着多于6个有效的Tile数据。
(4)0x10800 – 0x10bff:条纹配置区域C,划分成0x200个WORD,可容纳0x200个条纹显示信息;其格式如下(Big Endian字节序):
在本节的最后再强调两点:
A、在显存映射布局当中,存在着3种条纹数据——条纹链首条纹、条纹链中的条纹和无效的条纹;条纹链首指的是那些包含Tile总个数不为零且链标志为0的条纹数据;链条纹指的是那些链标志为1且与条纹链首存储地址连续的条纹数据;剩下的都是无效条纹。注意,这里特别强调一下条纹链首的条纹数据与条纹链中其他条纹数据在显存中存放地址的连续性,因为也有可能存在着某些无效条纹数据,其存放地址位置并没有与链首条纹或者是其他链条纹相邻,而其链标志却为1(龙虎之拳2),所以单从链标志本身无法判断一个条纹数据究竟是一个无效条纹还是一个链条纹,必须结合首条纹、链条纹存放地址的连续性来考虑。
B、在上述提及的3个条纹配置区域中,X坐标值、Y坐标值、Y-Zoom值、和Tilenum值均为整个条纹链的属性,同一条纹链中的各个条纹均具有相同的Y-Zoom值和Tilenum值,各条纹的X坐标和Y坐标则根据链首条纹的X/Y坐标类推得到;而X-Zoom值则为条纹属性,同一条纹链中的各个条纹可能具有不同的X-Zoom值。
3.8、Z-order of Strip Chains
在显存结构和基本视频架构的基础之上,来看一下视频结构中的Z-order是如何实现的。在MVS系统中,所谓Z-order(Z轴次序)指的是各个条纹链所代表的tile map之间的相互叠加关系,即,谁是前景、谁是背景的问题,越远离读者的层次Z-order值越小。这里需要强调的是,MVS中的Z-order关系只存在于条纹链与条纹链之间,同一条纹链中的条纹之间或者是同一条纹中的tile之间并不存在着叠加关系。
上图是MVS显存中可能的线性条纹映射结构示意图,对应着本文第二节所给出的那个基本视频结构图;图中每一个格子代表着一个可能的条纹存储位置,最多16×24=384个条纹位置,一个红色格子(首条纹)和后继相邻的若干深灰色格子(链条纹)构成了一个完整的条纹链;上图一共给出了5个条纹链,其中条纹链1的Z-order值最小,为0;条纹链2的Z-order值次之,为1,条纹链5的最大,为4,etc. 结合本文第二节的示意图其相互叠加关系可以看得很清楚了:位于高地址位置的条纹链(条纹)Z-order永远比低地址的条纹链(条纹)要高,每个条纹链都有其唯一的Z-order,不存在任何两个条纹链其Z-order值相等。
有了以上Z-order比较的准则,视频帧绘制过程便简单了:按照显存地址位置从低到高的顺序,逐一绘制出每一个条纹链即可,后画的条纹链图案会把先画的底层条纹链覆盖掉,这个过程本身便能够保证Z-order重叠关系是正确的,尽管在实际模拟的过程中绘制效率并不高,但硬件的确是这样运行的。
3.9、C Roms Decoding
当C Roms以奇偶交织定址方式载入之后,还要进行一层解码(Sprites Decoding),将瓦砾块图案象素数据解码成为如下格式:各个16×16 Tile的象素数据连续存放,每Tile由连续128字节表示。每8字节一行,16行象素共16×8=128字节,在每字节当中,高位4 bits(X+方向)和低位4 bits(X-方向)分别代表着相邻的两个象素,即,每象素可以选择16种颜色作为自己的颜色,这16种颜色选自当前色板Bank所指向的256个色板,整个Tile选用的色板值取自0x0000-0xdffff条纹数据区域中的对应双字域,其中值得注意的是,无论是哪个色板,其第0号颜色始终代表着透明色,也就是说当4个bit值均为0时代表着该点透明,无需绘制。在实际绘制时,还需要结合瓦砾属性X-Flip、Y-Flip等考虑绘制次序。
4、8×8 Tiles Plane,在本节中,一般无特殊指明场合下,Tile一词均指8×8的瓦砾块。
4.1、S Rom Decoding
在128KB的S Rom中存储了所有8×8 Tile的象素图案,一共4096个。在进行了解码(Text Decoding)之后,各8×8 Tile的象素数据连续存放,每Tile由连续32字节表示。Tile中的每水平行象素占用4字节,8行象素共8×4=32字节,在每字节当中,高位4 bits(左边的点)和低位4 bits(右边的点)分别代表着相邻的两个象素,即,每象素可以选择16种颜色作为自己的颜色,这16种颜色选自当前色板Bank所指向的256个色板中的前16个色板,其中值得注意的是,无论是哪个色板,其第0号颜色始终代表着透明色,也就是说当4个bit值均为0时代表着该点透明,无需绘制。8×8 Tile不存在X-Flip / Y-Flip及X-Zoom / Y-Zoom等render操作,比16×16 Tile简单太多。
4.2、Video Memory Layout
8×8 Text Plane仅涉及一个显存区域,其相对于显存首部的偏移值为0xe000-0xea00。该区域被划分成为2字节一块,每一个字中存放着8×8 tile相关的色板和瓦砾编号信息,根据瓦砾块编号信息能够正确地从S rom当中索引到Tile的象素图案数据(偏移值=瓦砾编号×32字节),而根据色板信息则能选择正确的色板为Tile象素上色。其中字格式如下(Big Endian序),很容易看出,S rom中最多只能存储0x1000(编号最多12bit)即4096个8×8 Tile,而每一个Tile只能使用一套色板,由于色板编号只有4位,所以其只能选择自当前色板Bank中的前16个色板。
上文曾经提到过绘制8×8 Text Plane时,每个8×8 Tile的定位是根据格点定位的,即整个屏幕被划分成为横40个×纵32个格子,每个格子尺寸为8×8 pixels,每个Tile就画在格子里。而0xe000-0xea区域中的Tile信息是以列优先2维数组方式排列的,先存第0列,然后再存第1列….etc. 也就是说,屏幕上格点位置为x/y的Tile,其在0xe000区域中对应信息字的存储位置(字偏移地址)为:Pos=(0xe000>>1)+y+x*32。
8×8 Text Plane的映射过程搞清楚之后,其绘制过程也就很清楚了,简单地遍历0xe000中的所有Tile字信息,然后将Tile绘制到偏移址所对应的屏幕格点位置即可。注意在8×8 Text Plane中各Tile之间并不存在内部Z-order关系,而8×8 Text Plane在整个MVS中其Z-order是最高的,也就是说,其必须在16×16 Tile Plane绘制完毕之后再渲染。
5、Palettes
MVS系统中的色板存储采用的是同址映射方式,也就是说,存在着2份色板数据,即2个Bank,这2份数据均映射至M68K CPU的同一地址空间处,即绝对偏移0x400000至0x401FFF处(共8192字节);每个Bank里又分为256个调色板,每个色板分别存储16种颜色,共4096种颜色,每种颜色用一个Word(16比特)表示。
既然是同址映射方式,那么我们怎么知道当前正在使用那一个色板Bank呢?在MVS中,选择色板Bank是通过I/O操作来实现。通过在M68K CPU地址空间绝对偏移址0x3A000F处或者0x3A001F处写入任意一字节,便可分别实现色板Bank 1和色板Bank 0的选择。这里要注意的是:M68K不同于x86 CPU,它的存储地址和I/O地址是统一编址,也就是说在M68K中并不存在端口号、端口地址这种独立的概念,因为其I/O端口定址的形式与内存存储地址是一致的;而同址映射——I/O选择切换这种存储机制在街机系统中也是很常见的,原因很简单——为了节省CPU的内存地址空间。
上文提到每种颜色由1个Word表示,下图给出了一个简单的计算R/G/B三色分量值的比特位映射图,其算法取自FBA的标准实现。
最后说说Neo.Geo系统中所用到的两种特殊类型的颜色:背景颜色和透明色。经确认(合金弹头2),在绘制16×16 Tile层和8×8 Tile层之前,MVS系统将首先使用当前色板Bank中的第4095号颜色(即4096种颜色当中的最后一种)填充整个屏幕,作为整个帧画面的背景颜色。而至于透明色,上文关于显存结构及S rom的解码部分说得很清楚了,这里总结一下:当前色板Bank中一共可分为256个调色板,每个色板的第0号颜色总是代表着透明色,也就是说色板Bank中存在着共计256种代表着透明色的颜色。在绘制使用其中某色板的Tile时,不管是8×8的还是16×16的,如果其中某象素使用了色板当中的第一个颜色(第0号),那么该象素实际上无需绘制,因为它是透明的。
6、Video Emulation Flow
全世界的模拟器基本运行流程都差不多,简单说说其中的视频模拟过程,一共可分为四个步骤,周而复始,一秒钟循环的次数等于MVS垂直刷新率(59.185 fps):
A、模拟过程:M68k Core模拟执行Rom中的代码,模拟运行的结果表现为显存数据的改变;
B、映射过程:将显存数据结构映射至具体的条纹数据/渲染属性、Tile编号/渲染属性和色板;
C、解码过程:根据条纹/Tile的属性,结合C/S Rom中的图案数据将Tile逐个绘制至模拟器视频缓冲区;
D、渲染过程:将模拟器视频缓冲区Blit至真实视频缓冲,并合理控制好相应时间因素,如跳帧和延迟等。
7、Video Frame Rendering Flow
当绘制单帧时,为保证Z-order的正确,首先使用背景颜色填充整个视频帧,然后再在此基础之上渲染整个16×16 Tile层,最后才能在其之上绘制8×8 Text Tile层。已经写得很长了,直接看下面的流程图吧。
8、Glossary
9、To-do List
(1)增补8×8 Text Tile Plane的显存结构(done)
(2)增补调色板详细信息(done)
(3)增补视频帧背景颜色设置细节(done)
(4)增补tilenum数据域的相关细节(done)
(5)增补MSB数据域的相关细节(done)
(6)解开Sprites Plane Fullmode之迷 (done)
(7)8×8 Tile、16×16 Tile的详细解码过程(done, not online)
10、Reference
11、Credits,All credit belongs to the people who devoted his life to the emulation of Neo.Geo!