Amiga Machine Code Letter VI - Blitter 3

Amiga Machine Code - Letter VI - Blitter 3

We have reached Letter VI of the Amiga Machine Code course. I will only skip through the details in this post. Make sure to read the letter, to get a grasp of things.

In this post I will go through the code of the mc0602 program, that’s found on Disk 1. I have annotated the code with a lot of comments, so that it should be possible to get a gist of things reading this post alone.

To get things running, you need to use the “ri” command of Seka to load the assets into memory at their designated labels. I got this demo running with 300 kb chip-mem in Seka. Remember that we want chip-mem, because that’s the only memory that the custom chips can read.

Open Seka and go to the folder where Disk 1 is. The book Amiga Machine Language contains some documentation on Seka if needed. Now write something along these lines:

SEKA>r
FILENAME>mc0602
SEKA>a
OPTIONS>
No Errors
SEKA>ri
FILENAME>screen
BEGIN>screen
END>
SEKA>ri
FILENAME>fig
BEGIN>fig
END>
SEKA>ri
FILENAME>mask
BEGIN>mask
END>

As is seen from the above, we have assets for the background screen and for the figure fig and it’s corresponding mask. We need to load these assets into memory at the designated labels for this demo to work. Let’s start with a screenshot 😃

mc0603 screenshot

This program builds upon the same general principals as the previous mc0601 program. But it uses many more subroutines to keep things organized.

The general gist, is that we now have 5 bitplanes and thus 32 colors, for both background and blitter object. The blitter object is moved on the screen, using the mouse.

We also use a more advanced blitter logic function, called the cookie-cut function, that we investigated in a previous post.

The shiftblit subroutine is also a bit interesting, so I will write a couple of notes on this in the buttom of this post.

move.w	#$4000,$dff09a ; INTENA clr master interrupt

;----Stop disk drives
or.b	#%10000000,$bfd100 ; set CIABPRB MTR
and.b	#%10000111,$bfd100 ; clr CIABPRB SEL3, SEL2, SEL1, SEL0

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

;-----Setup bitplanes, display and DMA data fetch---
;-----Resolution 320*256 with 5 bitplanes
move.w	#$5200,$dff100 ; BPLCON0 use 5 bitplanes (32 colors)
move.w	#$0000,$dff102 ; BPLCON1 scroll
move.w	#$0000,$dff104 ; BPLCON2 video
move.w	#0,$dff108     ; BPL1MOD modulus odd planes
move.w	#0,$dff10a     ; BPL2MOD modulus even planes
move.w	#$2c81,$dff08e ; DIWSTRT upper left corner ($81,$2c)
move.w	#$f4c1,$dff090 ; DIWSTOP enaple PAL trick
move.w	#$38c1,$dff090 ; DIWSTOP lower right corner ($1c1,$12c)
move.w	#$0038,$dff092 ; DDFSTRT data fetch start at $38
move.w	#$00d0,$dff094 ; DDFSTOP data fetch stop at $d0

;-----Transfer colors from screen to the color table registers
lea.l	screen,a1    ; write screen address into a1
move.l	#$dff180,a2  ; move address of COLOR00 into a2
moveq	#31,d0       ; set color counter to 31

colorloop:
move.w	(a1)+,(a2)+  ; move color from screen to color table
dbra	d0,colorloop ; if not -1 then go to colorloop

;-----Set bitplane pointers in bplcop---
lea.l	bplcop,a2 ; write bplcop address into a2
addq.l	#2,a2     ; add two bytes so a2 can set BPL1PTH
move.l	a1,d1     ; move a1 (points to screen data) into d1
moveq	#4,d0     ; set bitplane counter to 4

bplcoploop:
swap	d1         ; perform swap of words 
move.w	d1,(a2)    ; move bit 0-15 into what a2 points to (sets BPLxPTH)
addq.l	#4,a2      ; make a2 point to indput for PBLxPTL    
swap	d1         ; perform swap of words
move.w	d1,(a2)    ; move bit 0-15 into what a2 points to (sets BPLxPTL)
addq.l	#4,a2      ; make a2 point to the next BPLxPTH input
add.l	#10240,d1      ; make d1 point to next bitplane
dbra	d0,bplcoploop  ; decrement d0. if > -1 goto bplcoploop

;-----Start copper---
lea.l	copper,a1     ; put address of copper into a1
move.l	a1,$dff080    ; set COP1LCH and COP1LCL to address in a1
move.w	$dff088,d0    ; start copper by read of strobe address COPJMP1

move.w	#$8580,$dff096 ; DMACON set BLTPRI, PBLEN, COPEN

bsr	readmouse ; read mouse coordinates to determine blit area
bsr	storeback ; store background to blit in backbuffer

mainloop:
move.l	$dff004,d0 ; read VPOSR and VHPOSR into d0 as one long word
asr.l	#8,d0      ; shift right 8 poositions
and.l	#$1ff,d0   ; and for immediate data
cmp.w	#300,d0
bne	mainloop       ; if not at line 300 goto mainloop

bsr	recallback     ; recall blitted background from backbuffer
bsr	readmouse      ; read mouse coordinates to determine blit area
bsr	storeback      ; store background to blit in backbuffer
bsr	shiftblit      ; blit pixelwise horisontal
bsr	blitin         ; do the actual blit using cookie-cut

btst	#6,$bfe001 ; CIAAPRA FIR0 check mouse button
bne	mainloop       ; if not pressed goto mainloop

move.w	#$0080,$dff096 ; DMACON clear copper

move.l	$4,a6          ; reestablish DMA's and copper
move.l	156(a6),a6
move.l	38(a6),$dff080

move.w	#$80a0,$dff096
move.w	#$0400,$dff096

move.w	#$c000,$dff09a
rts                     ; return from mainloop

;-----blitin is blitting in the data from A, B, and C into D 
;-----using the cookie-cut logic function
blitin:
lea.l	maskbuffer,a1 ; store maskbuffer address in a1
lea.l	backbuffer,a3 ; store backbuffer address in a3
lea.l	figbuffer,a2  ; store figbuffer address in a2
lea.l	screen,a4     ; store screen in a4
add.l	#64,a4        ; skip first 64 bytes of color data

lea.l	mousex,a0     ; store mousex address in a0
move.l	(a0),d0       ; move mousex value into d0
lea.l	mousey,a0     ; store mousey address in a0
move.l	(a0),d1       ; move mousey value into d1

;-----find first blit position
lsr.l	#4,d0         ; mouse x shift right 4 bits  
lsl.l	#1,d0         ; mouse x shift left 1 bit
mulu	#40,d1        ; unsigned multiply to mousey (40 bytes is width of screen)
add.l	d0,a4         ; add mousex to screen address in a1
add.l	d1,a4         ; add mousey to a1

moveq	#4,d7         ; initialize loop counter (5 bitplanes)

blitinloop:
btst	#6,$dff002           ; wait for blitter
bne	blitinloop

move.l	a4,$dff054           ; BLTDPTH and BLTDPTL points to screen
move.l	a1,$dff050           ; BLTAPTH and BLTAPTL points to maskbuffer
move.l	a2,$dff04c           ; BLTBTPH and BLTBPTL points to figbuffer
move.l	a3,$dff048           ; BLTCPTH and BLTAPTL points to backbuffer
move.w	#32,$dff066          ; BLTDMOD set modulus to 32 bytes on D 40-(64/8)
move.w	#0,$dff064           ; BLTAMOD set modulus to 0 bytes on A
move.w	#0,$dff062           ; BLTBMOD set modulus to 0 bytes on B
move.w	#0,$dff060           ; BLTCMOD set modulus to 0 bytes on C
move.l	#$ffffffff,$dff044   ; set BLTAFWM/BLTALWM first and last word mask for A
move.w	#$0fca,$dff040       ; BLITCON0 use A,B,C, and D, with cookie-cut
move.w	#$0000,$dff042       ; BLITCON1
move.w	#$0b44,$dff058       ; set BLTSIZE height 45 lines, width 4 words (64 pixels)

add.l	#360,a2              ; point to next bitpane in figbuffer
add.l	#360,a3              ; point to next bitplane in backbuffer
add.l	#10240,a4            ; point to next bitplane in screen

dbra	d7,blitinloop        ; if d7 > -1 goto blitinloop
rts                          ; return from blitin

;----shiftblit enables us to blit 
;----pixelwise instead of wordwise horizontal
shiftblit:
lea.l	fig,a1        ; put fig address into a1
lea.l	figbuffer,a2  ; put figbuffer address into a2

lea.l	mousex,a0     ; put mousex address into a0
move.l	(a0),d1       ; put mousex value into d1

;-----preparing a value for BLTCON0 by first setting up
;-----the shift value (byte 12-15) and then use A and D
;-----with the logic function D=A
andi.l	#$f,d1        ; clear all but first byte of mousex in d1
lsl.l	#8,d1         ; shift left 8 bits (max allowed)
lsl.l	#4,d1         ; shift left another 4 bits
add.w	#$09f0,d1     ; value for using A and D with logic function D=A

moveq	#4,d7         ; intialize loop counter (5 bitplanes)

shiftfigloop:
btst	#6,$dff002    ; wait for blitter to finish
bne	shiftfigloop

move.l	a2,$dff054          ; set BLTDPTH and BLTDPTL to figbuffer
move.l	a1,$dff050          ; set BLTAPTH and BLTAPTL to fig
move.w	#0,$dff066          ; set BLTDMOD modulus to 0 bytes on D
move.w	#0,$dff064          ; set BLTAMOD modulus to 0 bytes on A
move.l	#$ffffffff,$dff044  ; set BLTAFWM/BLTALWM first and last word mask for A
move.w	d1,$dff040          ; BLTCON0 see above for settings
move.w	#$0000,$dff042      ; BLTCON1
move.w	#$0b44,$dff058      ; set BLTSIZE height 45 lines, width 4 words (64 pixels)

add.l	#360,a1             ; point to next bitplane in fig
add.l	#360,a2             ; point to next bitplane in figbuffer

dbra	d7,shiftfigloop     ; if d7 > -1 goto shiftfigloop

lea.l	mask,a1             ; put mask address into a1
lea.l	maskbuffer,a2       ; put maskbuffer address into a2

shiftmaskloop:
btst	#6,$dff002          ; wait for blitter (BLTSIZE triggers the blitter)
bne	shiftmaskloop

move.l	a2,$dff054          ; set BLTDPTH and BLTDPTL to maskbuffer
move.l	a1,$dff050          ; set BLTAPTH and BLTAPTL to mask
move.w	#0,$dff066          ; set BLTDMOD modulus to 0 bytes on D
move.w	#0,$dff064          ; set BLTAMOD modulus to 0 bytes on A
move.l	#$ffffffff,$dff044  ; set BLTAFWM/BLTALWM first and last word mask for A
move.w	d1,$dff040          ; BLTCON0 see above for settings
move.w	#$0000,$dff042      ; BLTCON1
move.w	#$0b44,$dff058      ; set BLTSIZE height 45 lines, width 4 words 64 pixels

rts                         ; return from shiftblit

;-----subroutine read mouse x, y---
;-----store result in mousex and mousey---
readmouse:
move.w	$dff00a,d0  ; move JOY0DAT into d0
move.l	d0,d1       ; move d0 value into d1
lsr.w	#8,d1       ; shift right 8 bits
andi.l	#$ff,d0     ; clean with and - d0 holds mouse x value
andi.l	#$ff,d1     ; clean with and - d1 holds mouse y value

lea.l	mousex,a1   ; store mousex result address into a1
move.l	d0,(a1)     ; write mouse x value into result address
lea.l	mousey,a1   ; same stuff for mouse y
move.l	d1,(a1)

rts                 ; return from readmouse

;-----Store screen in backbuffer---
storeback:
lea.l	screen,a1       ; store screen address in a1
add.l	#64,a1          ; move address past color data
lea.l	backbuffer,a2   ; store backbuffer address in a2

lea.l	mousex,a0       ; store mousex address in a0
move.l	(a0),d0         ; move mouse x value into d0
lea.l	mousey,a0       ; store mousey address in a0
move.l	(a0),d1         ; move mouse y value into d1

;-----find first blit position
lsr.l	#4,d0     ; mouse x shift right 4 bits 
lsl.l	#1,d0     ; mouse x shift left 1 bit
mulu	#40,d1    ; unsigned multiply to mousey (40 bytes is width of screen)
add.l	d0,a1     ; add mousex to screen address in a1
add.l	d1,a1     ; add mousey to a1

moveq	#4,d7     ; initializer loop counter (5 bitplanes)

storebackloop:
btst	#6,$dff002         ; wait for blitter
bne	storebackloop

move.l	a2,$dff054         ; set BLTDPTH and BLTDPTL to backbuffer
move.l	a1,$dff050         ; set BLTAPTH and BLTAPTL to mouse pos on screen
move.w	#0,$dff066         ; set BLTDMOD modulus to 0 bytes on D
move.w	#32,$dff064        ; set BLTAMOD modulus to 32 bytes on A
move.l	#$ffffffff,$dff044 ; set BLTAFWM/BLTALWM first and last word mask for A
move.w	#$09f0,$dff040     ; BLTCON0 use A and D, set logic function D=A
move.w	#$0000,$dff042     ; BLTCON1
move.w	#$0b44,$dff058     ; set BLTSIZE height 45 lines, width 4 words 64 pixels

add.l	#10240,a1          ; point to next bitplane in screen
add.l	#360,a2            ; point to next bitplane in backbuffer (45 * 8 bytes)

dbra	d7,storebackloop ; if d7 > -1 goto storebackloop
rts                      ; return from storeback

;-----Write backbuffer to screen
recallback:
lea.l	screen,a1       ; store screen address in a1
add.l	#64,a1          ; move address past color data
lea.l	backbuffer,a2   ; store backbuffer address in a2

lea.l	mousex,a0       ; store address of mousex in a0
move.l	(a0),d0         ; move mousex value into d0
lea.l	mousey,a0       ; store address of mousey in a0
move.l	(a0),d1         ; move mousey value into d1 

;-----find first blit position
lsr.l	#4,d0           ; mouse x is shifted 4 bits right  
lsl.l	#1,d0           ; mouse x is shifted 1 bit left
mulu	#40,d1          ; unsigned multiply to mousey (40 bytes is width of screen)
add.l	d0,a1           ; add mousex to screen address in a1
add.l	d1,a1           ; add mousey to a1

moveq	#4,d7           ; initialize counter for the loop (5 bitplanes)

recallbackloop:
btst	#6,$dff002          ; wait for blitter
bne	recallbackloop          
                            
move.l	a1,$dff054          ; set BLTDPTH and BLTDPTL to mouse pos on screen
move.l	a2,$dff050          ; set BLTAPTH and BLTAPTL to backbuffer
move.w	#32,$dff066         ; set BLTDMOD modulus to 32 bytes on D
move.w	#0,$dff064          ; set BLTAMOD modulus to 0 bytes on A
move.l	#$ffffffff,$dff044  ; set BLTAFWM/BLTALWM mask for A
move.w	#$09f0,$dff040      ; BLTCON0 use A and D, set logic function D=A
move.w	#$0000,$dff042      ; BLTCON1
move.w	#$0b44,$dff058      ; set BLTSIZE height 45 lines, width 4 words 64 pixels
                            
add.l	#10240,a1           ; point to next bitplane in screen
add.l	#360,a2             ; point to next bitplane in backbuffer (45 * 8 bytes)

dbra	d7,recallbackloop   ; if d7 > -1 goto recallbackloop
rts                         ; return from recallback

copper:
dc.w	$2c01,$fffe ; wait($01,$2c)
dc.w	$0100,$5200 ; (move) set BPLCON0 use 5 bitplanes, enable color burst

bplcop:
dc.w	$00e0,$0000 ; BPL1PTH
dc.w	$00e2,$0000 ; BPL1PTL
dc.w	$00e4,$0000 ; BPL2PTH
dc.w	$00e6,$0000 ; BPL2PTL
dc.w	$00e8,$0000 ; BPL3PTH
dc.w	$00ea,$0000 ; BPL3PTL
dc.w	$00ec,$0000 ; BPL4PTH
dc.w	$00ee,$0000 ; BPL4PTL
dc.w	$00f0,$0000 ; BPL5PTH
dc.w	$00f2,$0000 ; BPL5PTL

dc.w	$ffdf,$fffe ; wait($df,$ff) enable wait < $ff horiz
dc.w	$2c01,$fffe ; wait($01,$12c) for PAL
dc.w	$0100,$0200 ; (move) set BPLCON0 disable bitplanes
                    ; needed to support older PAL chips
dc.w	$ffff,$fffe ; end of copper

screen:
blk.l	12816,0 ; allocate 64 + 320/8*256*5 = 51264 bytes = 12816 longs

fig:
blk.l	450,0 ; 45 lines * 64 pixels * 5 bitplanes = 14400 bits = 450 longs

mask:
blk.l	90,0 ; allocate 45 lines * 64 pixels = 2880 bits = 90 longs

figbuffer:
blk.l	450,0

maskbuffer:
blk.l	90,0

backbuffer:
blk.l	450,0

mousex:
dc.l	0
mousey:
dc.l	0

Shiftblit subroutine

Up until now, we have blitted an object that scrolled vertically down the screen. This simple animation, was achived by moving the start address of the blit down the screen.

However, since we are now using the mouse as movement input, we are forced to also consider horizontal movement. This poses a problem, because the start of the blit is always given as an adress. If we left it at that, we would get some very clunky movement, moving the blitter object at words at a time.

What we want, is to move the blitter object by blitting a few pixels and not whole words. To accomblish that, we use the shift values in BLTCON0, which is stored in the d1 register in the shiftblit subroutine.

To read more about pixelwise horizontal movement, take a look at the Amiga Hardware Reference Manaual. They have an example of an animated car driving down the road.

Also, if you have some troubles understanding the differences between logic and arithmetic shift operations, like LSL and ASL, then take a look at this youtube video by Padraic Edgington.

One reader wrote, that he had a little trouble with blitting something else than a donut 🍩. Fortunatly it can be done - read about it here:

Make Your Own Graphic Assets


Amiga Machine Code Course

Previous post: Amiga Machine Code Letter VI - Blitter 2

Next post: Amiga Machine Code Letter VII - Blitting and Scrolling.

Mark Wrobel
Mark Wrobel
Team Lead, developer and mortgage expert