What's new

N64 tech documentation

Aphex123

New member
I thought I'd share the documentation I'd accumulated throughout my journey in low level N64 emulation. I haven't included the various source code I have (apart from mame/mess's very informative N64 emulator implementation), since most is easily available and takes up too much space to upload anyway. I originally posted an earlier version of the documents I have on a torrent site, but this is more up to date. To be honest, I haven't really made much of an effort to organise all the files in to directories, but they should be fairly self explanatory anyway.

As far as I'm aware, all of this documentation is public domain and legal. I've made an effort to not include any documents that may not be legal.

Dropbox link

If you've got any other useful info or N64 docs, please, post them. The N64 is notorious for being ill documented, but as far as I'm concerned, the truth is out there...
 
Last edited:

Azimer

Emulator Developer
Moderator
A nice document base is sorely needed as well as new research to fix inaccuracies and fill holes.
 
OP
A

Aphex123

New member
A nice document base is sorely needed as well as new research to fix inaccuracies and fill holes.

Could you name any inaccuracies that you know of? I know that parts of the RSP are badly documented, but other than that, I'd say it's not too bad. Besides, MESS provides some great information detailing the inner workings of the RSP.

Perhaps we could get a list of unknown or questionable information regarding the N64 (lacking official documentation). Some things that I can think of include...

RSP
- Vector instructions
- Flag registers

N64DD?

Pretty short list, but I can't think of much else.
 
Last edited:

smcd

Active member
Lol I just googled up some stuff trying to find about n64 microcode, and stumbled across an apparent "official" documentation for some n64 stuff, though it says it is supposed to be under a non disclosure agreement. bleh

EDIT: actually it appears it may be the entire n64 sdk. doh. Doesn't seem like it'd be too helpful for any type of hardware-in-software implementation anyhow really, at least not at a low level
 
Last edited:
OP
A

Aphex123

New member
In all my efforts to find low level documentation for the RSP's microcode (vector instruction set), I haven't come up with much (apart from the abstract descriptions of the instructions). No matter though, the source code of MESS's RSP emulation provides more than enough information. The N64 SDK's, official and non official, do provide some information but it's all high level really (well, the RSP info is atleast). Even the patent on the N64 only details the high level microcode tasks which the N64 SDK implemented, but mention nothing about the actual vector instructions themselves.

I don't know why Nintendo chose to keep the RSP so closely under wraps to developers. I even spoke the RCP's engineer, Tim Van Hook (via email, go to undrian.com), and asked him what he thought about disclosure to developers. He said that he would want the authors of the hardware/software to choose whether or not they wish to release information, but ultimately, it's up to the corporations like Nintendo to decide. Obviously, they're under strict confidentiality agreements.
 
Last edited:

zoinkity

New member
I was under the assumption that it was a variation of MIPS-3D, but then again it really isn't possible to know for certain without actual documentation. If it was though Nintendo could have been bound by agreement to withhold specific information concerning an emerging (and developing) technology.

Oddly, after talking to former employees at Factor 5, Rare, and a couple smaller studios that did microcodes, you hear entirely conflicting information about how much info was available, what tools were provided, and even the degree of support from big-N.

Incidenatally, obviously MESS does LLE for the RSP, but exactly how much of the RDP itself is handled?
 
OP
A

Aphex123

New member
I was under the assumption that it was a variation of MIPS-3D, but then again it really isn't possible to know for certain without actual documentation. If it was though Nintendo could have been bound by agreement to withhold specific information concerning an emerging (and developing) technology.

Oddly, after talking to former employees at Factor 5, Rare, and a couple smaller studios that did microcodes, you hear entirely conflicting information about how much info was available, what tools were provided, and even the degree of support from big-N.

Incidenatally, obviously MESS does LLE for the RSP, but exactly how much of the RDP itself is handled?

I can't say I've heard of the MIPS-3D ISA before now, but I'm sure the RSP's microcode is based around SGI's earlier work. I asked Tim Van Hook in an email but he couldn't remember what it was based on, however, he did mention the instruction sets he had personal developed and worked on. If anyone wants to know, I can always look through the emails.

Just for interest, and since this is an info thread, I'll post some general info that I (think I) know about the RSP that isn't necessarily common knowledge, although I can't guarantee complete accuracy.

General
- Scalar unit which implements most, but not all, of the r4300i's instruction set.
- Vector unit which implements the vector instruction set and load/store instructions.

Registers
- Thirty-two 32-bit general purpose registers.
- Thirty-two 128-bit vector registers which are operated on as eight 16-bit elements.
- Eight 48-bit accumulator registers, each consisting of 16-bit low, middle and high "slice".
- Four 16-bit flag registers for storing results of operations. From what I understand, the high 8 bits of flag 0 are used as zero flags, the low 8 bits are for carry flags. Flag 1 is for compare, flag 2 is "extension flags" ( I also heard the term "Anciliary clipping flags" being applied to flag 2), although I'm not sure what they mean exactly. The other flag I have no idea about. Does it even exist?

Some notes
The RSP can only directly access 4KB of DMEM, but it can also access memory mapped SP and DPC registers with the mfc0 and mtc0 instructions, which allows it to send commands directly to the RDP and to DMA back and forth between RDRAM and SP instruction and data memory.

Vector instructions use three vector operands, but one of the source vector registers can be operated on in different configurations. A field in the instruction ("el") specifies which element in the vector registers shall be used. For example, "vadd v1,v2,v3[11111111]" would add element 1 of v3 to every element of v2 and store the result in v1. There's different configurations of "el".

MESS emulates the RDP on the low level too and implements a software renderer. If you want info on the RDP, the N64's patent is a good source of information which lists all the RDP commands and their respective formatting and usage (note that both patent US6331856 and US6239810 are essentially the same, but the former gives more detailed RDP info). On another note, The RDP is quite interesting and unusual really. Unlike most graphics chips (even back then), it doesn't specify vertex data (e.g. coordinates, colour, texture coordinates etc) for rendering primitives. It instead uses edge and slope values to be used directly by the edge walking hardware and RGBAZ steppers. If anyone wants to know more, I'll be happy to explain as best I can. The RDP is a fascinating and quite unique piece of hardware I think.

edit: in fact, i'll post some info on the RDP too.

As I mentioned, RDP commands, more specifically the triangle commands, specify data as edge and (inverse) slope values. If you look at "Sheet 74 of 96" in the pdf file "n64_patent_6239810", you can see how the commands are formatted (as well as a helpful little diagram).

To render the triangles, the edge walking hardware has to walk down each edge. Here's the procedure which the edge walking hardware goes through to define the spans (edges) of the triangle (note that mooglyguy posted a similar list but in my opinion, it was slightly inaccurate).

IF is right major triangle (dir = 1)
1. load xright with xm
2. load xleft with xh
3. load xright_inc with dxmdy
4. load xleft_inc with dxhdy
5. load j with yh
6. if j is less than ym then, increment j by 1, increment xleft by xleft_inc, increment xright by xright_inc, render span (at vertical line j) from xleft to xright. repeat step 6 until j >= ym.
7. load xright with xl
8. load xright_inc with dxldy
9. load xleft_inc with dxhdy
10. load j with ym
11. if j is less than yl then, increment j by 1, increment xleft by xleft_inc, increment xright by xright_inc, render span (at vertical line j) from xleft to xright. repeat step 11 until j >= yl.
ELSE
1. load xright with xh
2. load xleft with xm
3. load xright_inc with dxhdy
4. load xleft_inc with dxmdy
5. load j with yh
6. if j is less than ym then, increment j by 1, increment xleft by xleft_inc, increment xright by xright_inc, render span (at vertical line j) from xleft to xright. repeat step 6 until j >= ym.
7. load xleft with xl
8. load xright_inc with dxhdy
9. load xleft_inc with dxldy
10. load j with ym
11. if j is less than yl then, increment j by 1, increment xleft by xleft_inc, increment xright by xright_inc, render span (at vertical line j) from xleft to xright. repeat step 11 until j >= yl.

That's pretty much how the edge walking hardware operates. During the rendering of the each line, the stepping hardware interpolates the shade values, texture coordinates and Z buffer values in a similar way.

Of course, this is all very low level information that most N64 programmers wouldn't need to know. As far as I'm aware, the libraries would convert vertex information (provided by the programmers) to edge/slope format for the RDP to process. The conversion between vertex values and edge coefficients is fairly straight forward, but if somebody wants to know, i'll be happy to detail what i know of it.
 
Last edited:
OP
A

Aphex123

New member
Here's a little bit of info about how the N64 boots up. It's quite widely known, but oh well, it can't hurt.

Upon power on/reset, the N64 begins executing the IPL (initial program load) boot ROM at physical address 0x1fc00000 (kseg 1 address 0xbfc00000). The boot code pretty much just initialises the hardware, does a few checks and copies the cartridge boot code (cart offset 0x40-0x1000) to 0xa4000040 (RSP DMEM) and jumps to it. (After this, the cartridge boot code is in control, so it can vary, but the following is typically what will occur). At this point, a portion of the main program code (starting cart offset 0x1000) is DMA'd to the 4 byte address specified at cart offset 0x8 (in the cart header). This address is usually referred to as the "Boot address offset" or "Program Counter". This address is where the actual program will begin. Think of it as the "main" function of the program. However, before this happens, a CRC check is done on the cartridge boot code. This is usually done somewhere around RDRAM address 0x80000100 - 0x80000200. The results are compared to the two CRC values in the cart ROM's header. If they don't match, the N64 will just hang in an infinite loop (using the BGEZAL instruction), otherwise, execution will continue.

Note that most emulators choose to not boot from the real PIF ROM and instead begin execution at 0xa4000040 (after copying the cart boot code to there). Of course, they set up all the registers to the values that the PIF ROM would put them in, first.

Also, another quick note. The cartridge ROM is mapped at physical address 0x10000000 (kseg 1 address 0xb0000000).
 
Last edited:

zoinkity

New member
Incidentally, there's six different CRC checks within the 64DD's IPL. That thing's a real pain.
ret = (A3 == 0xE1F9D977)
ret &=(T2 == 0xDA29C069)
ret &=(T3 == 0xBC587021)
ret &=(S0 == 0x33E17503)
ret &=(A2 == 0x96310A49)
ret &=(T4 == 0x08E5FB8C)

[edit]
Just noticed you pointed out that PIF ROM executes between that jump to rdram and sets some static values.

Incidentally, Nemu64's solution was to ignore all BGEZALs. Ran into a problem with custom code that used that particular op. Throw a breakpoint on it sometime after invalidating the CRC. It will walk right past it.

Incidentally, you wouldn't have a list of all the static values that are set by the hardware? Very few are set by hardware, but among them the video mode/region value.

For reference:
05000000 is 64DD device registers
06000000 is 64DD IPL
08000000 is the typical address for FLASHram/SRAM

Incidentally, GameShark/Pro Action Replays have a unique setup. It's apparently some 16bit memory system that uses an exploit for 32bit access. The upper halfwords are readable from 1EE00000 and the lower halfwords from 1EF00000. There are points where these are read and intermeshed via software.
 
Last edited:
OP
A

Aphex123

New member
Incidentally, there's six different CRC checks within the 64DD's IPL. That thing's a real pain.
ret = (A3 == 0xE1F9D977)
ret &=(T2 == 0xDA29C069)
ret &=(T3 == 0xBC587021)
ret &=(S0 == 0x33E17503)
ret &=(A2 == 0x96310A49)
ret &=(T4 == 0x08E5FB8C)

[edit]
Just noticed you pointed out that PIF ROM executes between that jump to rdram and sets some static values.

Incidentally, Nemu64's solution was to ignore all BGEZALs. Ran into a problem with custom code that used that particular op. Throw a breakpoint on it sometime after invalidating the CRC. It will walk right past it.

Incidentally, you wouldn't have a list of all the static values that are set by the hardware? Very few are set by hardware, but among them the video mode/region value.

For reference:
05000000 is 64DD device registers
06000000 is 64DD IPL
08000000 is the typical address for FLASHram/SRAM

Incidentally, GameShark/Pro Action Replays have a unique setup. It's apparently some 16bit memory system that uses an exploit for 32bit access. The upper halfwords are readable from 1EE00000 and the lower halfwords from 1EF00000. There are points where these are read and intermeshed via software.

With some basic emulation of the PIF, I have managed to get most games to pass all CRC checks with no hacks (such as ignoring instructions etc) in my emulator. There's one little hack which I copied from MESS. The PIF ROM fiddles with the CIC status byte in the PIF RAM and presumably expects a response from the CIC chip to be reflected in that byte, perhaps. All that needs to be done though, is set the CIC status byte to 0x80 upon reads to 0x1fC007e4. I have a feeling that the CIC status is some kind of bitfield, but even after reading the N64's security chip patent, I still don't know the details.

When referring to the static values set by the hardware, are you referring to the values that the PIF ROM writes to the hardware registers? Well, here's a dump from my emu of a portion of all the load/store word to hardware registers (up to the point where the execution is at the boot offset address specified in the cart header), as well as the DMA which are occurring. Just for your info, it's Zelda: Ocarina of Time. Bare in mind that not all of these are from the PIF ROM, the majority are from the cart ROM. Execution begins at the PIF ROM though.

SP_STATUS_REG (0x04040010) read word
SP_STATUS_REG (0x04040010) write word 0x0000000A
RSP halt set
SP_DMA_BUSY_REG (0x04040018) read word
PI_STATUS_REG (0x04600010) write word 0x00000003
VI_INTR_REG (0x0440000C) write word 0x000003FF
VI_H_START_REG (0x04400024) write word 0x00000000
VI_CURRENT_REG (0x04400010) write word 0x00000000
AI_DRAM_ADDR_REG (0x04500000) write word 0x00000000
AI_LEN_REG (0x04500004) write word 0x00000000
SP_STATUS_REG (0x04040010) read word
SI_STATUS_REG (0x04800018) read word
PI_BSD_DOM1_LAT_REG (0x04600014) write word 0x000000FF
PI_BSD_DOM1_PWD_REG (0x04600018) write word 0x000000FF
PI_BSD_DOM1_PGS_REG (0x0460001C) write word 0x0000000F
PI_BSD_DOM1_RLS_REG (0x04600020) write word 0x00000003
PI_BSD_DOM1_LAT_REG (0x04600014) write word 0x00000040
PI_BSD_DOM1_PWD_REG (0x04600018) write word 0x00803712
PI_BSD_DOM1_PGS_REG (0x0460001C) write word 0x00008037
PI_BSD_DOM1_RLS_REG (0x04600020) write word 0x00000803
DPC_STATUS_REG (0x0410000C) read word
SI_STATUS_REG (0x04800018) read word
SI_STATUS_REG (0x04800018) read word
SI_STATUS_REG (0x04800018) read word
SI_STATUS_REG (0x04800018) read word
RI_SELECT_REG (0x0470000C) read word
RI_CONFIG_REG (0x04700004) write word 0x00000040
RI_CURRENT_LOAD_REG (0x04700008) write word 0x00000000
RI_SELECT_REG (0x0470000C) write word 0x00000014
RI_MODE_REG (0x04700000) write word 0x00000000
RI_MODE_REG (0x04700000) write word 0x0000000E
MI_INIT_MODE_REG (0x04300000) write word 0x0000010F
??? (0x03F80008) write word 0x18082838
??? (0x03F80014) write word 0x00000000
??? (0x03F80004) write word 0x80000000
MI_VERSION_REG (0x04300004) read word
??? (0x03F08004) write word 0x00000000
RDRAM_MODE_REG (0x03F0000C) write word 0x46C0C0C0
RDRAM_MODE_REG (0x03F0000C) write word 0xC6C0C0C0
MI_INIT_MODE_REG (0x04300000) write word 0x00000000
MI_INIT_MODE_REG (0x04300000) write word 0x00002000
RDRAM_MODE_REG (0x03F0000C) read word
MI_INIT_MODE_REG (0x04300000) write word 0x00001000
MI_INIT_MODE_REG (0x04300000) write word 0x00002000
RDRAM_MODE_REG (0x03F0000C) read word
MI_INIT_MODE_REG (0x04300000) write word 0x00001000
RDRAM_MODE_REG (0x03F0000C) write word 0x46C0C0C0
RDRAM_MODE_REG (0x03F0000C) write word 0xC6C0C0C0
MI_INIT_MODE_REG (0x04300000) write word 0x00000000
MI_INIT_MODE_REG (0x04300000) write word 0x00002000
RDRAM_MODE_REG (0x03F0000C) read word
MI_INIT_MODE_REG (0x04300000) write word 0x00001000
MI_INIT_MODE_REG (0x04300000) write word 0x00002000
RDRAM_MODE_REG (0x03F0000C) read word
MI_INIT_MODE_REG (0x04300000) write word 0x00001000
RDRAM_MODE_REG (0x03F0000C) write word 0x46C0C0C0
RDRAM_MODE_REG (0x03F0000C) write word 0xC6C0C0C0
MI_INIT_MODE_REG (0x04300000) write word 0x00000000
MI_INIT_MODE_REG (0x04300000) write word 0x00002000
RDRAM_MODE_REG (0x03F0000C) read word
MI_INIT_MODE_REG (0x04300000) write word 0x00001000
MI_INIT_MODE_REG (0x04300000) write word 0x00002000
RDRAM_MODE_REG (0x03F0000C) read word
MI_INIT_MODE_REG (0x04300000) write word 0x00001000
RDRAM_MODE_REG (0x03F0000C) write word 0x46C0C0C0
RDRAM_MODE_REG (0x03F0000C) write word 0xC6C0C0C0
MI_INIT_MODE_REG (0x04300000) write word 0x00000000
MI_INIT_MODE_REG (0x04300000) write word 0x00002000
RDRAM_MODE_REG (0x03F0000C) read word
MI_INIT_MODE_REG (0x04300000) write word 0x00001000
MI_INIT_MODE_REG (0x04300000) write word 0x00002000
RDRAM_MODE_REG (0x03F0000C) read word
MI_INIT_MODE_REG (0x04300000) write word 0x00001000
RDRAM_MODE_REG (0x03F0000C) write word 0xC6C0C0C0
MI_INIT_MODE_REG (0x04300000) write word 0x00000000
??? (0x03F8000C) write word 0xC4000000
??? (0x03F80004) write word 0x80000000
??? (0x03F08004) write word 0x00000000
RDRAM_MODE_REG (0x03F0000C) write word 0xC6804080
MI_INIT_MODE_REG (0x04300000) write word 0x00000000
RI_REFRESH_REG (0x04700010) write word 0x00063634
RI_REFRESH_REG (0x04700010) read word
SP_STATUS_REG (0x04040010) write word 0x000000CE
RSP halt set
SP_PC_REG (0x04080000) write word 0x00000000
SP_STATUS_REG (0x04040010) write word 0x000000AD
RSP halt cleared
SP_DMA_FULL_REG (0x04040014) read word
SP_MEM_ADDR_REG (0x04040000) write word 0x00001120
SP_DRAM_ADDR_REG (0x04040004) write word 0x000001E8
SP_RD_LEN_REG (0x04040008) write word 0x000001E8
sp dma read: sp_mem_addr=0x1120, rdram_addr=0x1e8, len=0x1f0, count=0x0, skip=0x0
PI_DRAM_ADDR_REG (0x04600000) write word 0x00000400
SP_DMA_BUSY_REG (0x04040018) read word
PI_STATUS_REG (0x04600010) read word
SP_DMA_BUSY_REG (0x04040018) read word
SP_SEMAPHORE_REG (0x0404001C) read word
SP_DRAM_ADDR_REG (0x04040004) read word
PI_CART_ADDR_REG (0x04600004) write word 0x10001000
PI_WR_LEN_REG (0x0460000C) write word 0x000FFFFF
pi dma write: cart_addr=0x10001000, rdram_addr=0x400, len=0x100000
SP_MEM_ADDR_REG (0x04040000) write word 0x00000000
SP_DRAM_ADDR_REG (0x04040004) write word 0x00000180
SP_RD_LEN_REG (0x04040008) write word 0x00000000
SP_SEMAPHORE_REG (0x0404001C) read word
SP_SEMAPHORE_REG (0x0404001C) read word
SP_SEMAPHORE_REG (0x0404001C) read word
PI_STATUS_REG (0x04600010) read word
SP_SEMAPHORE_REG (0x0404001C) read word
SP_SEMAPHORE_REG (0x0404001C) read word
SP_SEMAPHORE_REG (0x0404001C) write word 0x00000000
SP_SEMAPHORE_REG (0x0404001C) read word
SP_MEM_ADDR_REG (0x04040000) write word 0x00000000
SP_DRAM_ADDR_REG (0x04040004) write word 0x00000400
SP_RD_LEN_REG (0x04040008) write word 0x00000FFF
sp dma read: sp_mem_addr=0x0, rdram_addr=0x400, len=0x1000, count=0x0, skip=0x0
SP_DMA_BUSY_REG (0x04040018) read word
SP_MEM_ADDR_REG (0x04040000) write word 0x0000B120
SP_DRAM_ADDR_REG (0x04040004) write word 0xB12FB1F0
SP_WR_LEN_REG (0x0404000C) write word 0xFE817000
DPC_STATUS_REG (0x0410000C) write word 0x00000240
SP_STATUS_REG (0x04040010) write word 0x00AAAAAE
RSP halt set
MI_INTR_MASK_REG (0x0430000C) write word 0x00000555
SI_STATUS_REG (0x04800018) write word 0x00000000
Unhandled write to SI mem address
AI_STATUS_REG (0x0450000C) write word 0x00000000
MI_INIT_MODE_REG (0x04300000) write word 0x00000800
PI_STATUS_REG (0x04600010) write word 0x00000002
Breakpoint detected @ 0x80000400
 
Last edited:

zoinkity

New member
To be more specific, at what point are the values between 80000300 and 80000318 written? They seem written by hardware after jumping from PIF to the bootstrap. The NMI buffer is software-managed though (cleared and filled after testing the flag by software).

It should be some kind of bitfield, concidering that's the same register used to trigger PIF command reads. I had always assumed that final byte in PIFram was just like the other register fields, where setting status immediately sets the interrupt, akin to setting a read or write length for DMA/PI/VI/etc. or setting status in virtually any of them. It is particularly obscure. We probably have a better reference for the RDP than the PIF.
 

bobby.smiles32

New member
To be more specific, at what point are the values between 80000300 and 80000318 written? They seem written by hardware after jumping from PIF to the bootstrap. The NMI buffer is software-managed though (cleared and filled after testing the flag by software).

Values at 0x80000300 are layed out this way :
typedef struct {
u32_t tv_type; (1=NTSC, 0=PAL,2=MPAL)
u32_t rom_type; (0=GamePack,1=DD)
u32_t rom_base; (0xb0000000 for GamePack, 0xa6000000 for DD)
u32_t reset_type; (0=ColdReset, 1=NMI)
u32_t cic_id; (CIC6103 writes 6103, CIC6106 writes 6104, CIC6105 writes 6105, CIC6102 & CIC6101 do not write anything)
u32_t version; (???)
u32_t mem_size; (specify the amount of detected rdram 4Mo or 8Mo usually)
u8_t app_nmi_buffer[64];
} os_boot_config_t;

They are written at the end of IPL3 just before clearing IMEM & DMEM and jumping to game entrypoint, except for 0x80000318 (mem_size)
which is written earlier at the end of rdram initialization (but still in IPL3).

However most of what is written directly comes from registers (s3..s7) initialized during IPL2 (part of PIF BootROM executed in RSP IMEM) :
s3 : rom_type
s4 : tv_type
s5 : reset_type
s6 : SEED (not related to os_boot_config, but still a very interesting value...)
s7 : version

IPL2 determines these values by reading at 0x1fc007e4 (PIF+0x24) and parse the value returned.
tv_type is an exception, because it is hardcoded in PIF BootROM.

For cic_id, different CIC types write different values :
CIC6101 & CIC6102 do not write anything
CIC6103 writes 6103
CIC6105 writes 6105
CIC6106 writes 6104 (yes it is 6104 not 6106)
 

Chilly Willy

New member
Sorry to bump the thread, but I did have a question related to an earlier post.

Of course, this is all very low level information that most N64 programmers wouldn't need to know. As far as I'm aware, the libraries would convert vertex information (provided by the programmers) to edge/slope format for the RDP to process. The conversion between vertex values and edge coefficients is fairly straight forward, but if somebody wants to know, i'll be happy to detail what i know of it.

Low-level info you don't need to know IF you're using the leaked Nintendo or PsyQ SDK to write your N64 programs. I use libdragon, a completely Nintendo-code free development kit for programming the N64. It has support for the Rectangle RDP commands, but not the Triangle RDP commands. I was working on my own "microcode" (silly name for a pcode interpreter if you ask me) and while a mixer is dead simple, I wanted to add in handling vertexes and the like, as well as adding support for triangle commands. While I could scrape enough brain cells together to figure out how to calculate the values the triangle command needs, I noticed your post here and decided to ask... yes, I would very much like to know about the conversion between vertex values and edge coefficients.

Thanks. :)
 
OP
A

Aphex123

New member
Sorry to bump the thread, but I did have a question related to an earlier post.



Low-level info you don't need to know IF you're using the leaked Nintendo or PsyQ SDK to write your N64 programs. I use libdragon, a completely Nintendo-code free development kit for programming the N64. It has support for the Rectangle RDP commands, but not the Triangle RDP commands. I was working on my own "microcode" (silly name for a pcode interpreter if you ask me) and while a mixer is dead simple, I wanted to add in handling vertexes and the like, as well as adding support for triangle commands. While I could scrape enough brain cells together to figure out how to calculate the values the triangle command needs, I noticed your post here and decided to ask... yes, I would very much like to know about the conversion between vertex values and edge coefficients.

Thanks. :)

Okay, sure. Right now, I'll just talk about converting the x/y coordinates to edge coefficients. The texture, Z and shading values are a little complex and I have yet to fully figure out an algorithm for converting to the RDP coefficient format. However, once you get the basic idea, it shouldn't be too much of a stretch. See sheet 73,74 of patent 6,239,810 for details on the RDP edge coefficients. I assume that you understand how the edge coefficients work, none of this will really make sense unless you understand how the triangles are rendered from the edge coeficcients (see my earlier posts).

Let's say you have three typical vertices (i'll represent this in C), each with an X and Y coordinate:

Vertex2f v[3] = { {47, 126}, {200, 20}, {300, 260} };

The first thing we do is sort the vertices by their Y coordinate, low to high. Just doing this, we already have our "Y coordinate of high minor, middle minor and low major edge". The X major (XH) and X middle (XM) edge coefficients can either be the same or have one pixel difference (to make a convincing triangle). Either way, XH and XM will be derived from the X value of the vertex with the lowest Y value (v[0] in our case, since we sorted them). X low (XL) will be the X value of the vertex with the middle Y value (v[1] in our case). The RDP also needs to know which direction the triangle is facing (which side XL is on). The triangle is a left major triangle if (v[2].x < v[1].x), else it's a right major triangle. If you take a look at the diagram on sheet 74, you can see why this is true.

Now that we have all the edge values, we need to calculate the inverse slopes, DxHDy, DxMDy and DxLDy. The inverse slopes effectively tell the RDP how to interpolate the spans of the triangle which are being rendered. That is, for each scanline, the RDP will increase each edge value by it's corresponding inverse slope. To calculate an inverse slope of two XY points, you use the following formula (remembering to use signed arithmetic).

(x1 - x0) / (y1 - y0)

So, given that the vertices are all sorted in Y order, it's quite trivial to calculate each inverse slope.

e.g. DxHDy = (v[2].x - v[0]) / (v[2] - v[0])
etc...

Now, given all that, we have calculated the values for all needed edge coefficients. However, the RDP expects these values in a specific format. For example, all X edges and inverse slopes are expect to be in fixed 16.16 format (16 bits integer, 16 bits fractional). Instead of telling you how to convert these values, I will just post this snippet of code that I was in the process of writing. I (for whatever reason) never finished it, however, it does take 3 (C float) vertices and outputs a binary RDP triangle command (although, only flat shaded triangles). Feel free to use this meager code in anyway you want.

The next step I was trying to achieve was to calculate the coefficients for Goraud shaded, textured or Z buffered triangles. However, I never quite understood one of the RDP's coefficient values, the "change in <whatever value> along the edge". This is present in the shade, texture and Z coefficients. If anyone can shed a light on this, it would be helpful. I did at one point ask the original architect of the RDP (Tim Van Hook) what this value was, but I don't think he could really recall.
 
Last edited:

Chilly Willy

New member
Okay, sure. Right now, I'll just talk about converting the x/y coordinates to edge coefficients. The texture, Z and shading values are a little complex and I have yet to fully figure out an algorithm for converting to the RDP coefficient format. However, once you get the basic idea, it shouldn't be too much of a stretch. See sheet 73,74 of patent 6,239,810 for details on the RDP edge coefficients. I assume that you understand how the edge coefficients work, none of this will really make sense unless you understand how the triangles are rendered from the edge coeficcients (see my earlier posts).

Yes, your earlier posts were most enlightening. I hadn't had much exposure to edge-walkers before.


Let's say you have three typical vertices (i'll represent this in C), each with an X and Y coordinate:

Vertex2f v[3] = { {47, 126}, {200, 20}, {300, 260} };

The first thing we do is sort the vertices by their Y coordinate, low to high. Just doing this, we already have our "Y coordinate of high minor, middle minor and low major edge". The X major (XH) and X middle (XM) edge coefficients can either be the same or have one pixel difference (to make a convincing triangle). Either way, XH and XM will be derived from the X value of the vertex with the lowest Y value (v[0] in our case, since we sorted them). X low (XL) will be the X value of the vertex with the middle Y value (v[1] in our case). The RDP also needs to know which direction the triangle is facing (which side XL is on). The triangle is a left major triangle if (v[2].x < v[1].x), else it's a right major triangle. If you take a look at the diagram on sheet 74, you can see why this is true.

Now that we have all the edge values, we need to calculate the inverse slopes, DxHDy, DxMDy and DxLDy. The inverse slopes effectively tell the RDP how to interpolate the spans of the triangle which are being rendered. That is, for each scanline, the RDP will increase each edge value by it's corresponding inverse slope. To calculate an inverse slope of two XY points, you use the following formula (remembering to use signed arithmetic).

(x1 - x0) / (y1 - y0)

So, given that the vertices are all sorted in Y order, it's quite trivial to calculate each inverse slope.

e.g. DxHDy = (v[2].x - v[0]) / (v[2] - v[0])
etc...

Now, given all that, we have calculated the values for all needed edge coefficients. However, the RDP expects these values in a specific format. For example, all X edges and inverse slopes are expect to be in fixed 16.16 format (16 bits integer, 16 bits fractional). Instead of telling you how to convert these values, I will just post this snippet of code that I was in the process of writing. I (for whatever reason) never finished it, however, it does take 3 (C float) vertices and outputs a binary RDP triangle command (although, only flat shaded triangles). Feel free to use this meager code in anyway you want.

Thanks. That will be helpful. I should be able to put that right into a sample app using libdragon to check it out.


The next step I was trying to achieve was to calculate the coefficients for Goraud shaded, textured or Z buffered triangles. However, I never quite understood one of the RDP's coefficient values, the "change in <whatever value> along the edge". This is present in the shade, texture and Z coefficients. If anyone can shed a light on this, it would be helpful. I did at one point ask the original architect of the RDP (Tim Van Hook) what this value was, but I don't think he could really recall.

If I figure any of that out, I'll be sure to post.
 
OP
A

Aphex123

New member
Thanks. That will be helpful. I should be able to put that right into a sample app using libdragon to check it out.

I was originally tempted to contact the creator or libdragon, since I noticed that it had no support for rendering triangles. I would love to see an unofficial library doing all the things the official ones can, and in my opinion, it's certainly not out of reach. Even when it comes to the RSP, there is documentation/code detailing the inner workings and architecture.

Anyway, I would love to see some home-brew triangles being rendered on the real N64. Rectangles are boring. Good luck.
 
Last edited:

Top