Sprites in Assembly I - Setup

One of fundamental tools in the game developer's toolbox are hardware sprites and Commander X16 comes with a very strong and robust support. The hardware takes a big chunk of workload and makes game programming even more feasible. However it can be quite confusing to start with and therefore in this short blog post I will explain how to configure sprites in Assembly.
We will take few basic steps and answer following questions:
  • How to turn on the sprites
  • Sizing and Colors
  • Positioning
  • Storing appearance

Important Addresses

Before we start coding let’s sketch video memory and registers that we will be using. As you probably know, VERA stands for Video Enhanced Retro Adapter and we can simply refer to it as video chip. We have to also understand that video memory is separate to main program memory and only way to access it is through a set of VERA registers.


More details can be found here.


Important VERA memory areas again:

Address Range Description How will we use it
$000000 - $1FFFF Video RAM To store sprite graphics and settings
$1FA00 - $1FBFF Palette We will use default palette in our sprite
$1FC00 - $1FFFF Sprite Attributes To define appearance of our sprites and to point to sprites definition and position on screen

Video RAM

Video RAM is 128K of memory that can be used for display and for storing other video related data that needs to be accessed by VERA. We will use it to store sprite graphics. Remember that Sprites are independent from other displayed data so it is very common to display Sprites over text or tile modes. Let’s remind us how much memory standard text modes use:

SCREEN 0 – 40 x 30 Character mode: It uses 256 bytes per line so total of 7,650 bytes.
SCREEN 2 – 80 x 60 Character mode: it also uses 256 bytes per line for total 15,360 bytes ($3C00 HEX).

So for our purpose if we want to use one of these two text modes we can safely store sprite data anywhere after address $3C00, assuming we only use one layer.

Palette

This area of memory is used to define the available colors for our sprites. We will use default 256 colors available to us and might look into tricks that can be done with it in some future post.

Sprite Attributes

This is the most important part of the video memory related to sprites. Commander X16 supports up to 128 hardware sprites. They all get exactly 8 bytes to get their attributes defined and therefore occupy 1024 bytes in memory area $1FC00 - $1FFFF:

Sprite 0:               $1FC00 - $1FC07
Sprite 1:               $1FC08 - $1FC0F
Sprite 2:               $1FC10 - $1FC17
Sprite 127:           $1FFF8 - $1FFFF

So what are the attributes?


Offset / Bit 7 6 5 4 3 2 1 0
0 Pointer to sprite graphics (bits 5-12)
1 Mode Pointer to sprite graphics (bits 13-16)
2 X Position
3 X Position
4 Y Position
5 Y Position
6 Collision Mask Z Depth V-Flip H-Flip
7 Height Width Palette Offset

We will use and therefore check in more detail the ones color coded in the picture above. Green ones are very straight forward, blue a bit more complicated and the red one is the trickiest and we will therefore spend most time on it.

Before we start…

One more thing before we start coding. In BASIC and C we have VPOKE and VPEEK commands/functions to write to and read  from VRAM. We don't have that in Assembly. Instead we have to use VERA registers directly in order to be able to write to VRAM.
This must seem like disadvantage but in reality VPOKE are very inefficient and the more data we need to transfer to VRAM the more inefficient they become.

Before every byte written to VRAM we have to tell VERA to which address we want to write it. Since VERA has 16K we need 17 bits to address it meaning we have to write 3 bytes to VERA registers before we can even transfer on byte of data. In addition to that we have to tell VERA which Data register will we use. 

Let's for example write Assembly equivalent to VPOKE 0,$4000,255 (BASIC) or vpoke(255,0x4000); (C):

stz $9F25            ; VERA_CTRL set to use DATA0 register
stz $9F22            ; VERA_HIGH bit 16 is set to 0
lda #$40
sta $9F21            ; VERA_MID set to $40
stz $9F20            ; VERA_LOW set to $00

lda #255
sta $9F23            ; Write 255 to VRAM address $04000

As we see there is significant overhead in setting up code for writing to VERA and we were even able to optimize by using stz instead of lda and sta instructions. It is important to remember that every time we call VPOKE from BASIC or vpoke from C those settings have to be updated.

In Assembly, on the other hand, we often need to write more than one byte to VRAM, in fact very often we write hundreds or thousand of bytes in sequence. Here one of VERA's most useful features comes to play. We can configure it in such a way to autoincrement the address at every write or read.

Let's imagine a scenario where we have to write 255 ten times to addresses from $04000 - $04009:

stz $9F25            ; VERA_CTRL set to use DATA0 register
lda #%0001000        ; VERA_HIGH Increment is set to 1
sta $9F22            ; and address bit 16 is set to 0
lda #$40
sta $9F21            ; VERA_MID set to $40
stz $9F20            ; VERA_LOW set to $00

lda #255
sta $9F23            ; Write 255 to VRAM address $04000
sta $9F23            ; Write 255 to VRAM address $04001
sta $9F23            ; Write 255 to VRAM address $04002
sta $9F23            ; Write 255 to VRAM address $04003
sta $9F23            ; Write 255 to VRAM address $04004
sta $9F23            ; Write 255 to VRAM address $04005
sta $9F23            ; Write 255 to VRAM address $04006
sta $9F23            ; Write 255 to VRAM address $04007
sta $9F23            ; Write 255 to VRAM address $04008
sta $9F23            ; Write 255 to VRAM address $04009

I believe this example clearly show why addressing VERA directly might feel a bit more cumbersome in the beginning but the more data we transfer between VRAM and CPU RAM the more efficient it becomes vs BASIC. It has to be said that in C we can implement both mechanisms.


Turning Sprites on

Sprites can be turned on in two steps. First we set the master enable flag on by setting bit 6 in the VERA Register DC_VIDEO ($9F29) to 1.

lda $9F29
ora #%01000000
sta $9F29


Next we can turn on individual sprites by setting Z-Depth values for each one we want to display. If we check above Z-Depth is stored in bits 2-3 at offset 6, so for Sprite 0 that would be $1FC06, Sprite 1 would be $1FC0E and so on.
We need two bits because (as the name suggest) we control not only on-off but also the depth in relation to screen layers. It has the following possible values:


Value Description
00 Disabled
01 Between Background and Layer 0
10 Between Layer 0 and Layer 1
11 In front of Layer 1

Since the default text screen is rendered on Layer 1 we have to use the last value to display sprite on top like this:

stz $9F25            ; VERA_CTRL set to use DATA0 register
lda #%0000001        ; VERA_HIGH Increment is set to 0
sta $9F22            ; and address bit 16 is set to 1
lda #$FC
sta $9F21            ; VERA_MID set to $FC
lda #$06
stz $9F20            ; VERA_LOW set to $06

lda #%00001100
sta $9F23            ; Write $0C to VRAM address $1FC06


The problem is that we still don’t see anything because we haven’t defined any other attributes, position on screen and of course the graphics of the sprite.

Sizing and Colors

Sprites can have 4 different sizes horizontally and vertically and any combination of those. Just like for the depth we have two bits to determine the size:

Value Description
00 8 pixels
01 16 pixels
10 32 pixels
11 64 pixels

We set the dimensions in the upper four bits of the eighth byte of the Sprite attributes. Bits 6-7 for height and bits 4-5 for width. So for example if we want 8x8 sprite we set all four bits to 00. If we want sprite that 32 bits wide and 16 wide we set it to %0110.
Lower four bits are used for palette shifting by 16 positions every time one is added, which can be quite useful but we will not explore it now, but feel free to experiment with it.
So, what are the colors that are at our disposal? By default in first 16 position we have standard Commodore 64 colors, next 16 are grayscale from black to white and the rest are different scales.
Only exception is color 0, which is always transparent and it is the only color that is transparent.

More about colors and palettes here.

The last thing about colors is to decide how many do we want to have in our sprite. We have two possibilities 16 or 256 and therefore we use either 4 bits per pixel or full byte per pixel. We set this in Mode setting – Bit 7 of Offset byte 1 with following values:
0 – 4 bits per pixel
1 – 8 bits per pixel
Clearly we have to find the balance between how many colors do we want to display per single sprite and how much space sprite occupies in memory. At first glance it seems that 4 bits and only 16 colors are very limited, however also in that case we have all 255 colors at our disposal and using palette shifting each sprite can have access to different part of palette if needed.

Positioning

We can position sprites anywhere on the screen. Since the maximum screen resolution of Commander X16 is 640x480 we need at least 10 bits to be able to store all values and that is exactly what we have.
In Offset byte 2 we store bits 0-7 of X (horizontal) coordinate with remaining 2 high bits in Offset byte 3. Similarly we store Y (vertical) coordinate in Offset byte 4 (bits 0-7) and Offset byte 5 (high bits).
If we have horizontal position in variable X, we could use following commands to store position to correct registers for Sprite 0.

stz $9F25            ; VERA_CTRL set to use DATA0 register
lda #%0010001        ; VERA_HIGH Increment is set to 1
sta $9F22            ; and address bit 16 is set to 1
lda #$FC
sta $9F21            ; VERA_MID set to $FC
lda #$02
stz $9F20            ; VERA_LOW set to $02

lda #XposLO
sta $9F23            ; Write Low byte to VRAM address $1FC02
lda #XposHI
sta $9F23            ; Write High byte to VRAM address $1FC03


Please not that in the current version of emulator position 0,0 is in the top left corner of the visible screen. It means that sprite can’t be drawn left or above the visible screen to allow for gradual entry into the visibility. I am assuming this might change in the future and this text will be updated accordingly.
We could consider Z-Plane as part of positioning in Z coordinate too. We talked about different options in the section above.

Storing Appearance

Sprites are essentially small rectangular graphic areas and we are already armed with all the information we need to understand the possibilities:

  • Horizontal x vertical dimensions can be 8, 16, 32 or 64 pixels
  • Each pixel can take 1 byte or half byte (4 bits)
  • We understand the available colors in palette (and know where to change them if needed)


The only tricky part is to store the pointer to sprite graphics into sprite registers because it is slightly unconventional. We are used to round all addresses to full bytes or at least nibbles (4 bits or half bytes) because that makes it convenient for displaying in hexadecimal notation. However pointer to sprite graphics is 17 bits but we can only store 12 so lower 5 bits are always 0. That means we can point to memory locations in increments of 32 bytes.
Video RAM is located from $00000 - $1FFFF – 16 KB, so obviously with 17 bits long address we can reach all of it. We also already determined that screen starts at $00000 and in default text mode (SCREEN 2) we are safe anywhere after $03C00.
So how do we calculate the pointer?
OK, let’s assume we have our graphics data located from $04000. In binary that is:

    %0   0100 0000   0000 0000

If we cut away lower 5 bits we get:

    %0   0100 0000   000

or properly grouped into nibbles and bytes:

    10   0000 0000

Or in Hex:

    $200

So all that remains is to write $00 to Offset 0 and $02 to Offset 1 (plus the Mode flag in the bit 7). I personally would prefer to have address shifted by 4 bits so it would be more clearly readable in Hex notation but I guess after a while one can get used to it.

Putting it all together

I think it is time to put all the knowledge together and use it in practical example. Current version of the emulator has an interesting feature built in, namely Sprite 0 is used to display mouse pointer. When mouse pointer is over the emulator window it shows Sprite 0 at its location. By default the Sprites are off and there is no graphics associated with it so nothing is shown but let’s define it and turn it on so we see its position. By default it comes with plain black pointer but we can change that. First turn it on like this:

MOUSE 1

I think it is appropriate to use Amiga style pointer:


  • We will therefore use 16 x 16 pixel sprite.
  • We will use 256 color mode
  • For Black we will use color index 16, for dark red index 50 (color $a22) and for pale red/tan index 38 (color $fbb).
  • Since we did hard calculations above already, we will use $04000 to store the data. Here is the complete code:

To review..
In lines 10-14 we have necessary directives to tell compiler how to build the binary. It will generate a BASIC line to call the machine code.
Lines 17-21 are some constants we use to make the code more readable.
In section from 29 -34 we prepare VERA registers for storing sprite data in VRAM starting at $04000 and then in lines 36-40 we transfer 256 bytes of data from data section at the end of the program into VRAM.
In lines 43-45 we set the Bit 6 in register VIDEO to turn on the sprites. This is actually redundant if we turned on the mouse pointer in BASIC but it is good practice nevertheless.
Then in lines 47-51 we configure VERA registers to point to the beginning of Sprite 0 registers which is $1FC00.
And finally in lines 53-63 we set all eight registers for Sprite 0.


To compile this program we have to save it as Sprite1.asm and using cc65 compiler and command line:

cl65 -t cx16 Sprite1.asm -o SPRITE1.PRG

This generates the SPRITE1.PRG binary that can then be simply loaded to CommanderX16 using

LOAD"SPRITE1.PRG"

and run simply by

RUN



Part II - IRQ Animation

Comments

Popular posts from this blog

Commander X16 Premium Keyboard

Hello VERA (BASIC vs C vs Assembly)

Default Palette