How to create a smooth color gradient across the screen using HDMA to rewrite CGRAM (the palette) every few scanlines. This technique changes the backdrop color (color 0) as the PPU draws each line, creating sky gradients, underwater effects, or sunset transitions.
Read effects/hdma_wave first (HDMA basics and table format).
| Button | Action |
|---|---|
| A | Remove the gradient (flat background) |
| B | Re-enable the gradient |
Then open gradient_colors.sfc in your emulator (Mesen2 recommended).
This configures HDMA mode 3 (2REG_2X), which writes 4 bytes per entry to two consecutive registers:
| Byte | Register | Purpose |
|---|---|---|
| 1 | $2121 | Set CGRAM address (low) |
| 2 | $2121 | Set CGRAM address (high) – always 0 for color 0 |
| 3 | $2122 | Write color data (low byte of 15-bit color) |
| 4 | $2122 | Write color data (high byte of 15-bit color) |
Each entry in hdmaGradientList is:
The table defines color 0 (backdrop) for groups of scanlines. By stepping through blues, purples, and oranges, it creates a smooth vertical gradient.
The table ends with a 0x00 byte (zero line count = end of HDMA table).
HDMA can be enabled/disabled at any time. When disabled, color 0 reverts to whatever was last written to CGRAM.
The SNES palette holds 256 colors, each stored as a 15-bit value (5 bits per R/G/B channel). Color 0 is the "backdrop" – visible wherever no BG or sprite pixel is drawn. By rewriting color 0 per scanline group via HDMA, each band of the screen displays a different background color.
This mode writes 4 bytes to 2 consecutive registers, 2 bytes each. It is perfect for CGADD+CGDATA pairs because you need to set the target color address first (CGADD at $2121), then write the color value (CGDATA at $2122).
This gradient uses non-repeat entries (bit 7 = 0 in the count byte). The color is written once, then holds for N scanlines. This works because CGRAM latches its value – unlike scroll registers (BG1HOFS etc.) which need repeat mode with fresh writes every scanline.
| File | Purpose |
|---|---|
| main.c | Gradient setup, enable/disable, input handling |
| res/opensnes.png.toml | the import settings of the picture (opensnes-tileset); the build converts and links it |
| data.asm | the HDMA gradient table (hdmaGradientList), hand-written |
| res/opensnes.png | Source background image |
| Makefile | LIB_MODULES := console dma background sprite hdma input math |
console, dma, background, sprite, hdma, input (LIB_MODULES in the Makefile).
krom's RedSpace9BitHDMA trick, the gradient_9bit example until 2026-10-05: one channel rewrites the backdrop colour per scanline exactly as this example does, and a second channel rewrites INIDISP's master brightness per scanline (HDMA_MODE_1REG into $2100). Colour x brightness, plus a one-line jitter, dithers the 5-bit gradient into more perceptual steps than the PPU has. INIDISP then belongs to the stream: the program never calls setScreenOn() itself, and the brightness table must end at full brightness or the screen stays dim after the last line.