Super FX Graphics - brunovalads/yoshisisland-disassembly GitHub Wiki

Graphical bitmaps in Yoshi's Island are generally split up into two kinds, those used by Super FX's plotting and those drawn directly by the SNES.

Banks $24, $25, half of $27, and $2D contain uncompressed SNES tile format of graphics, and half of $2E through $3F contain compressed SNES tile graphics.

Banks $26, half of $27, $28, $29, $2A, $2B, $2C, and half of $2E contain Super FX graphics.

The Super FX will take any color you give it and plot at any destination you set. Unlike the SNES, it doesn't have a "native format" for its graphics that you have to follow. As a developer you can lay out the graphics any way you please. There are certainly more optimal ways than others, but regardless, this article discusses the way Yoshi's Island in particular does it.

Super FX graphics are laid out in blocks of $2000 (8192) bytes. Each of the blocks makes up two full 16-color 256 x 32 bitmaps, starting at top left and going right first, then down (row major). The two bitmaps are interleaved in that the high nibble of every single byte corresponds to one of the 256 x 32 bitmaps (we'll call it the high bitmap), and the low nibble of every byte makes up the low bitmap.

So, as an example, if you were reading through byte by byte:

[0, 0 high] [0, 0 low] | [1, 0 high] [1, 0 low] | .... | [255, 0 high] [255, 0 low] [0, 1 high] [0, 1 low] | ....

The fact of it being 256 wide means, if you are drawing a 32 x 32 graphic, to get to the next row of pixels, you will need to add 256 ($100) then continue reading.

The reason this was done can be seen in the following snippet of Super FX plotting (drawing) code:

0008:8534 70           merge
0008:8535 0D 0E        bcs   8545
0008:8537 1E 56        add   r6

In this snippet, merge is placing the source Y coordinate into the high byte of r0 and the source X coordinate into the low byte of r0, then adds r0 to the address that begins the particular graphic being drawn (r6). In this way, we are indexing essentially gfx[256 * y + x] in only two lines of code. In a tight loop like this, that is a nice optimization.

The method Yoshi's Island employs for designating which bitmap to use when drawing (low or high nibble) resides in the address of the graphic used. As a scenario, Hookbill the Koopa's default head graphic begins at $29E0C0 in ROM. Of course, since the graphics are interleaved, technically in the same exact space as Hookbill's head is also another graphic; in this case it is the Yoshi block (hit it to revert back to normal Yoshi after a transformation). Hookbill's head resides in the high nibble, while Yoshi block is in the low nibble. So how does the game decide between the two? The answer lies in this code snippet (same drawing routine as above code):

0008:8515 2C 10        move  r0,r12      ;
0008:8517 03           lsr               ; >> 1 to test bit 0
0008:8518 0C 04        bcc   851E        ;
0008:851A 50           add   r0          ; << 1: clear bit 0 and put back
0008:851B 21 3E C4     or    #04         ; bit 0 is flag for high nibble of color data
0008:851E B1 3D 4E     cmode             ;

The game checks bit 0 of the address of the graphic, then resets bit 0 to 0 either way. More accurately, the word passed into r12 is laid out like:

a = address of graphic
b = 1 for high, 0 for low

So, bit 0 is the key. 1 means it is the high bitmap, and 0 is low. From our aforementioned scenario, using $E0C0 in code would result in the Yoshi block graphic, whereas $E0C1 would result in Hookbill's head. It's important to note that the 1 is acting as "I am the high bitmap", NOT as part of the address, i.e. the graphic does not actually start at $E0C1, but $E0C0 instead either way.