This tutorial covers SNES graphics fundamentals including video modes, backgrounds, and tilemaps.
SNES Video Modes
The SNES has 8 video modes (0-7). OpenSNES primarily uses:
| Mode | BG1 | BG2 | BG3 | BG4 | Best For |
| 0 | 4 colors | 4 colors | 4 colors | 4 colors | Text, simple UI |
| 1 | 16 colors | 16 colors | 4 colors | - | Most games |
| 7 | 256 colors (rotatable) | - | - | - | Racing, rotation effects |
Setting Up Graphics
Initialize Display
while (1) {
}
return 0;
}
int main(void)
Definition main.c:66
void consoleInit(void)
Initialize SNES hardware.
void WaitForVBlank(void)
Wait for next VBlank period.
void setScreenOn(void)
Enable screen display.
Definition console.h:115
#define REG_TM
Main screen designation (W).
Definition registers.h:187
#define TM_BG1
Definition registers.h:445
#define BGMODE_MODE1
Definition registers.h:435
OpenSNES common-case master header.
void setMode(u8 mode, u8 flags)
Set background mode.
Loading Tiles to VRAM
Tiles are 8x8 pixel graphics stored in VRAM. For 4bpp (16 color) tiles:
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
void load_tiles(void) {
for (
i = 0;
i <
sizeof(my_tiles);
i += 2) {
}
}
static u8 i
Definition main.c:156
#define REG_VMADDH
VRAM address high (W).
Definition registers.h:124
#define REG_VMAIN
VRAM address increment mode (W).
Definition registers.h:112
#define REG_VMADDL
VRAM address low (W).
Definition registers.h:121
#define REG_VMDATAL
VRAM data write low (W).
Definition registers.h:127
#define REG_VMDATAH
VRAM data write high (W).
Definition registers.h:130
unsigned short u16
16-bit unsigned integer (0 to 65535)
Definition types.h:53
unsigned char u8
8-bit unsigned integer (0 to 255)
Definition types.h:47
Setting Up Tilemaps
Tilemaps define which tiles appear where on screen. Each entry is 2 bytes:
Bits: vhopppcc cccccccc
v = vertical flip
h = horizontal flip
o = priority
ppp = palette (0-7)
cccccccccc = tile number (0-1023)
void setup_tilemap(void) {
for (y = 0; y < 32; y++) {
for (x = 0; x < 32; x++) {
}
}
}
Setting Palettes
SNES uses 15-bit color (RGB555):
void setup_palette(void) {
}
#define REG_CGADD
CGRAM address (W).
Definition registers.h:154
#define REG_CGDATA
CGRAM data write (W).
Definition registers.h:157
Background Scrolling
void update_scroll(void) {
}
#define REG_BG1VOFS
BG1 vertical scroll (W, 2x write).
Definition registers.h:91
#define REG_BG1HOFS
BG1 horizontal scroll (W, 2x write).
Definition registers.h:88
signed short s16
16-bit signed integer (-32768 to 32767)
Definition types.h:50
Parallax Scrolling
For depth effect, scroll backgrounds at different speeds:
scroll_x++;
#define REG_BG2HOFS
BG2 horizontal scroll (W, 2x write).
Definition registers.h:94
Using gfx4snes
Convert PNG images to SNES format using gfx4snes (based on PVSnesLib):
# Convert 8x8 tiles with palette output
gfx4snes -s 8 -p -i tiles.png
# Output: tiles.pic (tile data), tiles.pal (palette)
# Convert 16x16 sprites with palette
gfx4snes -s 16 -p -i sprites.png
# Convert with metasprite definition (for animation)
gfx4snes -s 16 -p -T -X 32 -Y 48 -i character.png
# Output: character.pic, character.pal, character_meta.inc
# Convert against a committed palette (do not re-quantise)
gfx4snes -s 8 -c master.pal -i tiles.png
-c <file.pal> imposes a committed palette instead of quantising the image's own colours. The build fails — naming the offending colour — if the image uses any colour not in that palette. This is the fix for the "adding one tile re-quantises the whole palette and shifts every existing
tile's hue" problem: author the palette once, commit it, and impose it on every conversion so a new tile can only reuse existing entries.
Include the generated data in your assembly file:
sprite_tiles:
.incbin "res/sprites.pic"
sprite_tiles_end:
sprite_pal:
.incbin "res/sprites.pal"
sprite_pal_end:
Example: Complete Background Setup
See examples/scrolling/parallax_scroll/ for a complete parallax scrolling example.
Hi-res and interlace (Modes 5/6, SETINI)
BG Mode 5 renders 512 horizontal pixels (16x8 tiles, stored as 8x8 character pairs N/N+1); adding screen interlace (videoSetInterlace(1), SETINI bit 0 through the lib's write-only shadow) doubles vertical to 448 lines. Three traps, all demonstrated by examples/backgrounds/mode5_hires:
- Hi-res content displays through BOTH screens: setMainScreen(LAYER_BG1) AND setSubScreen(LAYER_BG1), or odd columns stay blank.
- In interlace, tile texel rows map 1:1 to hi-res lines — a full-height page needs 56 tile rows (a 32x64 tilemap), not 28.
- On a modern LCD the alternating columns show as fringing; a period CRT blended them. The example's README has the full explainer.
videoSetObjInterlace, videoSetOverscan and videoSetPseudoHires cover the other SETINI bits — overscan shrinks the VBlank DMA budget by ~15 lines, see the header warning.
Next Steps