开发者用 Odin 语言编写 NES 模拟器,历时七个月后暂停
来源标题:A NES emulator written in Odin
一名开发者用 Odin 语言编写 NES 模拟器,从 2024 年 6 月底利用业余时间断断续续开发,2025 年 1 月暂停以专注其他事情。他表示让模拟器跑通 80% 到 90% 很快,但最后 10% 的精确模拟耗时漫长,难点在于 CPU 与 PPU 并行运行的时序处理。
以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。
这个项目始于2024年6月底。我利用业余时间断断续续地开发。2025年1月,我决定暂停这个项目,把精力放到其他事情上。
这是一段狂野的旅程。我从中获得了很多乐趣,但在后期试图让模拟器做到“完全”精确时,也吃了不少苦头。
关于项目的更多信息,可以看Github上的Readme。
我见过有人说做个NES模拟器就是个周末小项目,老实说我觉得这挺离谱的。因为很明显,事实并非如此。我花了7个月才把模拟器做到现在的程度。
我觉得他们真正想说的是,让一个NES模拟器“跑起来”确实花不了多少时间。比如让《大金刚》跑起来。这倒是没错。你可以很快达到80%甚至90%的完成度。但最后那10%要花很长很长的时间。
NES硬件有很多怪癖,要让某些游戏运行起来就必须把这些怪癖模拟到位。很多游戏依赖于NES非常特定的行为。如果你没实现对,它们就跑不起来。很大一部分挑战在于时序。CPU和PPU(NES中负责图形渲染的芯片)是并行运行的,而且它们之间会互相通信。这个时序必须非常精确,否则会出现很多错误。大多数游戏在不太精确的模拟器上也能正常运行,但很多游戏不行,它们需要你考虑更多细节。
另一个问题是mapper。Mapper是我们对某种卡带实现方式的称呼。你看,NES游戏装在卡带里,而卡带不只是数据。它们是带有自身行为逻辑和额外资源(比如只读存储器和可写存储器)的电路。有些卡带还带有额外的音频能力,不过我没有实现那部分。
总之,如果你想让某款卡带里的游戏在你的模拟器里运行,你就必须实现对应的mapper。它们补全了NES的电路。某种意义上,它们是NES硬件的一部分。Mapper有很多种,但只要实现少数几种就能支持大量游戏。我在实现某些mapper时遇到了困难,有些至今实现得不太好,导致一些游戏无法运行。我觉得有些mapper的文档很难理解。
我认为做模拟器最大的挑战在于知道该实现什么。关于硬件的信息,以及为了让游戏在你的模拟器里运行需要模拟硬件的哪些部分。幸运的是,NES在网上有非常完善的文档。我用了很多资源,比如:
没有这些资源以及nesdev Discord上人们的帮助,我不可能完成这些工作。
在这个项目中,我对Odin有了很多了解。我觉得它和这个项目简直是天作之合。它是一门非常简单、强类型的类C语言,给了我想要的全部自由,我觉得用做模拟器比其他语言容易得多。
有一件事让我不太适应,那就是Odin没有自动的整数提升。在对数字做运算时,Odin永远不会把类型提升到更大的类型。这导致了一些bug。但我把这全归咎于我自己习惯了C的整数提升。
我学到的一件有趣的事是内存分配策略。Odin没有自动内存管理,所以你必须想出聪明的方式来处理内存。思考如何最好地管理内存让我觉得很有趣。我不需要做任何复杂的事情。我主要用一个每帧清空的临时分配器、一个用于需要永久存活对象的全局堆分配器,以及一个用于NES会话期间存活对象的NES Arena分配器。我觉得内存分配挺有意思的。
我还需要学习的另一件事是如何序列化Odin对象。我用了CBOR库,遇到了一些问题。我以为是库的bug导致我的代码出问题,但后来搞清楚了。它出问题只是因为我定义了一些它不知道怎么序列化的奇怪类型。一旦我学会了如何正确地为类型编写自定义处理,一切就完美运行了。下面这个就是cbor不知道怎么序列化的棘手类型之一:
PpuCtrl :: struct #raw_union { // VPHB SINN using flags: bit_field u8 { n: u8 | 2, i: u8 | 1, s: u8 | 1, // sprite pattern table address for 8x8 sprites (0: $0000, 1: $1000) b: u8 | 1, // bg pattern table address (0: $0000, 1: $1000) h: u8 | 1, // sprite size (0: 8x8, 1: 8x16) p: u8 | 1, v: u8 | 1, // vblank NMI enable reg: u8,
我很喜欢Odin允许这样定义raw union,还能配合位域使用。这非常适合模拟器开发。
我不认为这个项目“完成了”。当然,这样的项目永远不会真正完成,但它有很多方面确实还需要大量工作:
这些是目前欠缺的主要部分。也许将来某天我会回来继续开发,让这个项目感觉上“完整”一些。
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。