4 ms·
I've been working on and off on a Nintendo DS emulator 100% written in Go. You can have a look here: https://github.com/rasky/ndsemu/ https://github.com/rasky/n
by giovannibajo1 10y ago
I've been working on and off on a Nintendo DS emulator 100% written in Go. You can have a look here: https://github.com/rasky/ndsemu/ https://github.com/rasky/ndsemu/
It features both the 2D and the 3D engine (with a full software rasterizer with poly-fillers generated through go generate). It's highly concurrent (each graphic layer is drawn in a different goroutine and then mixed together). The ARM interpreter is also generated via go generate.
I'll add some screenshots at some point.
- mappu 10y agoI think Go has great potential in this area. Some emulators (? PCSX2/Dolphin/...) are famously stuck with very few threads owing to the design of the underlying hardware, so anything that encourages extracting parallelism from within each subsystem could potentially be a major performance win.
- flohofwoe 10y agoCycle-perfect emulators at least for typical 8- and 16-bit systems need to run each emulator subsystem (CPU, video, audio, ...) in lock-step for each clock cycle. Spreading those emulator systems over multiple threads would mean that the threads need to synchronize with each other after each step (which is just a few host CPU instructions long). It might be easier for more modern hardware where subsystems are much more decoupled from each other (and I guess with Moore's Law dead this is our only hope to emulate more modern hardware in the future).
- giovannibajo1 10y agoThat's true. In the case of Nintendo DS, I'm aligning doing line-based emulation; so I first serially emulate the two CPUs (I could in theory parallelize then, but I haven't properly design the emulator around this, so all memory-mapped callbacks are not concurrent safe), and then emulate the graphic subsystem for a single line, spreading the different layers across different goroutines. This means that goroutines synchronize 263*60 times per second, which is something. I measure a high contention, so I'm not even sure it was worth in the end, but I thought it was a good experiment to attempt :) Another Go-related thing I did was using struct tags to configure memory-mapped registers in a declarative way. See for instance: https://github.com/rasky/ndsemu/blob/master/irq.go#L13 https://github.com/rasky/ndsemu/blob/master/irq.go#L13 where I configure the memory-mapped registers of the interrupt controller; "wcb" / "rcb" tags stand for write/read callbacks, which means that specific callbacks (with names matching the register names) will be called for each read/write operation; this is all achieved through reflection. The same applies to mapping the whole register bank, which is done here: https://github.com/rasky/ndsemu/blob/master/nds9.go#L69 https://github.com/rasky/ndsemu/blob/master/nds9.go#L69 The memory subsystem handling these and more features is in emu/hwio, and I'm actually pretty proud of it. For the memory mapping itself, a radix tree is built which is quite quick to lookup and doesn't require much barcoding. In fact, the whole "emu" package is a generic framework for emulators in Go, and I plan to reuse it in the future :)
- empressplay 10y agoThe Octalyzer has separate threads for cpu, sound, memory, networking, graphics and the main opengl loop. We use vertical blanking and sound timing to keep it all in sync.
- bussiere 10y agonice what do you use for displaying graphics ?
- giovannibajo1 10y agoFor interfacing with OS (window, sound, input), I use SDL bindings for Go (go-sdl2). Graphics is 100% being made in RAM pixel by pixel, so SDL is blitting the final image (see the emu/hw package).
- bussiere 10y agothanks