SNES HDMA (Horizontal-blanking DMA). More...
#include <snes/types.h>Go to the source code of this file.
Macros | |
| #define | HDMA_CHANNEL_0 0 |
| HDMA channel 0 (conflicts with common DMA usage). | |
| #define | HDMA_CHANNEL_1 1 |
| HDMA channel 1. | |
| #define | HDMA_CHANNEL_2 2 |
| HDMA channel 2. | |
| #define | HDMA_CHANNEL_3 3 |
| HDMA channel 3. | |
| #define | HDMA_CHANNEL_4 4 |
| HDMA channel 4. | |
| #define | HDMA_CHANNEL_5 5 |
| HDMA channel 5. | |
| #define | HDMA_CHANNEL_6 6 |
| HDMA channel 6 (recommended for HDMA — never used by the runtime). | |
| #define | HDMA_CHANNEL_7 7 |
| HDMA channel 7 — reserved for the OAM DMA, do not use for HDMA (see the. | |
| #define | HDMA_DEST_BG1HOFS 0x0D |
| Destination: BG1 H scroll ($210D). | |
| #define | HDMA_DEST_BG1VOFS 0x0E |
| Destination: BG1 V scroll ($210E). | |
| #define | HDMA_DEST_BG2HOFS 0x0F |
| Destination: BG2 H scroll ($210F). | |
| #define | HDMA_DEST_BG2VOFS 0x10 |
| Destination: BG2 V scroll ($2110). | |
| #define | HDMA_DEST_BG3HOFS 0x11 |
| Destination: BG3 H scroll ($2111). | |
| #define | HDMA_DEST_BG3VOFS 0x12 |
| Destination: BG3 V scroll ($2112). | |
| #define | HDMA_DEST_BGMODE 0x05 |
| Destination: BGMODE ($2105) — mid-frame BG mode switch (F-Zero split). | |
| #define | HDMA_DEST_CGADD 0x21 |
| Destination: CGRAM address ($2121) - for palette effects. | |
| #define | HDMA_DEST_CGDATA 0x22 |
| Destination: CGRAM data ($2122) - for color effects. | |
| #define | HDMA_DEST_COLDATA 0x32 |
| Destination: Fixed color ($2132). | |
| #define | HDMA_DEST_INIDISP 0x00 |
| Destination: INIDISP brightness ($2100). | |
| #define | HDMA_DEST_M7A 0x1B |
| Destination: Mode 7 matrix A ($211B). | |
| #define | HDMA_DEST_M7B 0x1C |
| Destination: Mode 7 matrix B ($211C). | |
| #define | HDMA_DEST_M7C 0x1D |
| Destination: Mode 7 matrix C ($211D). | |
| #define | HDMA_DEST_M7D 0x1E |
| Destination: Mode 7 matrix D ($211E). | |
| #define | HDMA_DEST_TM 0x2C |
| Destination: TM main-screen layer enable ($212C). | |
| #define | HDMA_DEST_WH0 0x26 |
| Destination: Window 1 left ($2126). | |
| #define | HDMA_DEST_WH1 0x27 |
| Destination: Window 1 right ($2127). | |
| #define | HDMA_INDIRECT 0x40 |
| Indirect HDMA flag (DMAP bit 6) — use hdmaSetupIndirect(). | |
| #define | HDMA_MODE_1REG 0x00 |
| HDMA mode: 1 register, 1 byte. | |
| #define | HDMA_MODE_1REG_2X 0x02 |
| HDMA mode: 1 register, 2 bytes written twice. | |
| #define | HDMA_MODE_2REG 0x01 |
| HDMA mode: 2 registers, 2 bytes (low/high). | |
| #define | HDMA_MODE_2REG_2X 0x03 |
| HDMA mode: 2 registers, 4 bytes (2 to each). | |
| #define | HDMA_MODE_4REG 0x04 |
| HDMA mode: 4 registers, 4 bytes. | |
Functions | |
| void | hdmaBrightnessGradient (u8 channel, u8 topBrightness, u8 bottomBrightness) |
| Create a vertical brightness gradient. | |
| void | hdmaBrightnessGradientStop (u8 channel) |
| Stop brightness gradient and restore full brightness. | |
| void | hdmaColorGradient (u8 channel, u8 colorIndex, u16 topColor, u16 bottomColor) |
| Create a per-scanline CGRAM color gradient. | |
| void | hdmaColorGradientStop (u8 channel) |
| Stop color gradient effect. | |
| void | hdmaDisable (u8 channel) |
| Disable one HDMA channel, given by number. | |
| void | hdmaDisableAll (void) |
| Disable all HDMA channels. | |
| void | hdmaDisableMask (u8 channelMask) |
| Disable HDMA channel(s), given as a bit mask. | |
| void | hdmaEnable (u8 channel) |
| Enable one HDMA channel, given by number. | |
| void | hdmaEnableMask (u8 channelMask) |
| Enable HDMA channel(s), given as a bit mask. | |
| u8 | hdmaGetEnabled (void) |
| Get currently enabled HDMA channels. | |
| void | hdmaGradient (u8 channel, const void *colorTable) |
| Set up a fixed color gradient effect. | |
| void | hdmaIrisWipe (u8 channel, u8 layers, u8 centerX, u8 centerY, u8 radius) |
| Create a circular window mask (iris/spotlight effect). | |
| void | hdmaIrisWipeStop (u8 channel) |
| Stop iris wipe effect and restore window registers. | |
| void | hdmaParallax (u8 channel, u8 bg, const void *scrollTable) |
| Set up a background parallax scroll effect. | |
| void | hdmaSetTable (u8 channel, const void *table) |
| Update HDMA table pointer (for dynamic effects). | |
| void | hdmaSetup (u8 channel, u8 mode, u8 destReg, const void *table) |
| Set up an HDMA channel. | |
| void | hdmaSetupIndirect (u8 channel, u8 mode, u8 destReg, const void *table, u8 dataBank) |
| Configure an INDIRECT HDMA channel (table of pointers to data). | |
| void | hdmaWaterRipple (u8 channel, u8 bg, u8 amplitude, u8 speed) |
| Create a water ripple distortion effect. | |
| void | hdmaWaveH (u8 channel, u8 bg, u8 amplitude, u8 frequency) |
| Set up horizontal wave effect (water reflection). | |
| void | hdmaWaveInit (void) |
| Initialize HDMA wave effect system. | |
| void | hdmaWaveSetSpeed (u8 speed) |
| Set wave speed. | |
| void | hdmaWaveStop (void) |
| Stop wave effect and disable HDMA channel. | |
| void | hdmaWaveUpdate (void) |
| Update wave animation. | |
| void | hdmaWindowShape (u8 channel, const void *windowTable) |
| Set up window position HDMA for shape effects. | |
SNES HDMA (Horizontal-blanking DMA).
HDMA transfers data to PPU registers once per scanline during HBlank, enabling effects like color gradients, parallax scrolling, and wave distortion that change across the screen.
HDMA uses a table in memory that specifies:
Each entry in the table is:
Non-repeat mode (bit 7 = 0): Write data ONCE, then skip N-1 scanlines
Use for registers that hold their value (COLDATA, CGADD). Efficient: 1 data set per group, regardless of line count.
Repeat mode (bit 7 = 1): the entry carries ONE DATA SET PER SCANLINE — N lines means N data sets follow the count byte, each written on its line.
Use it when the value changes every line (a wave, a perspective table). A table of $81, data triples is the simplest per-scanline layout. (Until 2026-09-20 this block showed $82 followed by a single data set: the hardware would have read the next entry's count byte as line 2's data.)
The table pointer is a 4-byte far pointer (chantier A6, v0.19.0): hdmaSetup() programs the channel's source bank from the pointer's bank byte, so a table in any ROM bank (the asset banks, where C const data lives since #127.3) or in bank $7E RAM works as is. A table assembled outside C is a far pointer too (its label carries the bank); the explicit-bank form of 0.x was removed on 2026-10-05.
Any PPU register HOLDS the value HDMA wrote until something writes it again, scroll registers included: a non-repeat entry (32, lo, hi) writes once and the value stays for the 32 lines. Repeat mode is not about the register "forgetting" — it is needed exactly when the DATA differs from line to line, because only repeat entries carry per-line data.
This section said the opposite until 2026-09-20 ("scroll registers require repeat mode … the value is lost"), with an example ($A0, $20, $00) that was itself malformed — a 32-line repeat entry needs 32 data sets. The claim came from a misread table format; the references state it plainly: snesdev-wiki, "DMA registers / HDMA table format" ("$01-$80: write once, then wait for X scanlines; $81-$FF: write every scanline for X-$80 scanlines"; in repeat mode "the total size of the data section is the number of scanlines multiplied by the number of bytes in the pattern"), and anomie's register doc, same wording. See also .claude/rules/hardware_claims.md.
| #define HDMA_CHANNEL_0 0 |
HDMA channel 0 (conflicts with common DMA usage).
| #define HDMA_CHANNEL_1 1 |
HDMA channel 1.
| #define HDMA_CHANNEL_2 2 |
HDMA channel 2.
| #define HDMA_CHANNEL_3 3 |
HDMA channel 3.
| #define HDMA_CHANNEL_4 4 |
HDMA channel 4.
| #define HDMA_CHANNEL_5 5 |
HDMA channel 5.
| #define HDMA_CHANNEL_6 6 |
HDMA channel 6 (recommended for HDMA — never used by the runtime).
| #define HDMA_CHANNEL_7 7 |
HDMA channel 7 — reserved for the OAM DMA, do not use for HDMA (see the.
| #define HDMA_DEST_BG1HOFS 0x0D |
Destination: BG1 H scroll ($210D).
| #define HDMA_DEST_BG1VOFS 0x0E |
Destination: BG1 V scroll ($210E).
| #define HDMA_DEST_BG2HOFS 0x0F |
Destination: BG2 H scroll ($210F).
| #define HDMA_DEST_BG2VOFS 0x10 |
Destination: BG2 V scroll ($2110).
| #define HDMA_DEST_BG3HOFS 0x11 |
Destination: BG3 H scroll ($2111).
| #define HDMA_DEST_BG3VOFS 0x12 |
Destination: BG3 V scroll ($2112).
| #define HDMA_DEST_BGMODE 0x05 |
Destination: BGMODE ($2105) — mid-frame BG mode switch (F-Zero split).
| #define HDMA_DEST_CGADD 0x21 |
Destination: CGRAM address ($2121) - for palette effects.
| #define HDMA_DEST_CGDATA 0x22 |
Destination: CGRAM data ($2122) - for color effects.
| #define HDMA_DEST_COLDATA 0x32 |
Destination: Fixed color ($2132).
| #define HDMA_DEST_INIDISP 0x00 |
Destination: INIDISP brightness ($2100).
| #define HDMA_DEST_M7A 0x1B |
Destination: Mode 7 matrix A ($211B).
| #define HDMA_DEST_M7B 0x1C |
Destination: Mode 7 matrix B ($211C).
| #define HDMA_DEST_M7C 0x1D |
Destination: Mode 7 matrix C ($211D).
| #define HDMA_DEST_M7D 0x1E |
Destination: Mode 7 matrix D ($211E).
| #define HDMA_DEST_TM 0x2C |
Destination: TM main-screen layer enable ($212C).
| #define HDMA_DEST_WH0 0x26 |
Destination: Window 1 left ($2126).
| #define HDMA_DEST_WH1 0x27 |
Destination: Window 1 right ($2127).
| #define HDMA_INDIRECT 0x40 |
Indirect HDMA flag (DMAP bit 6) — use hdmaSetupIndirect().
When set, the table contains [count][ptr16] entries whose pointers reference the actual data. Useful for large or dynamic per-line data (e.g. a full CGRAM gradient per scanline).
| #define HDMA_MODE_1REG 0x00 |
HDMA mode: 1 register, 1 byte.
Writes 1 byte to destination register each scanline. Table entry: 1 byte line count + 1 byte data
| #define HDMA_MODE_1REG_2X 0x02 |
HDMA mode: 1 register, 2 bytes written twice.
Writes 2 bytes to same register (for double-write registers). Table entry: 1 byte line count + 2 bytes data
| #define HDMA_MODE_2REG 0x01 |
HDMA mode: 2 registers, 2 bytes (low/high).
Writes 2 bytes to consecutive registers (e.g., scroll low/high). Table entry: 1 byte line count + 2 bytes data
| #define HDMA_MODE_2REG_2X 0x03 |
HDMA mode: 2 registers, 4 bytes (2 to each).
Writes 4 bytes: 2 to dest, 2 to dest+1. Table entry: 1 byte line count + 4 bytes data
| #define HDMA_MODE_4REG 0x04 |
HDMA mode: 4 registers, 4 bytes.
Writes 4 bytes to 4 consecutive registers. Table entry: 1 byte line count + 4 bytes data
Create a vertical brightness gradient.
Smoothly fades screen brightness from top to bottom using HDMA on the INIDISP register ($2100). Useful for:
| channel | HDMA channel (6 or lower; 7 belongs to the NMI OAM DMA) |
| topBrightness | Brightness at top of screen (0-15, 15=full) |
| bottomBrightness | Brightness at bottom of screen (0-15) |
Called again while the effect runs, it only moves the channel's table pointer, which HDMA reads at the next frame: the change lands whole. (Until 2026-10-03 the channel was set up again, which restarted the table at the next HBlank — one frame with the top of the table on the bottom of the screen.)
| void hdmaBrightnessGradientStop | ( | u8 | channel | ) |
Stop brightness gradient and restore full brightness.
| channel | The channel used for the gradient |
Create a per-scanline CGRAM color gradient.
Smoothly interpolates a palette color from one value to another across the screen. Uses HDMA to rewrite a CGRAM entry per scanline. Useful for:
| channel | HDMA channel (6 or lower; 7 belongs to the NMI OAM DMA) |
| colorIndex | CGRAM color index to modify (0-255) |
| topColor | 15-bit SNES color at top of screen (use RGB() macro) |
| bottomColor | 15-bit SNES color at bottom of screen |
Called again while the effect runs, it only moves the channel's table pointer, which HDMA reads at the next frame: the change lands whole. (Until 2026-10-03 the channel was set up again, which restarted the table at the next HBlank — one frame with the top of the table on the bottom of the screen.)
| void hdmaColorGradientStop | ( | u8 | channel | ) |
Stop color gradient effect.
| channel | The channel used for the gradient |
| void hdmaDisable | ( | u8 | channel | ) |
Disable one HDMA channel, given by number.
The same as hdmaDisableMask(1 << channel). A value above 7 is refused and nothing changes (see hdmaEnable()).
| channel | HDMA channel 0-7 |
| void hdmaDisableAll | ( | void | ) |
Disable all HDMA channels.
Convenience function to stop all HDMA activity.
| void hdmaDisableMask | ( | u8 | channelMask | ) |
Disable HDMA channel(s), given as a bit mask.
Disables the specified HDMA channel(s).
| channelMask | Bitmask of channels to disable (1 << channel) |
| void hdmaEnable | ( | u8 | channel | ) |
Enable one HDMA channel, given by number.
The same as hdmaEnableMask(1 << channel): the channel runs from the next HBlank, the others are left as they are. A value above 7 is refused and nothing changes — the 0.x form of this call took a bit mask, so a call left as hdmaEnable(1 << HDMA_CHANNEL_6) fails visibly instead of enabling channel 64. (Since 0.49, 2026-10-05; a mask of 1, 2 or 4 left behind is the one case this check cannot see — make check-upgrade lists every call to re-read.)
| channel | HDMA channel 0-7 (HDMA_CHANNEL_0 … HDMA_CHANNEL_7) |
| void hdmaEnableMask | ( | u8 | channelMask | ) |
Enable HDMA channel(s), given as a bit mask.
Enables the specified HDMA channel(s). The channel runs from the next HBlank: hdmaSetup() has initialised its table address and line counter by hand, which the hardware only does by itself at the start of a frame (anomie-regs, "DMA and HDMA"). snesdev-wiki advises writing HDMAEN during VBlank while the screen is on; an effect enabled mid-frame shows from the next line. The one pair of this header that takes a mask where every other function takes a channel number: the name says so. For a single channel, hdmaEnable(channel) is the same write.
| channelMask | Bitmask of channels to enable (1 << channel) |
| u8 hdmaGetEnabled | ( | void | ) |
Get currently enabled HDMA channels.
| void hdmaGradient | ( | u8 | channel, |
| const void * | colorTable ) |
Set up a fixed color gradient effect.
Creates a vertical color gradient by changing the fixed color register per scanline. Useful for sky gradients, underwater effects, etc.
| channel | HDMA channel to use |
| colorTable | HDMA table with COLDATA values |
Create a circular window mask (iris/spotlight effect).
Uses HDMA to drive window registers (WH0/WH1) per scanline, approximating a circle. Configures all window registers automatically. Useful for:
| channel | HDMA channel (6 or lower; 7 belongs to the NMI OAM DMA) |
| layers | Layer bitmask to apply window masking (TM_BG1, TM_BG2, etc.) |
| centerX | Horizontal center of circle (0-255) |
| centerY | Vertical center of circle (0-223) |
| radius | Circle radius in pixels (0-128) |
Called again while the effect runs, it only moves the channel's table pointer, which HDMA reads at the next frame: the change lands whole. (Until 2026-10-03 the channel was set up again, which restarted the table at the next HBlank — one frame with the top of the table on the bottom of the screen.)
While it runs the effect owns W12SEL, W34SEL, WOBJSEL and TMW and writes them directly: the window module's own shadows do not see those writes, so do not call windowEnable() / windowSetMainMask() for the same layers until hdmaIrisWipeStop().
| void hdmaIrisWipeStop | ( | u8 | channel | ) |
Stop iris wipe effect and restore window registers.
Disables the HDMA channel and clears all window masking registers (W12SEL, W34SEL, WOBJSEL, TMW) to restore normal display.
| channel | The channel used for the iris wipe |
Set up a background parallax scroll effect.
Creates horizontal parallax scrolling where each section of the screen scrolls at a different speed based on the scroll table.
| channel | HDMA channel to use |
| bg | Background layer (0=BG1, 1=BG2, 2=BG3) |
| scrollTable | HDMA table with scroll values |
| void hdmaSetTable | ( | u8 | channel, |
| const void * | table ) |
Update HDMA table pointer (for dynamic effects).
Changes the table pointer for an already-configured channel. Takes effect on the next frame.
| channel | HDMA channel (0-7) |
| table | New table pointer |
Set up an HDMA channel.
Configures an HDMA channel with the specified parameters. The channel is NOT enabled automatically - call hdmaEnableMask() to start it.
| channel | HDMA channel (0-7, use HDMA_CHANNEL_6 or lower — 7 belongs to the NMI OAM DMA) |
| mode | Transfer mode (HDMA_MODE_*) |
| destReg | Destination B-bus register (low byte of $21xx address) |
| table | Pointer to HDMA table in ROM or RAM |
Configure an INDIRECT HDMA channel (table of pointers to data).
Table entries are [count][ptr_lo][ptr_hi]: each 16-bit pointer references count lines worth of data in dataBank. The INDIRECT bit is OR'd into mode internally — pass a plain HDMA_MODE_*. The table's own bank comes from the far pointer (any bank works); dataBank is the bank of the POINTED-TO data ($43x7).
Classic use: per-scanline CGRAM reloads (HiColor-style gradients) — each table entry points at a block of palette data for that line.
| channel | HDMA channel (0-7) |
| mode | Transfer pattern (HDMA_MODE_*, without HDMA_INDIRECT) |
| destReg | Destination register (HDMA_DEST_*) |
| table | Indirect table ([count][ptr16] entries + 0 terminator) |
| dataBank | Bank byte of the data the pointers reference. For a C array, extract it from the far pointer: (u8)((u32)(void *)my_data >> 16) (post-A6 pointers carry the bank in byte 2) — RAM arrays give 0. |
Create a water ripple distortion effect.
Similar to hdmaWaveH but with amplitude that increases from top to bottom, simulating underwater refraction or heat haze. Uses the wave system's double-buffered tables internally.
| channel | HDMA channel (6 or lower; 7 belongs to the NMI OAM DMA) |
| bg | Background layer (0=BG1, 1=BG2, 2=BG3) |
| amplitude | Maximum ripple amplitude at bottom of screen (1-60 pixels, clamped) |
| speed | Animation speed (1=slow, 4=fast) |
Set up horizontal wave effect (water reflection).
Creates a wavy horizontal distortion, commonly used for:
| channel | HDMA channel to use (6 or lower; 7 belongs to the NMI OAM DMA) |
| bg | Background layer to affect (0=BG1, 1=BG2, 2=BG3) |
| amplitude | Wave amplitude in pixels (1-60, clamped internally) |
| frequency | Wave frequency (1-16, higher = tighter waves). Period = 256/frequency scanlines. |
| void hdmaWaveInit | ( | void | ) |
Initialize HDMA wave effect system.
Must be called once before using wave effects. Resets the wave state (the buffers are static — nothing is allocated).
| void hdmaWaveSetSpeed | ( | u8 | speed | ) |
Set wave speed.
| speed | Animation speed (1=slow, 4=fast, default=2) |
| void hdmaWaveStop | ( | void | ) |
Stop wave effect and disable HDMA channel.
The wave table writes ABSOLUTE offsets into the layer's HOFS, over the value bgSetScroll() set; while it runs, the layer's own scroll is not applied. Stopping marks the layer's scroll dirty so the NMI rewrites it from the shadows at the next VBlank (until 2026-10-03 it wrote 0).
Disables the wave effect and frees the HDMA channel.
| void hdmaWaveUpdate | ( | void | ) |
Update wave animation.
Call this once per frame (after WaitForVBlank) to animate the wave effect. Updates the HDMA table with new wave values.
| void hdmaWindowShape | ( | u8 | channel, |
| const void * | windowTable ) |
Set up window position HDMA for shape effects.
Uses HDMA to change window boundaries per scanline, creating shapes like circles, triangles, or custom masks.
| channel | HDMA channel to use |
| windowTable | HDMA table with left/right pairs |