This example demonstrates how to use HDMA (Horizontal Blank DMA) to write different brightness values to the INIDISP register ($2100) on each group of scanlines, creating a vertical brightness gradient across the screen. A full-color 256-color background image is displayed in Mode 3, and HDMA progressively dims the image from top to bottom. Press A to cycle through 15 gradient intensity levels.
HDMA is a special DMA mode that transfers a small amount of data to a PPU register at the start of every horizontal blanking period (between scanlines). Register $2100 (INIDISP) controls the master screen brightness: bits 0-3 set brightness from 0 (black) to 15 (full), and bit 7 enables forced blanking. By writing different brightness values per scanline group, you create a gradient fade effect entirely in hardware – the CPU only builds the table once.
HDMA mode 0 writes 1 byte per entry to a single register. Each table entry is 2 bytes: a scanline count followed by the data byte. A count of 0 terminates the table. This example creates 16 entries of 14 scanlines each (16 x 14 = 224, covering the full visible screen). The brightness value decreases as you go down the screen:
The library function hdmaSetup(channel, mode, dest_register, table_ptr) configures an HDMA channel. In this example: channel 3, mode 0 (1-register/no-repeat), destination register offset $00 (which maps to $2100 = INIDISP), and a pointer to the gradient table in RAM. The channel is activated with hdmaEnable(3) and runs automatically every frame until disabled.
The 8bpp background image requires about 39KB of tile data – more than the 32KB bank $00 limit. The data is split across two SUPERFREE ROM sections that the linker may place in bank $01+. Since the C library's dmaCopyVram() hardcodes bank $00, a custom assembly loadGraphics function uses WLA-DX's :label syntax to resolve the correct source bank byte at link time, performing 4 separate DMA transfers (two tile chunks, palette, and tilemap).
| Button | Action |
|---|---|
| A | Cycle gradient intensity (15 down to 2, then back to 15) |
1. Graphics loading – An assembly function handles all VRAM/CGRAM transfers during force blank, splitting the large tileset across two DMA operations with correct bank bytes:
2. BG1 configuration – Mode 3 is selected for 256-color BG1. The tilemap is at VRAM $0000 and tile data starts at VRAM $1000:
3. Building the gradient table – The buildGradientTable() function computes 16 brightness steps from the current level down to 0, writing them into a static RAM buffer:
4. HDMA activation – Channel 3 is configured for mode 0 writes to register $00 (INIDISP = $2100), and enabled:
5. Interactive cycling – Pressing A decreases the gradient level (fewer brightness steps = more uniform brightness), wrapping from 2 back to 15. The table is rebuilt in RAM and HDMA picks up the new values on the next frame automatically.
Then open hdma_gradient.sfc in your emulator (Mesen2 recommended).