Learn Mips Assembly Programming...

Platform Specific Series

In this series we'll take a look at hardware related tasks

Lesson P1 - Joystick Reading on the N64
lets look at reading from the joystick on the N64

N64_JoystickExample.asm


Reading the joystick

We're going to read in from the Joystick ports.
We'll load in 64 bytes from the serial controller - there are 8 bytes which will be returned from each of the four joysticks.
In the screenshot shown UDLR have been pressed on all four controllers

Each controller returns the button states and analog data
The N64 controllers are connected via the so called 'PIF' chip (Peripheral Interface?)
The base of the PIF registers is at address 0xBFC007C0

To initialize things we first write 0x8 to the status register at 0xBFC007FC
We're going to use the serial interface (SI)  to send data to the PIF,
This is basically a DMA copy, which sends a block of data.

We need to prepare a 96 byte block of data to send to the PIF to initialize things.

We also define 8 bytes to get back the results when we do a read!
OK we need to use the SI to Send the INIT code to the PIF
The SI registers are at 0xA4800000+

We need to calculate our source address (PIF_Init) - but we need the true 'hardware address' we can get this by ANDing with 0x1FFFFFFF
We send the source address to 0xA4800000 (SI_DRAM_ADDR_REG)

We want to transfer to the PIF ram at 0xBFC007C0 (PIF_RAM_START) - we also AND this with 0x1FFFFFFF
We write the destination to the write register at 0xA4800010 (SI_PIF_ADDR_WR64B_REG) - this write also starts the transfer
OK Let's actually get the keypresses from the joypads!
This time we use the SI to READ from the PIF.

We need to calculate our destination address (PIF_JoyState) - but we need the true 'hardware address' we can get this by ANDing with 0x1FFFFFFF
We send the destination address to 0xA4800000 (SI_DRAM_ADDR_REG) - this is the same as the address for writing

This time we want to transfer FROM to the PIF ram at 0xBFC007C0 (PIF_RAM_START) - we also AND this with 0x1FFFFFFF
We write the source to the read register 0xA4800004 (SI_PIF_ADDR_RD64B_REG) - this write also starts the transfer
As a test we dump with our memdump sub

There are 8 bytes returned, the first 4 are status, the next 4 are directions and buttons in the format:

%ABZSUDLR --LRUDLR XXXXXXXX YYYYYYYY

AB - A/B Buttons
ZLR  - Shoulder buttons / Triggers
UDLR - Digital Directions
UDLR - C Pad
XXXXXXXX YYYYYYYY - Analog stick
S - Start
The SI makes getting the directions easy!

We're defining 'PIF_JoyState' in our ROM, but it's changed during the program, that's because the ROM is actually copied to RAM at execution time by the firmware, so it's actually changeable at run time!


Lesson P2 - Joystick Reading on the PSX
lets look at reading from the joystick on the Playstation

PSX_JoystickExample.asm


Reading the joystick via the Bios

The easiest way to read in the joypad is via the Bios functions.

Here we're pressing the buttons on Joypad 1, and showing the results to the screen.


In theory Joypad 2 should be shown as well, but it seems maybe there's an issue with our emulator which stops them working.
We're going to execute the bios functions at address 0xB0 (the so called 'B functions')
We need to call this with a function number in T1 - Here we use 0x12 which is 'Init Pad'
This takes 4 parameters in A0-A3

A0 is the address of the buffer for joypad 1, A1 is the size (Should be 0x22)
A2 is the address of the buffer for joypad 2, A3 is the size (Should be 0x22)
Once we have Initialized the joypad we can start the read process.

We use Bios function 0x13 'Startpad' - which takes no parameters.

This will automatically fill the buffer with the joypad data.
Here is a crude test program, to read in the raw joypad data and show it to the screen!

The direction buttons have the following bits
Bit 7 6 5 4 3 2 1 0
Byte 1 L D U R Start R3 L3 Select
Byte 2 Square Cross Circle Triangle R1 L1 R2 L2


Reading the joystick Directly!

If we're feeling masochistic we can read from the hardware directly!
We'll need to use various hardware ports to control the hardware.

0x1F801040  JOY_DATA
0x1F801044  JOY_STAT
0x1F801048 JOY_MODE
0x1F80104A JOY_CTRL
0x1F80104E JOY_BAUD
We point T1 to the base address of the controller ports.

First we reset the control port, via 0x1F80104A

We have a small delay to ensure the hardware has time to process the change.
Next we set the Baud rate and mode

We then select  a joystick via the CTRL port.
We send and receive a total of 9 bytes from the joypad

We need to send two init bytes to the control port... 0x01 and 0x42

We use JoyWait to wait for a reply, and MonitorJoy to show the returned data and status flags.
JoyWait will read in from the status port, and wait for bit 1 to equal one, this implies a response from the joypad.

MonitorJoy is for our testing, it shows the returned data in A0, and status byte in A1 to the screen.
We can now read back the data bytes.

We repeatedly send a 0x0 byte, and read back the response, this will be a set of digital buttons or an analog axis.




Lesson P3 -NativeSprite on the PSX
Lets look at 'NativeSprite' on the Playstation. It allows us to use hardware sprites in a multiplatform way! On this system we use XOR 'software' sprites.

PSX_V1_NativeSprite.asm


What Is NativeSprite?

Nativesprite allows us to use platform specific sprite capabilities, to form 'objects' (grids of sprites which can be used in the same way on all systems.

This allows us to write a multiplatform program, but gain the benefits of the hardware capabilities.

Nativesprite splits the job of drawing sprites into three parts.
Part 1 is multiplatform, it is a list of NativeSprite objects with X,Y co-ordinates (in logical units - Pairs of Pixels)

Our game can change the co-ordinates and sprite object pointers to change in-game graphics in a multiplatform way.


With ChibiVM, the 16 bit pointer is a 'numbered entry' in the AddressRemapTable. This is to allow 16 bit ChibiVM to address the 32 bit address space of the MIPS

Part 2 is platform specific, and defines the sprite pattern grid,
This defines how the object is made up of hardware sprite patterns.
It also offers platform specific functions, like sprite scaling or palettes where available.

On systems where no Hardware sprites are available, XOR software sprites are used (made up of 8x8 tiles)... XOR is used because it means we don't need to worry about redrawing the background.

On these XOR systems, we define a 16 bit 'pattern number' for each 8x8 pixel 'tile', which is used to calculate an offset within the bitmap pattern data.


Part 3 is also platform specific.
This is the bitmap pattern data used to draw the sprite, in the format of the hardware sprites or screen memory.



NativeSprite
We need to define some memory for our variables.
nativespriteaddr is the address of the tile pattern bitmap data

nativespriteactivebuffer is the settings of the current sprites which have been drawn to the screen, we need this to 'remember' what sprites are there, so we can remove them if they move or change.

Because we're using XOR, we just 'redraw' a sprite in it's old position to remove it.

On the PSX we cannot access VRAM directly so we must transfer an 8x8 block FROM vram to regular RAM, XOR it and copy it back to VRAM, we use NativeSprite_XORbuffer for this 'temporary store
NativeSpr_Init will prepare the system for drawing sprites.

On hardware sprite systems, we transfer patterns to VRAM

On the XOR sprite systems we just remember the address we were passed, which contains the bitmap 'tile pattern' data.
NativeSpr_DrawArray draws the array of sprite objects defined in Part1

The Co-ordinates of the sprite (in pairs of pixels) and the pointer to the sprite object (part 2)

There are two versions:

NativeSpr_DrawArrayReiKou is used with ChibiVM, and uses the AddressRemapTable to convert a 16 bit pointer to 32 bits.

NativeSpr_DrawArray uses a 32 bit pointer... If you're using NativeSprite on it's own, this is the one you would use.



On XOR based systems nativespriteactivebuffer keeps a copy of the positions and sprite objects drawn to the screen, this is used to remove a sprite (VIA XOR - simply redrawing it in the same position) if the position or object changes.

We use NativeSpr_DrawOne to draw the sprite to the screen.



NativeSpr_TestOne Checks the 'ActiveBuffer' to see if the XOR sprite is already drawn to the screen at this position

If it is it doesn't need redrawing

If it has moved or changed, we remove the old sprite (by XOR drawing it in the same position) then draw the new sprite.
Here is the correct Object definition for the XOR systems

The Width and Height in patterns is defined in the first two words.

On these XOR systems, we define a 32 bit 'pattern number' for each 8x8 pixel 'tile', which is used to calculate an offset within the bitmap pattern data.
NativeSpr_drawone will load the parameters of the sprite object to draw loading the X,Y position into S1,S4,and the object definition address into S5
NativeSpr_DrawExtra will draw object S5 at position S1,S4


We load in the Width and height of the object (in 8x8 pattern tiles)

We load in the pattern number, and see if it's 0xFFFFFFFF (Which is an 'empty pattern'


We convert the X,Y co-ordinate from 'Logical units' (pairs of pixels making a centered virtual screen of 128,96 from co-ordinate 64,80) to a screen co-ordinate in pairs of pixels


We then calculate the source pattern address for the tile patterns. On these systems each pattern is 128 bytes.


On the Playstation we can't access VRAM directly.

We send commands to 0x1F8001810 to get the video hardware to work for us.

We have to use command 0xC0 'Get Image from Framebuffer' to transfer an 8x8 block into RAM (NativeSprite_XORbuffer)

We then XOR the source pattern into the buffer.

Finally we write it back to screen with command 0xA0 'Send image to Framebuffer'

We repeat for the next tile on the line, and the other lines that make up this 'spriteobject'

drawnative_overrow Handles when part of our spriteobject has gone off the right of the screen.

drawnativeskipx Handles when part of the spriteobject is off the left of the screen, or contains empty patterns
NativeSpr_HideAll will remove all the hardware sprites from the screen, and should be used for things like title screens. It should also be used before redrawing the background with XOR sprites.

With XOR sprites, we walk through the ActiveSpriteBuffer, redrawing each sprite to the screen, which effectively removes it (as we're using XOR)





Lesson P4 - NativeSprite on the N64
Lets look at 'NativeSprite' on the N64. It allows us to use hardware sprites in a multiplatform way! On this system we use XOR 'software' sprites.

N64_V1_NativeSprite.asm


What Is NativeSprite?

Nativesprite allows us to use platform specific sprite capabilities, to form 'objects' (grids of sprites which can be used in the same way on all systems.

This allows us to write a multiplatform program, but gain the benefits of the hardware capabilities.

Nativesprite splits the job of drawing sprites into three parts.
Part 1 is multiplatform, it is a list of NativeSprite objects with X,Y co-ordinates (in logical units - Pairs of Pixels)

Our game can change the co-ordinates and sprite object pointers to change in-game graphics in a multiplatform way.


With ChibiVM, the 16 bit pointer is a 'numbered entry' in the AddressRemapTable. This is to allow 16 bit ChibiVM to address the 32 bit address space of the MIPS

Part 2 is platform specific, and defines the sprite pattern grid,
This defines how the object is made up of hardware sprite patterns.
It also offers platform specific functions, like sprite scaling or palettes where available.

On systems where no Hardware sprites are available, XOR software sprites are used (made up of 8x8 tiles)... XOR is used because it means we don't need to worry about redrawing the background.

On these XOR systems, we define a 16 bit 'pattern number' for each 8x8 pixel 'tile', which is used to calculate an offset within the bitmap pattern data.


Part 3 is also platform specific.
This is the bitmap pattern data used to draw the sprite, in the format of the hardware sprites or screen memory.



NativeSprite
We need to define some memory for our variables.
nativespriteaddr is the address of the tile pattern bitmap data

nativespriteactivebuffer is the settings of the current sprites which have been drawn to the screen, we need this to 'remember' what sprites are there, so we can remove them if they move or change.

Because we're using XOR, we just 'redraw' a sprite in it's old position to remove it.
NativeSpr_Init will prepare the system for drawing sprites.

On hardware sprite systems, we transfer patterns to VRAM

On the XOR sprite systems we just remember the address we were passed, which contains the bitmap 'tile pattern' data.
NativeSpr_DrawArray draws the array of sprite objects defined in Part1

The Co-ordinates of the sprite (in pairs of pixels) and the pointer to the sprite object (part 2)

There are two versions:

NativeSpr_DrawArrayReiKou is used with ChibiVM, and uses the AddressRemapTable to convert a 16 bit pointer to 32 bits.

NativeSpr_DrawArray uses a 32 bit pointer... If you're using NativeSprite on it's own, this is the one you would use.



On XOR based systems nativespriteactivebuffer keeps a copy of the positions and sprite objects drawn to the screen, this is used to remove a sprite (VIA XOR - simply redrawing it in the same position) if the position or object changes.

We use NativeSpr_DrawOne to draw the sprite to the screen.



NativeSpr_TestOne Checks the 'ActiveBuffer' to see if the XOR sprite is already drawn to the screen at this position

If it is it doesn't need redrawing

If it has moved or changed, we remove the old sprite (by XOR drawing it in the same position) then draw the new sprite.
Here is the correct Object definition for the XOR systems

The Width and Height in patterns is defined in the first two words.

On these XOR systems, we define a 32 bit 'pattern number' for each 8x8 pixel 'tile', which is used to calculate an offset within the bitmap pattern data.
NativeSpr_drawone will load the parameters of the sprite object to draw loading the X,Y position into S1,S4,and the object definition address into S5
NativeSpr_DrawExtra will draw object S5 at position S1,S4


We load in the Width and height of the object (in 8x8 pattern tiles)

We load in the pattern number, and see if it's 0xFFFFFFFF (Which is an 'empty pattern'

We convert the X,Y co-ordinate from 'Logical units' (pairs of pixels making a centered virtual screen of 128,96 from co-ordinate 64,80) to a screen co-ordinate in pairs of pixels




Next we calculate the VRAM Destination.
Each line on the N64 screen is 320 pixels, and each pixel is 2 bytes.


We also calculate the source pattern address for the tile patterns. On these systems each pattern is 128 bytes.
We read 4 bytes (2 pixels) from the source pattern, xor them with the screen contents, and write back to the screen.

We do this 4 times to write a full line, then move down a line (640 bytes - the 16 we just wrote)


We repeat for the next tile on the line, and the other lines that make up this 'spriteobject'

drawnative_overrow Handles when part of our spriteobject has gone off the right of the screen.

drawnativeskipx Handles when part of the spriteobject is off the left of the screen, or contains empty patterns
NativeSpr_HideAll will remove all the hardware sprites from the screen, and should be used for things like title screens. It should also be used before redrawing the background with XOR sprites.

With XOR sprites, we walk through the ActiveSpriteBuffer, redrawing each sprite to the screen, which effectively removes it (as we're using XOR)




Lesson P5 - Multiplatform Bitmap on the PSX
'Multiplatform Bitmap' is a routine that allows us to work with pixels, or 8x8 pixel 'tiles' (In bitplane format) in a common way on all systems.

It supports up to 16 colors (4 bitplanes). While not allowing us to use the full functionality of the hardware, it allows us to use the same graphics routines and source data on every system supported by the routine!


PSX_V1_MultiplatformBitmap.asm


What Is Multiplatform Bitmap?

Multiplaform bitmap allows us to draw graphics in a common way, whatever the capabilities of the system!

It's intended for low speed drawing or real-time calculated drawing, like drawing title screens, or graphs, or even making a sprite editor or other graphics package!

There are 4 functions provided:

mpbitmap_setpixel - Set a pixel to a color (0-15)
mpbitmap_getpixel - Reads a pixel from the screen

mpbitmap_settile - Set an 8x8 pixel tile block (in bitplane format, 1-4 bitplanes)
mpbitmap_gettile - Get an 8x8 pixel tile block from the screen (in bitplane format, 1-4 bitplanes)

Pixel Commands

mpbitmap_setpixel draws a pixel to the screen

The color to set is specified in S0
The Y position in pixels is specified in S5

The Low byte of the X position is specified in S2
The High byte of the X position is specified in S6
(The X position is split into two registers to maintain functional compatibility with the Z80 Version!)

To write a pixel to the screen we need to use video command '0xA0' - send image to framebuffer

We use the GP0 command port 0x1F801810 to define the command, XY pos, and image size

The 'image' in this case is a single 1x1 pixel!!!

We convert our numbered pixel (0-15) into the native screen format (%-BBBBBGGGGGRRRRR)
We then send it to the screen in the low 16 bits of the word written to the command port
The mpbitmap_getpixel function is almost the same, we use command '0xC0' to read back a byte from the screen.

We use mpbitmap_getpixel_FindColor to convert the 16 bits read from the screen (in %-BBBBBGGGGGRRRRR format) into a color number (0-15) ... we do this for compatibility with the other systems (like the Z80 and 6502, were even 16 colors are a luxury!)

mpbitmap_getpixel_FindColor will take a 16 bit value read from the screen, and convert it to a numbered color using the 'PSX_Palette'

We seek entries in the palette, until we find a match, or go through all 16 colors.

The 16 color limit is to maintain compatibility with the other systems Multiplatform Bitmap supports.

Tile Commands

The 'mpbitmap_settile' command will take an 8x8 block of data in bitplane format, and draw it to the screen.
Up to 4 bitplanes are supported, meaning 16 colors, 32 bytes for the full tile.

We start by selecting the VRAM destination using GPU command 0xA0.


Next we load in the bitplanes we've been provided with, The source data may be less than 4 bitpanes, so we load any others from the default 'MpBitmap_TileTints'

To calculate the color number we take the most significant bit from each bitplane byte.

We use this as a lookup in our palette, giving us a 16 bit screen-format color.

We want to write this to the screen, but we need two pixels of data before we do, so we build them up into a3, until we have a pair, then send them to the screen
mpbitmap_gettile will read pixel data back from the screen, returning 1-4 bitplanes as required.


We read data back from the screen using the 0xC0 command. Each read must load 2 pixels, so we do one read every two pixels.


We load in 8 pixels, convert the colors, and shift the bitplane bits into 4 bytes.


We store the data back to address S6, writing back as many bitplanes as we were asked to (originally specified by S0 - now in A3)




Lesson P6 - Multiplatform Bitmap on the N64
'Multiplatform Bitmap' is a routine that allows us to work with pixels, or 8x8 pixel 'tiles' (In bitplane format) in a common way on all systems.

It supports up to 16 colors (4 bitplanes). While not allowing us to use the full functionality of the hardware, it allows us to use the same graphics routines and source data on every system supported by the routine!


N64_V1_MultiplatformBitmap.asm


What Is Multiplatform Bitmap?

Multiplaform bitmap allows us to draw graphics in a common way, whatever the capabilities of the system!

It's intended for low speed drawing or real-time calculated drawing, like drawing title screens, or graphs, or even making a sprite editor or other graphics package!

There are 4 functions provided:

mpbitmap_setpixel - Set a pixel to a color (0-15)
mpbitmap_getpixel - Reads a pixel from the screen

mpbitmap_settile - Set an 8x8 pixel tile block (in bitplane format, 1-4 bitplanes)
mpbitmap_gettile - Get an 8x8 pixel tile block from the screen (in bitplane format, 1-4 bitplanes)

Pixel Commands

We need to calculate VRAM destinations.

Screen memory starts at 0xA0100000,

Each pixel is 2 bytes in the format %RRRRRGGGGGBBBBBB-
Each line is 320 pixels.

We have two routines to do this

mpbitmap_GetScreenPos works in pixels
mpbitmap_GetScreenPosTile works in 8x8 tile blocks

they also load A0 with the address of the palette (N64_Palette)

mpbitmap_setpixel draws a pixel to the screen

The color to set is specified in S0
The Y position in pixels is specified in S5

The Low byte of the X position is specified in S2
The High byte of the X position is specified in S6
(The X position is split into two registers to maintain functional compatibility with the Z80 Version!)


We convert our numbered pixel (0-15) into the native screen format %RRRRRGGGGGBBBBBB-  using our palette lookup

We then write it to the screen.
The mpbitmap_getpixel function works in reverse.

We load the pixel from the screen, We then use mpbitmap_getpixel_FindColor to convert the 16 bits read from the screen (in %RRRRRGGGGGBBBBBB- format) into a color number (0-15) ...

We do this for compatibility with the other systems (like the Z80 and 6502, were even 16 colors are a luxury!)

mpbitmap_getpixel_FindColor will take a 16 bit value read from the screen, and convert it to a numbered color using the 'N64_Palette'

We seek entries in the palette, until we find a match, or go through all 16 colors.

The 16 color limit is to maintain compatibility with the other systems Multiplatform Bitmap supports.

Tile Commands

The 'mpbitmap_settile' command will take an 8x8 block of data in bitplane format, and draw it to the screen.
Up to 4 bitplanes are supported, meaning 16 colors, 32 bytes for the full tile.

We start by calculating the VRAM destination.


Next we load in the bitplanes we've been provided with, The source data may be less than 4 bitpanes, so we load any others from the default 'MpBitmap_TileTints'

To calculate the color number we take the most significant bit from each bitplane byte.

We use this as a lookup in our palette, giving us a 16 bit screen-format color.

We write the 16 bit value to the screen (1 pixel).

At the end of each line we add 320*2 to the VRAM position, as each line is 320 pixels, and 2 bytes per pixel
mpbitmap_gettile will read pixel data back from the screen, returning 1-4 bitplanes as required.


We read data back from the screen, converting it via mpbitmap_getpixel_FindColor


We load in 8 pixels, and shift the bitplane bits into 4 bytes.


We store the data back to address S6, writing back as many bitplanes as we were asked to (originally specified by S0 - now in A3)




Lesson P7 - MaxTile Software Tilemap on the N64
Lets take a look at the advanced 'Maxtile' tilemap on the n64. it supports Xflip,Yflip, Fastfill and more!

This version supports 'native' 16 bit color tiles, and 'common format' 2 bitplane tiles


N64_V1_MaxTile.asm


MaxTile Definitions

Maxtile offers X-flip,Y-flip, XY flip, Doubleheight and filled tiles.

It uses software drawing without needing a double buffer.



This version supports 'native' 16 bit color tiles (128 bytes per tile - in a format exclusive to the N64)
and 'common format' 2 bitplane tiles (16 bytes per tile - in a format which is supported by all systems in my tutorials

The screen is 256x192, which is 128x96 in logical units.

We define the screen size, and memory pointers for the various variables of the test routine.
When we want to draw a sprite object to the screen, we need to calculate the VRAM destination.

MaxTile uses X,Y co-ordinates in 'Logical Units' (Pairs of pixels) - this is passed in S1 and S4, and the Vram destination is returned in S6

Each pixel is 2bytes, Each line is 320 bytes.

The screen base is defined by AdrScreenBase.

Tile Drawing!
The DrawTile Routine is called by the shared code, This shared code will load the first byte of the 16 bit tile number into R0

The low bit is shifted out (the update bit)... we need to reset it to 0 anyway!

The following registers are loaded:
S4=Tilemap
S5=Tile Bitmap Pattern data
S6=VRAM Destination

To optimize things, the tile drawing works as a 'binary tree', deciding the kind of tile drawing routine to use

The Platform specific draw routine DrawTile starts by ckecking Bit 1 (now Bit 0)
This is the 'Program' flag - If this is 0, then this is the simplest unflipped tile, otherwise we switch to the advanced routine.

If we're drawing a basic tile, we shift a 0 back into S0, and write it back, that clears the Update flag, as we will draw the tile now.
We're going to draw a basic unflipped tile

We need to load the second byte of the tilenumber, and add it to R1 (the pattern data)

If we're using true color patterns, we need to bit shift to the left. to multiply by 128

We backup the screenpos into S3, so we can restore it after drawing the tile, and load the line count into T2

We're ready to draw our tile!
If we're using 2 bitplane format, we load in a pair of bytes, this has 'enough bits' for one screen line, so we shift the bits out into a byte one pixel at a time

We need to convert this color 0-3 into a screen 16 bit value, so we do this via a Palette lookup



In native format, the source data is in screen format, so we just copy four 32 bit words from the source to the screen.
If the Program bit was 1, then either we need to flip, or run some 'custom code'

Next we check the XY bits, if both are 0 this is not a flip (Transparent, Filled, Double etc)

If either bit (or both) is 1, then this is some kind of flip, so we calculate the pattern source address, and move it into S1.
We also backup the VRAM address into S3
If the Y flip bit is 0 we must be X flipping!

If using 2 bitplane mode, we shift the bits out the Right of the source bytes, and draw them left to right.



If using 16 color mode we read Left -> right, but draw right to left.

If the Y flip bit was set we now check if the X flip bit is also set.

If it's not we're just Yflipping!

To flip vertically we start from the last line of the source data, and move up after each line is drawn.

We draw top to bottom, but read bottom to top!
if the X bit was set as well as Y we need to XY flip.

We do this with a combination of both 'tricks'


MaxTile Custom Draw Types

Bits 1,2,3... XYP=%001 defines a custom program

When a custom program is being used Bits 4,5 define the program type (rather than part of the low tile number)

%00=Filled Tile
%01=Double height tile
%10=unused
%11=Transparent tile/empty tile

The remaining two bits 6,7 act as the High part of the tile number %------NN nnnnnnnn
A double height tile uses only 4 lines of a pattern,

We then draw each line of the pattern twice to the screen,

We do two lines per loop, repeating 4 times
The fill tile is the simplest!

We load a color from the source data, and draw it to every pixel of the tile.

As so little data is read, This routine is the fastest, so should be used for as much as possible of our tilemap!


In 2 Bitplane mode, we repeat two lines, this is to allow for 'checkerboard' pattern fills.
The final type is the Transparent tile.

if the Tilenumber=255 then this tile is completely transparent, and no data will be drawn
Our transparency is a crude '0 byte' transparency.

Basically, any 16 bit word equal to 0 is not drawn to the screen, others are drawn normally.


More impressive transparency can be achieved via a LUT and a combination of AND/OR, however this simple transparency is fast, and tends to give a nice 'cartoon' effect with black outlines to characters





Lesson P8 - MaxTile Software Tilemap on the PSX
Lets take a look at the advanced 'Maxtile' tilemap on the PSX, it supports Xflip,Yflip, Fastfill and more!

This version supports 'native' 16bit color tiles, and 'common format' 2 bitplane tiles


PSX_V1_MaxTile.asm


MaxTile Definitions

Maxtile offers X-flip,Y-flip, XY flip, Doubleheight and filled tiles.

It uses software drawing without needing a double buffer.



This version supports 'native' 16 bit color tiles (128 bytes per tile - in a format exclusive to the PSX)
and 'common format' 2 bitplane tiles (16 bytes per tile - in a format which is supported by all systems in my tutorials

The screen is 256x192, which is 128x96 in logical units.

We define the screen size, and memory pointers for the various variables of the test routine.
To draw to the screen we need to send the data to VRAM, but we can't do this directly

We have to define an 'area' that the we're going write, and then send the data the graphics hardware that will fill that area.

MaxTileSelectVRAM does this for us
When we want to draw a sprite object to the screen, we usually need to calculate the VRAM destination.

MaxTile uses X,Y co-ordinates in 'Logical Units' (Pairs of pixels) - this is passed in S1 and S4, and the Vram destination is returned in S6

To maintain compatibility, the PSX also has a version of this routine, however this time we just combine the X and Y pos in pixels into S6



Tile Drawing!
The DrawTile Routine is called by the shared code, This shared code will load the first byte of the 16 bit tile number into R0

The low bit is shifted out (the update bit)... we need to reset it to 0 anyway!

The following registers are loaded:
S4=Tilemap
S5=Tile Bitmap Pattern data
S6=VRAM Destination

To optimize things, the tile drawing works as a 'binary tree', deciding the kind of tile drawing routine to use

The Platform specific draw routine DrawTile starts by ckecking Bit 1 (now Bit 0)
This is the 'Program' flag - If this is 0, then this is the simplest unflipped tile, otherwise we switch to the advanced routine.

If we're drawing a basic tile, we shift a 0 back into S0, and write it back, that clears the Update flag, as we will draw the tile now.

Whatever happens, we'll probably need to call MaxTileSelectVRAM, so we load it's address in t6
We're going to draw a basic unflipped tile

We need to load the second byte of the tilenumber, and add it to R1 (the pattern data)

If we're using true color patterns, we need to bit shift to the left. to multiply by 128

We're ready to draw our tile!
If we're using 2 bitplane format, we load in a pair of bytes, this has 'enough bits' for one screen line, so we shift the bits out into a byte one pixel at a time

We need to convert this color 0-3 into a screen 16 bit value, so we do this via a Palette lookup



In native format, the source data is in screen format, so we just copy four 32 bit words from the source to the screen.
If the Program bit was 1, then either we need to flip, or run some 'custom code'

Next we check the XY bits, if both are 0 this is not a flip (Transparent, Filled, Double etc)

If either bit (or both) is 1, then this is some kind of flip, so we calculate the pattern source address, and move it into S1.
We also backup the VRAM address into S3
If the Y flip bit is 0 we must be X flipping!

If using 2 bitplane mode, we shift the bits out the Right of the source bytes, and draw them left to right.



If using 16 color mode we read Left -> right, but draw right to left.

If the Y flip bit was set we now check if the X flip bit is also set.

If it's not we're just Yflipping!

To flip vertically we start from the last line of the source data, and move up after each line is drawn.

We draw top to bottom, but read bottom to top!
if the X bit was set as well as Y we need to XY flip.

We do this with a combination of both 'tricks'


MaxTile Custom Draw Types

Bits 1,2,3... XYP=%001 defines a custom program

When a custom program is being used Bits 4,5 define the program type (rather than part of the low tile number)

%00=Filled Tile
%01=Double height tile
%10=unused
%11=Transparent tile/empty tile

The remaining two bits 6,7 act as the High part of the tile number %------NN nnnnnnnn
A double height tile uses only 4 lines of a pattern,

We then draw each line of the pattern twice to the screen,

We do two lines per loop, repeating 4 times



The fill tile is the simplest!

We load a color from the source data, and draw it to every pixel of the tile.

As so little data is read, This routine is the fastest, so should be used for as much as possible of our tilemap!


In 2 Bitplane mode, we repeat two lines, this is to allow for 'checkerboard' pattern fills.
The final type is the Transparent tile.

if the Tilenumber=255 then this tile is completely transparent, and no data will be drawn
Our transparency is a crude '0 byte' transparency.

Basically, any 16 bit word equal to 0 is not drawn to the screen, others are drawn normally.


Because we can't directly access VRAM, we have to read the tile area from the screen into a buffer first (MaxTile_PSXbuffer)

We then alter that buffer, and write the changed data back to the screen



More impressive transparency can be achieved via a LUT and a combination of AND/OR, however this simple transparency is fast, and tends to give a nice 'cartoon' effect with black outlines to characters




Lesson P9 - MaxTile Helper Routines
Maxtile needs some helper routines which will transfer data to the tilemap, and initialize the tile draw procedure.


V1_MaxTile.asm



Note that these routines are designed to work with ChibiVM. They work 100% the same as they do on the Z80, This means in many cases 16 bit values may be split into two 8 bit parts. This is to ensure multiplatform support

A Maxtile tile is defined by 16 bits in the Little Endian format: %NNNNNNNN NNNNXYPU
N=tile Number
X=Xflip Y=Yflip P=Program flag (flip + custom tiles) U=Update Flag

Helper Routines

Setscroll sets the X,Y offset for drawing, it can also optionally totally change the base position of the source tilemap (For moves of many tiles)

The bottom 2 bits of the X,Y offset are used for 'Partial tile shifts' (2 pixel shifts) for relatively smooth scrolling
GetTileByteFromHL Loads a tile number from the address R6 (in the tilemap)

12 of the 16 bits in this are the tile number (4 are for flip and other options), but this routine converts it to a single 8 bit byte.

This is intended to allow for easy 'tile comparison' on 8 bit systems, but limits you to up to 256 tiles.
maxtileBcLsr Performs a number of bitshifts on a 16 bit pair in registers R1 (B) R4 (C)

These work with two 8 bit values, as they are used by ChibiVM to bitshift it's 8 bit registers quickly
ShifTilemap Returns the memory address of a tile (16 bit pair) according to the X Y position in R1,R4

The XY position is measured in TILES (8x8 pixel blocks)
ShifTilemapLU Returns the memory address of a tile (16 bit pair) according to the X Y position in R1,R4

The XY position is measured in Logical Units (2x2 pixel blocks)

Logical units are also used by NativeSprite, so allow for easy comparison between a sprite position, and the underlying tile map.

MaxTileFlagForRedraw Flags part of the tilemap for redrawing (Setting Bit 0 of the little endian 16 bit tile pairs)

This ensures the flagged tiles are redrawn when the tilemap is next 'drawn'
MaxTilePrint Works like a 'PrintString' routine.

A 2 dimentional sequence of one byte 'tiles' is 'printed' to the tilemap, each line is 255 terminated, and the whole sequence s 255 terminated.

While the left side of the area will be aligned, the lines can be different lengths, it's intended for printing a 'paragraph' of text to the tilemap.

Although each tile can only be defined by a value of 0-254, an 'offset' is added to this number before writing to the tilemap, allowing for options like  X/Y flip to even be used.
MaxTileDrawArea Draws a square grid of tiles from a source tilemap to the main tilemap

Unlike 'Print' routines, this uses full 16 bit tiles  and always works in a square area.
MaxTilePrintArea Draws a square grid of tiles from a source tilemap to the main tilemap

Unlike 'MaxTileDrawArea' routines, this uses 8 bit source tiles, with a 16 bit offset (like MaxTilePrint)

This is intended to save memory by halving the size of the source tilemap.


Note This version loads some of it's parameters directly from ChibiVM registers R8-R11 (Zeropage 16+)

MaxTileFillArea Fills an area of the tilemap with a single 16 bit tile.


Draw Routine

MaxTileRedraw will redraw any changed tiles to the screen (Based on the Update Flag.


First we load in the address of the Tilemap, and the bitmap pattern data.

We also load in the width of the tilemap in bytes (usually 36x2)


We check the 'partial tile shift' - Our draw routines can only draw whole tiles, so if we need to shift, we do this by adding to the draw position.
This means the edges of the screen may not be properly drawn, but the solution to this is to draw a 'border' covering 6 pixels of the edges of the screen, which hides these incomplete tiles.
The Draw routine moves through the tilemap, checking bit 0 of each tile (The Update Flag) and running drawtile if the bit was set

We process each line of the tilemap, then move down to the next line (in the tilemap, and of course also the screen) and repeat





 

View Options
Default Dark
Simple (Hide this menu)
Print Mode (white background)

Top Menu
***Main Menu***
Youtube channel
Patreon
Introduction to Assembly (Basics for absolute beginners)
Amazon Affiliate Link
AkuSprite Editor
ChibiTracker
Dec/Bin/Hex/Oct/Ascii Table

Alt Tech
Archive.org
Bitchute
Odysee
Rumble
DailyMotion
Please note: I wlll upload more content to these alt platforms based on the views they bring in

Z80 Content
***Z80 Tutorial List***
Learn Z80 Assembly (2021)
Learn Z80 Assembly (old)
Hello World
Simple Samples
Advanced Series
Multiplatform Series
Platform Specific Series
ChibiAkumas Series
Grime Z80
Z80 Downloads
Z80 Cheatsheet
Sources.7z
DevTools kit
Z80 Platforms
Amstrad CPC
Elan Enterprise
Gameboy & Gameboy Color
Master System & GameGear
MSX & MSX2
Sam Coupe
TI-83
ZX Spectrum
Spectrum NEXT
Camputers Lynx

6502 Content
***6502 Tutorial List***
Learn 6502 Assembly
Advanced Series
Platform Specific Series
Hello World Series
Simple Samples
Grime 6502
6502 Downloads
6502 Cheatsheet
Sources.7z
DevTools kit
6502 Platforms
Apple IIe
Atari 800 and 5200
Atari Lynx
BBC Micro
Commodore 64
Commodore PET
Commander x16
Super Nintendo (SNES)
Nintendo NES / Famicom
PC Engine (Turbografx-16)
Vic 20

68000 Content
***68000 Tutorial List***
Learn 68000 Assembly
Hello World Series
Platform Specific Series
Simple Samples
Grime 68000
68000 Downloads
68000 Cheatsheet
Sources.7z
DevTools kit
68000 Platforms
Amiga 500
Atari ST
Neo Geo
Sega Genesis / Mega Drive
Sinclair QL
X68000 (Sharp x68k)

8086 Content
Learn 8086 Assembly
Platform Specific Series
Hello World Series
Simple Samples
8086 Downloads
8086 Cheatsheet
Sources.7z
DevTools kit
8086 Platforms
Wonderswan
MsDos

ARM Content
Learn ARM Assembly
Learn ARM Thumb Assembly
Platform Specific Series
Hello World
Simple Samples
ARM Downloads
ARM Cheatsheet
Sources.7z
DevTools kit
ARM Platforms
Gameboy Advance
Nintendo DS
Risc Os

Risc-V Content
Learn Risc-V Assembly
Risc-V Downloads
Risc-V Cheatsheet
Sources.7z
DevTools kit

MIPS Content
Learn Risc-V Assembly
Platform Specific Series
Hello World
Simple Samples
MIPS Downloads
MIPS Cheatsheet
Sources.7z
DevTools kit
MIPS Platforms
Playstation
N64

PDP-11 Content
Learn PDP-11 Assembly
Platform Specific Series
Simple Samples
PDP-11 Downloads
PDP-11 Cheatsheet
Sources.7z
DevTools kit
PDP-11 Platforms
PDP-11
UKNC

TMS9900 Content
Learn TMS9900 Assembly
Platform Specific Series
Hello World
TMS9900 Downloads
TMS9900 Cheatsheet
Sources.7z
DevTools kit
TMS9900 Platforms
Ti 99

6809 Content
Learn 6809 Assembly
Learn 6309 Assembly
Platform Specific Series
Hello World Series
Simple Samples
6809 Downloads
6809/6309 Cheatsheet
Sources.7z
DevTools kit
6809 Platforms
Dragon 32/Tandy Coco
Fujitsu FM7
TRS-80 Coco 3
Vectrex

65816 Content
Learn 65816 Assembly
Hello World
Simple Samples
65816 Downloads
65816 Cheatsheet
Sources.7z
DevTools kit
65816 Platforms
SNES

eZ80 Content
Learn eZ80 Assembly
Platform Specific Series
eZ80 Downloads
eZ80 Cheatsheet
Sources.7z
DevTools kit
eZ80 Platforms
Ti84 PCE

IBM370 Content
Learn IBM370 Assembly
Simple Samples
IBM370 Downloads
IBM370 Cheatsheet
Sources.7z
DevTools kit

Super-H Content
Learn SH2 Assembly
Hello World Series
Simple Samples
SH2 Downloads
SH2 Cheatsheet
Sources.7z
DevTools kit
SH2 Platforms
32x
Saturn

PowerPC Content
Learn PowerPC Assembly
Hello World Series
Simple Samples
PowerPC Downloads
PowerPC Cheatsheet
Sources.7z
DevTools kit
PowerPC Platforms
Gamecube

Work in Progress
ChibiAndroids

Misc bits
Ruby programming









Buy my Assembly programming book
on Amazon in Print or Kindle!


Buy my Assembly programming book





Available worldwide!
Search 'ChibiAkumas' on
your local Amazon website!
Click here for more info!
















































































































































Buy my Assembly programming book
on Amazon in Print or Kindle!


Buy my Assembly programming book





Available worldwide!
Search 'ChibiAkumas' on
your local Amazon website!
Click here for more info!
















































































































































Buy my Assembly programming book
on Amazon in Print or Kindle!


Buy my Assembly programming book





Available worldwide!
Search 'ChibiAkumas' on
your local Amazon website!
Click here for more info!