N64 Assembly Tutorial - Lesson 5

Updated: N64 Assembly YouTube Series

The same content updated with more explanation and showing how it's done. Twitch stream style.

https://www.youtube.com/playlist?list=PLjwOF_LvxhqTXVUdWZJEVZxEUG5qt8fsA

Lesson 5

Inspired by Peter

Advanced Video Settings

The N64 has a very capable Video chip that just needs to be configured correctly for NTSC or PAL. It's the same chip, the only known difference between the PAL and NTSC Console is a couple of bytes in the PIF binary code and a PCB clock crystal.

This lesson has a lot of background and a little bit of code. The code below will improve the quality of the game that we can produce. I'm not as familiar with the PAL standard, although any calculations should be similar.

NTSC

The commonly accepted parameters of NTSC are 320 x 240 @ 29.97 frames/second. Using Interlacing which is supported by many TV's, it's possible to output 640 x 480.

The Video Interface configuration includes a parameter "lines", and set with a value of 525 lines (625 PAL). The TV does in fact receive 525 lines per frame, this includes non-image lines which provide horizontal and vertical synchronization and additional data (Closed Captioning) related to the video signal as well as the odd and even interlaced lines of Video.

Console

The Main CPU of the Console (VR4300i) runs at 93.75 MHz and the RSP is clocked at 62.5 MHz.

Resources Available per Frame at different Frame Rates (frames/second in Millions)

These numbers might seem big when you are are working at such a low level but little things add up quickly.

  • A 640 x 480 @ 32 bit color, frame buffer is 1.2 MB of RAM (of 4 to 8 MB).

  • Copying it using a 4 instruction loop would use up 1.2 Million Main CPU Instruction cycles.

This is a contrived example a raw copy like this would usually be done with DMA, but the reminder is there are not a lot of processing resources or RAM available.

Put it all together

The first question is when is "my draw" time (buffer write) vs "tv draw" time (buffer read). If both the write and read cross each other in the same block of RAM there is a risk of "tearing" when the frame in memory has changed while it's being drawn on the TV. The easiest way to avoid this is with Double Buffering.

  1. Set Buffer 1 to Display on TV

  2. Programmer writes to Frame Buffer 2

  3. Set Buffer 2 to Display on TV

  4. Programmer writes to Frame Buffer 1

  5. Go to Step 1

This is a common technique, the questions are:

When should we swap the buffers?

Older TV's had one "pixel gun" shooting each pixel from left to right and top to bottom. There is a Horizontal blanking period when the "pixel gun" is moving from the end of one line to the beginning of the next (left <- right) and a Vertical blanking period when the "pixel gun" is moving from the bottom right corner to the top left corner. On the N64 it's called the VBLANK period. As stated above there are 525 lines and the first 42 lines are for synchronization and data. Combining the VBlank period and the first 42 lines is the perfect window of time. We just need to make sure the Buffer is swapped and ready when it starts drawing Pixel 1 of Visible Line 1.

How long does it take to swap buffers?

For now we are using an emulator for testing, each emulator chooses how to implement this. TODO: Experiment on real hardware.

Video Configuration - "Final"

At some point we need to pick some values and use them. I'll discuss some pros and cons after.

  • 640 x 480 (w x h)

  • 16 Bit Color

  • Double Buffered

  • 60 Frames / second

The screen resolution of 640 x 480 @ 16 bit color is a really nice resolution. The goal is to set some reasonable defaults that will work for just following along or if you decide to create your own full featured game.

  1. 640 x 480 works on my actual console over an S-Video cable (Looks great!)

  2. Color Constants makes selecting a color easier

  3. Double Buffers are 600 KB x 2 which fits nicely in a 4 MB console

    1. Note also when we get to 3D we will need an additional 600 KB for Z Buffer

  4. Should be able to fit program code, primary resources, and both Frame buffers under 2 MB.

    1. Additional "Level" data could be loaded in the 2-6 remaining MB of RAM

    2. Many references suggest that the first 2 MB of RAM is faster by some small amount.

At this point I'm showing that there is a lot of planning for games and lots of things to consider. I've been working on a "Game Memory Map" but I don't want to lay it all out because of the complexity, it will be revealed as we need it ( I may also adjust some values as we progress).

Implementation

What's the compiler got to do with our frame buffer?

Maybe you've noticed but we haven't declared any variables yet? When we declared the frame buffer earlier it was just a location in memory, our program wasn't really at risk of using the memory so that worked fine.

As our programs get more complex we need to think about how our memory is used. To keep this simple I want to focus on the 4 MB RAM default configuration of the N64. While 8 MB makes it easy to get sloppy, it also makes it harder to "grow" the program/game beyond a certain point.

Assumptions

  • 4 MB RAM

  • 8 MB ROM Size (for explanation purposes)

  • 1st MB is copied during boot to the 1st MB of RAM.

    • 0x1000 0000 - 0x1010 0000 copied to 0x8000 0000 - 0x8010 0000

  • First 2 MB of RAM contains shared code, buffer and scratch memory.

  • Third and Fourth MB contains Level specific Code & Resources.

Things that we need:

  • MAP our ROM (*.N64) to match what we need in RAM

  • Load the additional memory areas to RAM

  • Learn about DMA

The first step is to define and reserve the memory space for our 2 frame buffers.

origin $0003'B000

constant FRAMEBUFFER1($A003'B000)

origin $000D'2000

constant FRAMEBUFFER2($A00D'2000)

In this case I'm going back to the 'origin' assembler directives and mapping the location of the Frame Buffer into the Cartridge Memory, which carries over to RAM when it's copied. The constant doesn't have to be here, but I think it adds to the clarity of the code.

N64/FrameBuffer/16BPP/FrameBufferDMA320x240/FrameBufferDMA16BPP320X240.asm

TODO:

DMA Cart Data to RAM

Lesson 4 - Lesson 6