Amiga Machine Code Letter V - Sprites

Amiga Machine Code - Letter V - Sprites

We have reached Letter V of the Amiga Machine Code course.

The program examples are now so big, that we have to read them from the course disk Disk 1

Below is a listing of the program mc0501 that shows a sprite of a flag of Norway, that scrolls down the screen. I have annotated the program and below the listing I will dive into a couple of details.

Norway sprite

; mc0501
move.w	#$4000,$dff09a ; INTENA - clear external interrupt

or.b	#%10000000,$bfd100 ; CIABPRB stops drive motors
and.b	#%10000111,$bfd100 ; CIABPRB

move.w	#$01a0,$dff096 ; DMACON clear bitplane, copper, sprite

move.w	#$1200,$dff100 ; BPLCON0 one bitplane, color burst
move.w	#$0000,$dff102 ; BPLCON1 scroll
move.w	#$003f,$dff104 ; BPLCON2 video
move.w	#0,$dff108     ; BPL1MOD bitplane modulo odd planes
move.w	#0,$dff10a     ; BPL2MOD bitplane modulo even planes
move.w	#$2c81,$dff08e ; DIWSTRT upper left corner of display ($81,$2c)
move.w	#$f4c1,$dff090 ; DIWSTOP enable PAL trick
move.w	#$38c1,$dff090 ; DIWSTOP lower right corner of display ($1c1,$12c)
move.w	#$0038,$dff092 ; DDFSTRT Data fetch start
move.w	#$00d0,$dff094 ; DDFSTOP Data fetch stop

lea.l	sprite,a1 ; put sprite address into a1
lea.l	copper,a2 ; put copper address into a2
move.l	a1,d1     ; move sprite address into d1
move.w	d1,6(a2)  ; transfer sprite address high to copper
swap	d1        ; swap
move.w	d1,2(a2)  ; transfer sprite address low to copper

lea.l	blanksprite,a1 ; put blanksprite address into a1
lea.l	copper,a2      ; put copper address into a2
add.l	#10,a2         ; add 10 to copper address in a2
move.l	a1,d1          ; move blanksprite address into d1
moveq	#6,d0          ; setup sprite counter

sprcoploop:            ; set all 7 sprite pointers
swap	d1             ; high and low to point to blanksprite 
move.w	d1,(a2)
addq.l	#4,a2
swap	d1
move.w	d1,(a2)
addq.l	#4,a2
dbra	d0,sprcoploop  ; loop trough all 7 sprite pointers

lea.l	screen,a1      ; put screen address into a1
lea.l	bplcop,a2      ; put bplcop address into a2
move.l	a1,d1          ; transfer screen address to bplcop
move.w	d1,6(a2)
swap	d1
move.w	d1,2(a2)

lea.l	copper,a1      ; put copper address into a1
move.l	a1,$dff080     ; COP1LCH (also sets COP1LCL)
move.w	$dff088,d0     ; COPJMP1 
move.w	#$81a0,$dff096 ; DMACON set bitplane, copper, sprite

wait:              ; wait until at beam line 0
move.l	$dff004,d0 ; read VPOSR and VHPOSR into d0 as one long word
asr.l	#8,d0      ; shift right 8 places
and.l	#$1ff,d0
cmp.w	#0,d0
bne	wait       ; if not equal jump to wait

wait2:             ; wait until at beam line 1
move.l	$dff004,d0 ; read VPOSR and VHPOSR into d0 as one long word
asr.l	#8,d0
and.l	#$1ff,d0
cmp.w	#1,d0
bne	wait2      ; if not equal jump to wait

bsr	movesprite ; branch to subroutine movesprite

btst	#6,$bfe001 ; test left mouse left mouse click
bne	wait       ; if not pressed jump to wait

move.w	#$0080,$dff096 ; reestablish DMA's and copper

move.l	$04,a6
move.l	156(a6),a1
move.l	38(a1),$dff080

move.w	#$8080,$dff096

move.w	#$c000,$dff09a
rts

movesprite:         ; movesprite subroutine
lea.l	sprite,a1
cmp.b	#250,2(a1)  ; sprite bottom line at 250
bne	notbottom   ; if not go to notbottom

move.b	#30,(a1)
move.b	#44,2(a1)

notbottom:
add.b	#1,(a1)     ; move sprite top line by 1
add.b	#1,2(a1)    ; move sprite bottom line by 1
rts                 ; return from subroutine

copper:
dc.w	$0120,$0000 ; SPR0PTH
dc.w	$0122,$0000 ; SPR0PTL
dc.w	$0124,$0000 ; SPR1PTH
dc.w	$0126,$0000 ; SPR1PTL
dc.w	$0128,$0000 ; SPR2PTH
dc.w	$012a,$0000 ; SPR2PTL
dc.w	$012c,$0000 ; SPR3PTH
dc.w	$012e,$0000 ; SPR3PTL
dc.w	$0130,$0000 ; SPR4PTH
dc.w	$0132,$0000 ; SPR4PTL
dc.w	$0134,$0000 ; SPR5PTH
dc.w	$0136,$0000 ; SPR5PTL
dc.w	$0138,$0000 ; SPR6PTH
dc.w	$013a,$0000 ; SPR6PTL
dc.w	$013c,$0000 ; SPR7PTH
dc.w	$013e,$0000 ; SPR7PTL

dc.w	$2c01,$fffe
dc.w	$0100,$1200

bplcop:
dc.w	$00e0,$0000 ; BPL1PTH
dc.w	$00e2,$0000 ; BPL1PTL

dc.w	$0180,$0000 ; COLOR00 black
dc.w	$0182,$0ff0 ; COLOR01 yellow
dc.w	$01a2,$0f00 ; COLOR17 sprite0 red 
dc.w	$01a4,$0fff ; COLOR18 sprite0 white
dc.w	$01a6,$000b ; COLOR19 sprite0 blue

dc.w	$ffdf,$fffe ; wait($df,$ff) enables waits > $ff vertical
dc.w	$2c01,$fffe ; wait($01,$12c) - $2c is $12c
dc.w	$0100,$0200 ; BPLCON0 unset bitplanes, enable color burst
                    ; needed to support older PAL chips
dc.w	$ffff,$fffe ; end of copper

screen:
blk.b	10240,0 ; allocate 1 kb of memory and set it to zero

sprite:
dc.w	$1e8c,$2c00
dc.w	$FB7F,$0780 ; %1111 1011 0111 1111, %0000 0111 1000 0000
dc.w	$FB7F,$0780 ; %1111 1011 0111 1111, %0000 0111 1000 0000
dc.w	$FB7F,$0780 ; %1111 1011 0111 1111, %0000 0111 1000 0000
dc.w	$FB7F,$0780 ; %1111 1011 0111 1111, %0000 0111 1000 0000
dc.w	$FB7F,$0780 ; %1111 1011 0111 1111, %0000 0111 1000 0000
dc.w	$0300,$FFFF ; %0000 0011 0000 0000, %1111 1111 1111 1111
dc.w	$FFFF,$FFFF ; %1111 1111 1111 1111, %1111 1111 1111 1111
dc.w	$FFFF,$FFFF ; %1111 1111 1111 1111, %1111 1111 1111 1111
dc.w	$0300,$FFFF ; %0000 0011 0000 0000, %1111 1111 1111 1111
dc.w	$FB7F,$0780 ; %1111 1011 0111 1111, %0000 0111 1000 0000
dc.w	$FB7F,$0780 ; %1111 1011 0111 1111, %0000 0111 1000 0000
dc.w	$FB7F,$0780 ; %1111 1011 0111 1111, %0000 0111 1000 0000
dc.w	$FB7F,$0780 ; %1111 1011 0111 1111, %0000 0111 1000 0000
dc.w	$FB7F,$0780 ; %1111 1011 0111 1111, %0000 0111 1000 0000
dc.w	$0000,$0000 ; %0000 0000 0000 0000, %0000 0000 0000 0000
dc.w	$0000,$0000 ; %0000 0000 0000 0000, %0000 0000 0000 0000

blanksprite:
dc.w	$0000,$0000 ; an empty sprite

The sprite it self is defined as 16 pixels in width, while you define the length. A sprite can have four colors, one of which is a transparent color, and can be placed at any position on the screen. The first color register in the group of four color registers is ignored and thus is the transparent color.

A sprite is build by a series of longs.

long 1 ; position and height
long 2 ; sprite data 
long 3 ; sprite data
...
long N ; zeros denotes the last line of the sprite.

The first long of a sprite defines the position and height of the sprite. It’s a little bit convoluted becasue we can’t fit any screen position into 8 bits. Here’s a schematic.

Sprite first line

The schematic shows that the sprite starts at line $1e and ends at line $2c. The horizontal position of the left side starts at $8c * 2 = $118. We multiply with two because the first control bit is bit 0.

The sprite graphics data works the same way as bitplanes. Each line of sprite graphics data takes two words, where each word defines a bitplane of 16 pixels width. With this setup we can have four colors.

The color register assignments can be seen in the Amiga Hardware Reference Manual.

sprite color register assignments

Sprite wave

The next example in the letter is mc0502, which is also found on Disk 1. It is twice as long as the above program, so I won’t list it here. It’s also almost similar in principle to the first example.

However, the mc0502 program has more sprites and a background image. The sprites move according to a pattern defined in a table. Both background and table should be loaded into Seka.

SEKA>ri
FILENAME>screen
BEGIN>screen
END>
SEKA>ri
FILENAME>movetable
BEGIN>movetable
END>

Here’s a screenshot.The sprites spells “letter course” in norwegian.

Sprites move

The movement pattern is stored in a table, because it’s quite expensive to calculate.

Trigonomic functions like sine and cosine are also non-trivial to implement in assembler. Hence, there grew a need for wave generator programs, that could produce such tables. One such program is called wavegen and can be found on Disk 1.

These days, we all have access to floating point hardware, so we don’t see the lookup table optimization technique so much anymore. Functions like sine and cosine has the interesting property of being pure functions. That is, a function that given the same input produces the same output. E.g. sine to 90 degrees is always the same. Such class of functions can be completely replaced by table lookups.

There’s an interesing note about using tables to store movements over at coppershade.org. Go and take a look, it’s a fantastic site.

It’s expensive to calculate sine and cosine, but not impossible. Many used an algorithm from 1959 called CORDIC or Volder’s algorithm that only relied on addition, subtraction, bitshift and table lookups. This algorithm has found it’s way into many chips, includning the floating point coprocessors from Motorola, the 68881 and 68882.


Amiga Machine Code Course

Previous post: Amiga Machine Code Letter V

Next post: Amiga Machine Code Letter VI - Blitter.

Mark Wrobel
Mark Wrobel
Team Lead, developer and mortgage expert