cassidymoen's blog

Using SNES HDMA For Palette Flexibility


Lately I’ve been learning and experimenting with a SNES hardware feature called HDMA (Horizontal Direct Memory Access) for my work on the Super Metroid DASH Randomizer. DMA is a feature that allows a programmer to read from and write to regions of memory that aren’t mapped to the 24-bit memory space the CPU can address, also known as the A bus, at speeds much faster than the CPU can using hardware registers manually. For example, the Picture Processing Unit (the chip that puts together and outputs the video signal) has its own memory for tile and palette data which can’t be addressed by the main CPU, but we often use DMA transfers to write things like tile data and tilemaps. DMA allows us to program transfers for arbitrary amounts of data to and from each bus by writing the transfer parameters to hardware registers mapped to memory that the CPU can access. Most of the time, DMAs happen during a reset period, called vertical blanking (or v-blank,) after a frame is drawn but before the next frame has begun drawing. This is because we can only access certain PPU registers during blanking periods. However, there’s another blanking period, called horizontal blanking, that we can write to these registers as well. Horizontal DMA is a special type of DMA that happens mid-frame after a scanline has finished drawing but before the next has begun. HDMA is commonly used for visual effects such as windowing, gradients, wave-like effects, and so on due to the fine-grained control it gives us over writes to registers that can achieve these effects.

Modified version of Super Metroid HUD with the mini map in the upper-right replaced with several new, colorful icons and numbers. DASH Randomizer HUD

We can also use HDMA to write to CGRAM, where the SNES palette colors are stored. CGRAM can hold 255 word-length (16 bits, or two bytes) color values. In Super Metroid’s typical game screen (that is, while the player is actively playing and moving around,) the colors in CGRAM are used either as 16 palettes of 15 colors each (for the main portion,) or as 16 palettes of 3 colors each (for the HUD, which uses simpler graphics.) The zeroth (or first) color in each palette is used for transparency. If we imagine CGRAM as a 16x16 grid with the zeroth color at the top left and 255th at the bottom right, then the first eight rows would be palettes used by all background (or BG) tiles and the first four would be palettes that are available for the HUD tiles. But something to keep in mind is that these palettes must be shared. If we want to modify our HUD, which we do, then we can’t always simply change a color value that the HUD doesn’t use, because a background tile may also be using it. This is a typical difficulty encountered when it comes to SNES rom hacking and modifying an existing game with new art. You must take care to either use the existing palettes or not mistakenly alter the original graphics.

16x16 grid of colors representing the SNES CGRAM. Visual representation of Super Metroid CGRAM when first entering Crateria

But we don’t always want to use Super Metroid’s existing palettes. I could attempt to draw tiles we want on the sprite layer where there’s usually at least one free palette available because it has not been assigned to an enemy or another sprite. But that comes with a lot of extra effort and complication compared to drawing something on a BG layer. Sprites are good for graphics that we want to manipulate and animate smoothly. But I want a new color on a part of the screen that’s relatively simple and static, the HUD. This is where HDMA comes in. HDMA allows us to write to the CGRAM registers, which are unavailable during regular code execution outside of a blanking period. With HDMA, we can write one word of data (or one color,) per DMA channel per scanline. Since I know Super Metroid’s HUD ends on scanline 31, I can use a combination of setting new palette colors during the v-blank interrupt routine (NMI) before the “top” of the frame and swapping colors around every scanline as long as they’re at the desired color index during the HUD scanlines and back to the correct, vanilla colors by scanline 31.

Long story short, this is exactly what we do. First we set up an HDMA table in RAM that we can point our DMA controller at. This table has a binary format where each row has one byte indicating how many scanlines to wait until executing the next write (the first always happens on scanline 0 at the top of the screen,) one word indicating the color index, or where in our 255-color palette RAM we want to change, and one word for the color value to be written. This works because the register that sets our target address (CDADD) and the register that transfers the data to that address (CGDATA) are adjacent, so when we set our HDMA up we can configure it to conveniently use a write mode that writes the first word to CGADD and the second to CGDATA in that order. We keep our HDMA table in RAM so we can modify it to restore the correct colors in certain areas and situations that have different requirements. Our HDMA setup code extends the vanilla HDMA handling by jumping to a custom pointer in RAM that runs the table setup for a given game state. We set this pointer when the game starts to a routine that handles most situations, and run some code to replace the pointer in a handful of situations where we need a different setup for restoring different palettes. In the end, we have a reasonably convenient system that allows us to use four or five “extra” colors on Super Metroid’s HUD without altering or compromising on any other graphics.