Hey everyone!
It has been a long time since I posted any updates about my development journey with the Nintendo 64. I started out with the very naive idea of wanting to build some sort of very primitive graphics engine for the console but I quickly found out how difficult that idea would be to actually carry out. Since I failed to achieve my original goals, I thought that I should write about my process behind my learning about the console and the simple development that I managed to pull off. I hope that maybe this review can help others who are more skilled and are more persistent than I to create new content for the cherished and first truly 64 bit system. Sorry Jaguar (
http://www.giantbomb.com/jaguar/3045-28/), I’m not so sure that two 32 bit processors working in tandem counts as 64 bit computations (I could be wrong though!). Also note, I’m not a hardware or emulation expert! What I say is based off of my limited knowledge and I may not end up explaining something correctly. If you spot anything that is wrong please let me know! I want this write up to be as accurate as possible.
When I started my development path for the N64 I first concluded that I needed some sort of device that would allow me to play new programs developed for the console. After some searching I quickly found the 64drive created by Marshall (
http://64drive.retroactive.be/index.php). The dev cart works great and is the only true way to know if your new ROM actually works. Don’t just trust emulation. I learned the hard way...
While I was waiting for dev cart to arrive in the mail I thought it would be a good idea to also have a good emulator to test on. I figured I’d do most development on an emulator for speed and then occasionally upload to the actual system. I used the MESS emulator (
http://mamedev.org/release.html) and compiled it from source since I desired to do development on a Mac. I wanted to work in a more Linux based environment in case I needed to keep compiling tools found online. I seem to always have difficulty compiling for Windows but I find Linux based OS’s to be much, much easier to build on.
After I got the emulator built, I then started searching for some sort of SDK someone may have created. I knew there was no way I could begin from COMPLETE scratch due to my reverse engineering skills being nil. I came across libdragon (
https://github.com/DragonMinded/libdragon) and it was exactly what I was looking for at the time. It can render sprites, play audio (with some patching), handle input but it couldn’t yet draw hardware based triangles. That bummed me a little since my intention from the beginning was to make some sort of 3D application. I could have easily just drawn triangles using only the CPU (Central Processing Unit) and VI (Video Interface) but I wanted to utilize the hardware accelerated capabilities of the N64. So due to this predicament I decided “Hey, why not try and implement hardware rendered triangle support in libdragon!” With that idea I began to seek out knowledge of the internals of the N64 and learn some of its mysteries.
Scouring the web for many, many hours I found patents which contained some very interesting information on how triangle rendering is carried out. One of the patents that was very helpful was patent US 6239810 B1 titled “High performance low cost video game system with coprocessor providing high speed efficient 3D graphics and digital audio signal processing” (
https://www.google.com/patents/US6239810?dq=US+6239810+B1&hl=en&sa=X&ved=0CB0Q6AEwAGoVChMI3K_JjPn1yAIVCD6ICh2wRATM)
After reading, well more like skimming, I came across opcodes that are used in the rendering process. There are many ones that the N64 supports but the one that was of most importance to me was the triangle drawing opcodes. Upon discovering those, they didn’t make any sense at all. Everything was very cryptic to me but I thought that things may become more clear if I was to study the libdragon source code and figure out how to translate all of this alien information into something meaningful. To my relief, the author of libdragon had already implemented some functionality that used some of the opcodes I read about. So using the code in libdragon that already implemented some of the opcodes, I used that as a reference for my triangle rendering implementation. Before I go into how data should be prepared and transferred to the hardware renderer I should discuss a little about the how the N64 actually draws things.
The console consist of a VI (Video Interface) and most importantly the RCP or the Reality Co-Processor. The RCP is a very special piece of hardware that consist of two microprocessors: the RDP and the RSP. The RSP (Reality Signal Processor) is responsible for doing graphics and sound processing. It does things like lighting, matrix and perspective calculations among other operations. Once it completes a particular calculation say for example getting ready to draw a rectangle, that information is then transferred to the RDP or the Reality Display Processor. Commands have to be sent in precisely the right way or you'll get garbage on the screen or nothing. Also worth noting, things are transferred between the two microprocessors via DMA or Direct Memory Access. So the RSP once done computing something to draw, transfers that data into the RDRAM (Rambus DRAM) and then tells the RDP to read certain registers with special data ready just for the RDP to read. The memory system also is accessible by all components on the system. Nintendo designed it this way in order to have developers have the most flexibility on how they wanted to utilize the whopping 4k of available memory! If you want to learn further about this, there are some confidential Nintendo documents floating around online that can explain MUCH more in depth. I'll leave that up to you to find it though since it's not entirely legal for me to share.
Back to the RDP. Once the RDP has read in the data prepared by the RSP it then is responsible for preparing the video signal information to be sent to the VI. The RDPs only responsibility is rasterization. The output from the RDP will eventually be converted to analog signals for the TV via the digital to analog converter (DAC) onboard the system. Now since I didn't want to mess with doing things “The Right Way”, like having the RSP prepare the Graphics Binary Interface (GBI) commands for the RDP, I decided to bypass the RSP and just write directly to the RDP. This is what libdragon was already doing. That made things much easier in some way but proved a little difficult when wanting to render a triangle because I had to do calculations normally reserved for the RSP.
Moving forward I studied the patent and began piecing together how to prepare the data that needed to be sent to render a triangle. I eventually after many days of tracing through emulator code and fumbling around with bit packing, wrote the “correct” code in order to achieve this and you can see the finished code here:
https://github.com/DragonMinded/libdragon/commit/d9fd4f88cfeba137f981c070148dac71a09707b0.
To my surprise the author of libdragon accepted my new triangle rendering code into the library! So you can begin drawing triangles now and not have to worry about all that RSP/RDP stuff but note however that my code only supports flat colored rendered triangles. I wasn't brave enough to tackle textured triangles just yet.
Once getting some beautiful triangles bouncing around on my little LCD TV (you can see the code I used here
https://github.com/calebhc/nEgg/blob/59dd9548c1d9527d1d547c53d6537238bf5cdaf0/nEgg.c) I started to feel a bit guilty of my implementation. Nintendo had reasons, or more namely Silicon Graphics Inc, for designing the RSP and not using it felt bothersome. I could have easily implemented all of the 3D graphics mathematics using only the CPU, which if you're curious it's a MIPS based NEC VR4300, but that would end to sluggish performance. The CPU was designed to just pass the ROM data around and handle other things such as player input. So from there I set out to do things the correct way and utilize the RSP.
To my surprise, or lack thereof, this turned out to be much more difficult and eventually made me throw in the towel. I learned that the RSP runs little programs written in what they call microcode. Microcode is just fancy marketing speak for programs written in custom MIPS based assembly. The microcode programs sort of remind me of GPU shaders. Nintendo/SGI wrote a bunch of microcode programs that licensed developers could use to draw 3D scenes but figuring out how to construct brand new programs without any documentation was proving to be very difficult. I found the official, compiled Nintendo microcode programs but I wanted to understand and create new ones.
I managed to find an actual microcode program in its uncompiled state but it wasn’t very helpful in figuring out how to write a new one. I wanted to push the triangle preparation code I was doing on the CPU into the RSP where I could take advantage of vector arithmetic operation optimizations since the RSP was designed to handle 3D mathematics quickly.
I later learned about the #n64dev channel on IRC and jumped on to talk to any experienced N64 devs. I met Krom and discovered his wonderful test suite of programs for the N64 (
https://github.com/PeterLemon/N64). He even wrote new microcode to render some points in perspective! I enjoyed traversing through all of the assembly code but I really wanted to write new ROMs in C and keep the assembly to a minimum. This is where things got really weird. I knew that you can only write new RSP code in assembly and the opcodes it uses are not actually standard but custom for the microprocessor. So I couldn’t just study some MIPS datasheet available online. Krom got around this by emulating what the custom opcode commands should do via macros. That was great to have the macros available but how was I going to write microcode in assembly and then link it into my C code? It was just too beyond me so I decided to take a break.
Months later the author of the CEN64 emulator (
https://github.com/tj90241/cen64) started doing exactly what I sought out to do (
https://github.com/tj90241/n64chain)! He is using Krom’s test code (
https://github.com/PeterLemon/N64) to actually start writing new RSP code in a C based project. Seeing how he figured out what I was trying to achieve was great! Now only if I had the motivation to build off of his work but alas that’s where my problem lies. No motivation right now to continue further but perhaps someday.
This feels a bit abrupt but that pretty much sums up my journey with the N64. It was a very interesting ride and the most work I’ve ever done with an old system. Getting to spend hours digging around the MESS source code was very humbling. The amount of complexity in emulating integrated circuits is incredible. I’m glad other people can actually figure that stuff out! I’ve attached additional links at the bottom that you can check out if you want to know more about all things N64. Again I’m not an expert on hardware and emulation so sorry if I wrote about something incorrectly.
Thanks for reading and all of the support! Let me know if you have any questions or things I should correct. :-)
Resources
http://n64.icequake.net/http://www.n64dev.net/https://github.com/ARM9/n64brewhttps://github.com/n64dev/docshttps://github.com/shlomnissan/ultra64demoshttps://github.com/mikeryan/n64devhttp://patater.com/gbaguy/n64asm.htmhttp://n64dev.org/Also here are some really interesting articles
http://www.gamasutra.com/view/feature/131449/bringing_dr_jones_to_the_infernal_.phphttp://www.gamasutra.com/view/feature/131556/postmortem_angel_studios_.phphttp://www.gamasutra.com/view/news/177106/Video_The_humble_beginnings_of_GoldenEye_007_for_the_Nintendo_64.phpEdit - 11/4/15 3:31 PMI mentioned that I did my development on a Mac but that's not completely true.
I actually started on a Mac just to emulate the the ROMs I was producing but later switched to Windows. I think I mainly switched because the 64drive software only came with Windows executables. I did however later switch to using Arch Linux for development. I found a Linux port (
https://github.com/jkbenaim/64drive_usb_2xx) of the 64drive transfer software so I could actually just use a complete Linux environment to do everything.