Open dustpg opened 6 years ago
调色板也是ROM运行起来自己填充的吗
调色板也是ROM运行起来自己填充的吗
@Slackre 调色板“索引”当然是ROM指定的,而默认的64色调色板是固定的
请教一下,ROM运行起来填充名称表总是有一小部分是错误的大概是什么原因呢?
rom运行的起来为什么没有填充vram. 我的指令log是不是不对。可以发一下step4的log吗?感谢
`ROM: PRG-ROM: 1 x 16kb CHR-ROM 1 x 8kb Mapper: 000 1 - $C004 SEI ; A:00 X:00 Y:00 P:34 SP:FD 2 - $C005 CLD ; A:00 X:00 Y:00 P:34 SP:FD 3 - $C006 LDX #$FF ; A:00 X:00 Y:00 P:34 SP:FD 4 - $C008 TXS ; A:00 X:FF Y:00 P:B4 SP:FD 5 - $C009 LDA $2002 ; A:00 X:FF Y:00 P:B4 SP:FF 6 - $C00C BPL $FB(-005); A:00 X:FF Y:00 P:36 SP:FF 7 - $C009 LDA $2002 ; A:00 X:FF Y:00 P:36 SP:FF 8 - $C00C BPL $FB(-005); A:00 X:FF Y:00 P:36 SP:FF 9 - $C009 LDA $2002 ; A:00 X:FF Y:00 P:36 SP:FF 10 - $C00C BPL $FB(-005); A:00 X:FF Y:00 P:36 SP:FF 11 - $C009 LDA $2002 ; A:00 X:FF Y:00 P:36 SP:FF 12 - $C00C BPL $FB(-005); A:00 X:FF Y:00 P:36 SP:FF 13 - $C009 LDA $2002 ; A:00 X:FF Y:00 P:36 SP:FF
`
rom运行的起来为什么没有填充vram. 我的指令log是不是不对。可以发一下step4的log吗?感谢
@slmeng2008 时间久远, 你现在让我发step4的log, 那我也是把工程下载下来再编译运行. 你手上没有VS2017或者CodeLite吗, 都是免费的?
STEP4: 背景渲染
那么进入第四步, 背景的渲染. 终于进入关于图像渲染的步骤了, 这开始后就加快节奏了, 图形API的部分会略过, 因为可能读者拥有自己习惯的图形API, 这里采用的是D2D 1.1, 至少需要Win7的平台更新或者Win8.
黑匣子
如果没有自己熟悉的图形API的话, 这里介绍一下"黑匣子"函数, 就像
printf
那样, 不需要了解实现细节, 只需要知道它是干什么的就行. 这里, 自己将这些称为黑匣子函数:void main_cpp()
入口函数, 这个函数内部会一直循环直到窗口被关闭void main_render(void* rgba)
以RGBA字节序填充一个256x240像素的缓存, 实际上为了避免越界, 是256x256+256的缓冲空间. 由于RGBA序列刚好和uint32_t
一样大, 所以这个缓存的类型是uint32_t
. 结果会显示在窗口的左边int sub_render(void* rgba)
同上, 不过在返回非零值才会显示. 会显示在稍微右边点SFC_NO_SUBRENDER
void user_input(int index, unsigned char data)
用户输入, 很简单的接口SFC_NO_INPUT
void qsave()
void qload()
为了更为方便地调试, 即时存档/读档的接口SFC_NO_SL
PPU 地址空间
进入正题, 关于PPU的地址空间. PPU拥有16kb的地址空间, 完全独立于CPU. 再高的地址会被镜像.
调色板
FC理论能显示64种颜色(使用某种技巧的话):
自己使用的RGBA序调色板:
调色板索引是32字节的一段数据
16个也就是需要4位, 换句话说一个像素仅仅需要4位来描述, 就目前而言:
4位数据 -> 调色板索引 -> 输出颜色
但是, 实际上当最低俩位是0的时候:
也就是说虽然有16个, 但是有1/4的用不到:
镜像
名称表
名称表就是用来排列显示背景的. 背景每个图块是8x8像素. 而FC总共是256x240即有32x30个背景图块.每个图块用1字节表示所以一个背景差不多就需要1kb(32x30=960b). 为了实现1像素滚动, 两个背景连在一起然后用一个偏移量表示就行了:
就像马里奥这样, 红框就是当前屏幕显示(不是中央, 而是两侧两部分合起来).
虽然PPU支持4个名称表,不过FC只支持2个(FC自带的2kb显存), 另外两个被做了镜像. 当然, 如果卡带自带了显存那么就可以支持4个名称表了, 也就是ROM中提到的4屏模式.
.....
好像缺了点什么.每个表还有64字节没用? 那个时候的汇编程序猿怎么可能浪费RAM的字节, 被用在了属性表上.
属性表
64字节被瓜分成8x8, 也就是把背景分成8x8的区域:
属性表自然是描述熟悉属性的, 描述该区域(32x32像素, 也就是4x4个图块)所使用的调色板.
其中又被瓜分成'田'字, 即16x16像素(2x2图块), 每部分分得2位(太抠门了)
换句话说, 16x16像素区域公用了一个2位的属性, 导致这16x16中, 只能显示最多4种颜色.
于是调色板中4位其中2位就在这里了.剩下的两位自然就是在图样(非彼图样)表里面了:
图样表
图样表一般来映射自ROM中的CHR-ROM或者卡带上的CHR-RAM.
每个'图样'使用16字节, 描述了一个8x8的图块.
前面8字节(平面0)表示2位中的低位, 后面8字节(平面1)表示两位中的高位. 这两位合在一起是索引需要的4位中的低两位.
图样分为两个平面(plane), 多个平面组合起来才是最终的数据, 这个概念在多媒体中很常见.
名称表是一个字节, 也就是16*256 = 4kb, 刚好填充一个图样表. 那么如何确定是使用两个中的哪个? 答案是利用PPU寄存器状态位控制. 当然, 渲染精灵的话, 如果是8x16的精灵, 两个图样表都要用上了.
PPU寄存器
还记得大明湖畔, 呃不, 8字节步进镜像的PPU寄存器吗?
目前需要的:
以及VRAM读写相关的寄存器
BANK
PPU地址空间也同CPU地址空间使用BANK处理, 之前在Mapper000中应该提到了. CPU的BANK目前是8KB, PPU则更为细腻一点: 1KB. 这1KB导致了一些BUG到最后才发现.
PPU缺陷
个人认为是因为PPU来不及在一个CPU周期内, 返回数据, 才不得已这么实现. PPU地址空间读取, 实际上返回的是内部的一个缓冲值, 也就是说第一次读取的[\$0000, \$3F00]显存值是无效:
不过对于后面的调色板, 这个缺陷不存在(但是会更新缓冲值).
这里需要注意的是, 这里调色板更新缓冲值的实现是错误的, 但是没有关系, 除了测试ROM没人会利用调色板修改的缓冲值. 这个实现会在后面修改.
写入就很简单了, 注意一下镜像数据:
PPU地址空间读写
有了读写函数, 实现起来就很简单了:
读取也是类似的, 其他寄存器按照表格, 有些不着急实现.
绘制背景
现在我们就可以绘制背景了.
从图样表中了解到, 一个字节对应8个像素, 所以最好的实现方式是一次性渲染8个或者8个整数倍像素. 逻辑和效率兼备.
这里就不走寻常路, 是以像素为单位渲染, 目的是为了方便以后像素着色器的编写:
C没有类型安全一说, 有时很方便, 有时很X疼. 直接
extern
就能重解释, 连警告都没有 —— 爽.同步
由于现在没有任何同步手段, 但是一般来说游戏会等待VBlank, 所以我们目前渲染逻辑如下:
窒息的操作
实际编码中, 不小心让NMI跳向RESET向量(Ctrl+C, V大法好), 图像老是有问题...一步一步反汇编发现是NMI实现错了, 差点弃坑.
编程中小问题总是会引发大麻烦.
输出测试ROM左上背景屏幕
这就是这ROM左上角屏幕的显示了, 正好是显示内容.
项目地址Github-StepFC-Step4
作业
REF