SNES programming tutorial. Example 3.
So, this is the big lesson. There are about a dozen things we need to do just to see a picture on the screen. We need to set a video mode. We need to enable a layer on the main screen. We need to set the address for tiles. We need to set the address for a tilemap. We need to make tiles and a tilemap (and a palette). We need to copy all the things to the VRAM. And we need to turn screen brightness on (and end forced blank).
I picked a random picture of a moon. Open in GIMP, resize to 128×128. (a little later, the image looked stretched out when I got it running on my actual SNES, due to the aspect ratio of SNES pixels being about 8/7)… resize to 112×128 to fix the sideways stretching. Save as PNG.
Now get my background tool for SNES (Mode 1). It can import images (up to 256×256). It can do all the steps we need.
Import Image / get palette will auto generate an ideal 16 color palette.
Import Image / get tiles/map will turn the image into SNES graphics, and fill the map. Make sure the map height is 28. I used the arrows above the map editor to shift the image to the center. Now save… Save palette 16 colors (.pal). Save tiles 4bpp x 1 (.chr). Save maps just to the map height (.map).
These were added to the project by adding .incbin lines at the bottom of main.asm.
Now the code, which I will go over, line by line… but first I want to figure out where we are putting things in the VRAM. This is what I have been using, and it seems to work for my current needs. This arrangement is optional. You can rearrange the VRAM any way you like.
$0000 4bpp BG tiles (768 of them)
$3000 2bpp BG tiles (512 of them)
$4000 4bpp sprite tiles (512 of them)
$6000 layer 1 map (up to 2 screens)
$6800 layer 2 map (up to 2 screens)
$7000 layer 3 map (up to 2 screens)
$7800 layer 4 map (up to 2 screens)
So we need to put the 4bpp tiles at $0000 and the layer 1 map at $6000.
; DMA from Palette_Copy to CGRAM ; see previous tutorial page for that code ; DMA from Tiles to VRAM lda #V_INC_1 ; the value $80 sta VMAIN ; $2115 register, set the increment mode +1 ; each write will go +1 the previous write address ldx #$0000 stx VMADDL ; $2116 set an address in the vram of $0000 ; now we set up the DMA lda #1 sta $4300 ; transfer mode, 2 registers 1 write ; $2118 and $2119 are a pair Low/High lda #$18 ; $2118 sta $4301 ; destination, vram data ldx #.loword(Tiles) stx $4302 ; source lda #^Tiles sta $4304 ; bank ldx #(End_Tiles-Tiles) ; let the assembler calculate the size of transfer ; using 2 labels before and after our tiles. stx $4305 ; length lda #1 sta $420b ; start dma, channel 0 ; DMA from Tilemap to VRAM ldx #$6000 stx VMADDL ; set an address in the vram of $6000 lda #1 sta $4300 ; transfer mode, 2 registers 1 write ; $2118 and $2119 are a pair Low/High lda #$18 ; $2118 sta $4301 ; destination, vram data ldx #.loword(Tilemap) stx $4302 ; source lda #^Tilemap sta $4304 ; bank ldx #$700 stx $4305 ; length lda #1 sta $420b ; start dma, channel 0 ; a is still 8 bit. lda #1 ; mode 1, tilesize 8x8 all sta BGMODE ; $2105 stz BG12NBA ; $210b tiles for BG 1+2 at VRAM address $0000 lda #$60 ; bg1 map at VRAM address $6000 sta BG1SC ; $2107 lda #BG1_ON ; $01 = only bg 1 is active sta TM ; $212c lda #FULL_BRIGHT ; $0f = turn the screen on (end forced blank) sta INIDISP ; $2100
Let’s go over these last lines. $2105 is for video mode. We want mode 1, and 8×8 tiles. Our tiles will need to be 4bpp for layers 1 and 2. (we are only using layer 1). The bits of $2105 are 4321Emmm, with mmm for BG Mode. E will affect priority of BG3. 4321 are zero so all the layers have 8×8 tiles. If any of these are set, the corresponding layer will have 16×16 tiles.
$210b tells the PPU where our tiles are (for layer 1 and 2). Low nibble for layer 1 and high nibble for layer 2. Our tiles are at $0000 so we are just storing zero. But if we wanted to, we could put our tiles at $1000 for layer 1 by storing #1 to 210b. They are steps of $1000.
This is the perfect opportunity to point out that for all these “VRAM address X is for Y” registers the upper bit is always zero. There are only $8000 VRAM addresses, and the registers always look like they go up to $FFFF, but they don’t. My guess is that the original engineers were told that there would be 128 kB of VRAM, but some bean counter said “128 kB is too expensive, we only need 64 kB”.
So, bbbb aaaa is really -bbb -aaa (a = layer 1, b = layer2).
So, for 210b, only values 0-7 make sense for each nibble.
Maps have 6 bits for VRAM address. They are steps of $400, but the low 2 bits of the $2107 register are for map size… aaaaaayx where a is VRAM address and yx is map size (is really -aaaaayx with upper bit always 0 since VRAM addresses don’t go above $8000). It looks like you are just multiplying by $100. The value $60 is for VRAM address $6000, where our tile map for layer 1 will go.
You can reference this page for more information.
And we need to turn on layer 1 on the main screen $212c. (It would look really weird if we turned on ALL layers right now. All the other maps are still set to $0000 where our tiles are).
My main focus was setting up a tool chain for putting an image on screen. Here’s what it looks like. Try to repeat the process with a different picture. Maybe something more colorful?
What’s that RLE folder?
Another option for M1TE files (map and tiles) is to save as .rle (run length encoding). It’s slightly more advanced than a simple rle. It is designed for map compression. Decompression should be done during Forced blank. First set a VRAM address, then use this macro: UNPACK_TO_VRAM [rle address]. That will decompress it (to $7f0000) and then copy it to the VRAM.
See mainB.asm for an example.
You may choose to leave everything uncompressed and skip the RLE stuff, and only use it if you run out of ROM space. You decide.