Amiga Machine Code Letter VII - Blitting and Scrolling

Amiga Machine Code - Letter VII

We have reached Letter VII of the Amiga Machine Code course. It took a while getting here 😉.

In this post, we are going to take another round with the blitter. This time to show how to make scrolling work! There’s a few concepts that are a bit hard to grasp, but the letter does a great job at explaining them. However, there some darker spots, so lets dive in!

Descending mode

Up until now, we have only used the blitter in ascending mode. We have blitted seperate areas of memory into the destination - the screen memory.

This time, however, we are going to blit from the screen to the screen. If the source and destination overlaps, we risk overwritting data, before the blitter has had the chance to copy it to the destination.

In the book Mapping the Amiga, there’s a simple example of how such a data overwrite could happen.

For example, say memory location 102 holds a $\$FEDC$ and memory location 104 holds a $\$BA98$. If you want to copy these two words into locations 104 and 106, the blitter would first take the $\$FEDC$ from 102 and move it into 104. Then, when it want to move the word in 104, it would find $\$FEDC$, not $\$BA98$, because that word had been destroyed by the first move operation.

data loss

To avoid loss of data, the blitter can run in descending mode, where it works in reverse. It starts at the end and works it way back to the beginning of the source and destination. Descending mode is turned on by setting bit 1 of the BLTCON1 register.

Let’s look at the example again, now with descending mode turned on.

Start with the last word of the source, and blit it to the last word of the destination. Descending mode step 1

Proceed backwards until the beginning of the source is reached, and blit it to the first word of the destination. Descending mode step 2

Voila! No source data was lost 👍.

Mapping the Amiga, provides the following rule for choosing between ascending and descending mode.

If the end of the source overlaps the start of the destination, use descending mode. If the start of the source overlaps the end of the destination, use ascending mode. If the source and destination don’t overlap at all, use either mode you please.

In the image below, the end of the source overlaps the start of the destination. In this case use descending mode.

Descending rule

In the image below, the start of the source overlaps the end of the destination. In this case use ascending mode.

Ascending rule

In descending mode, the modulo of BLTxMOD will be subtracted rather than added. Also the shifts will work in reverse, so that the shifts defined in BLTCON0 and BLTCON1 will shift to the left, rather than to the right.

As we shall see later, shifting to the left makes text scrolling from right to left possible.

A simple text scroller

The first code listing, mc0701, in Letter VII, shows how to make a simple text scroller. The code is organized by using subroutines, but every now and then the occasional magic number pops up, which make the code a little bit hard to read.

The letter does a good job of explaining the code, so I will only add explainations to what i found a little murky.

Let’s start out with a screenshot of the program in action.

Screenshot mc0701

What the font!

The characters in the font are all 20 lines high and 16 pixels wide. However since not every character has equal width, we get some rather unattractive gabs in the text spacing. Just look at the gab between “I” and “G” when spelling “AMIGA” in the screenshot above.

One of the exercises in the letter, is to introduce character dependent font width 😉.

The font is stored in a binary file, called font, and it contains 32 characters, including comma, punctuation and space. The font is stored as a bitmap of 512 * 20 in one bitplane.

Font set

The screenshot of the font asset is made by IFFCon. The program can be found on disk1 in the c folder.

Start IFFCon from the CLI. In IFFCon press right mouse and choose load bitmap from the projects menu. In the pop-up type in 512 as width, 20 as height, and 1 bitplane. Location of asset on my system is amigahd:disk1/brev07/font

Make it scroll

The scrolling is done by transfering the characters into memory, and then shift-blitting them to the left. Hence, we need to blit in descending mode, to get the text to scroll from right to left.

The screen memory is setup in a way we haven’t seen before. The visible screen is 22 words times 256 lines i.e. 353 * 256 pixels. We set the bitplane modulo to 2, so that each line will have 16 pixels at the end, that are not visible. It’s here we place the characters to be scrolled, and then left-shift them into the visible screen. This will make it look like the characters flow into the screen.

Screen area

In the code, we have some magic numbers. Two of them are point A and B, shown in the figure above.

Point A defines the offset from the start of the screen, where the graphics characters from the font should be written. The characters are all 20 lines high and 16 pixels wide (1 word). We start writting the character at the 150th line at the start of word 23 (byte 44).

$$Point A: 46 * 150 + 44 = 6944$$

Because the blitter is set to decending mode, we have to provide it with a pointer to the last address to be blitted. Point B defines the offset from the start of the screen, and is set at the end of the last line where we placed the character.

$$Point B: 46 * (150 + 20) = 7820$$

The program, mc0701, is rather simple, once the above is understood. Here’s the source code

move.w	#$4000,$dff09a      ; INTENA clear master interupt
;-----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 352*256 with 1 bitplane
move.w	#$1200,$dff100 ; BPLCON0 use 1 bitplanes (2 colors)
move.w	#$0000,$dff102 ; BPLCON1 scroll
move.w	#$0000,$dff104 ; BPLCON2 video
move.w	#$0002,$dff108 ; BPL1MOD modulus odd planes
move.w	#$0002,$dff10a ; BPL2MOD modulus even planes
move.w	#$2c71,$dff08e ; DIWSTRT upper left corner ($71,$2c)
move.w	#$f4d1,$dff090 ; DIWSTOP enaple PAL trick
move.w	#$38d1,$dff090 ; DIWSTOP lower right corner ($1d1,$12c)
move.w	#$0030,$dff092 ; DDFSTRT data fetch start at $30
move.w	#$00d8,$dff094 ; DDFSTOP data fetch stop at $d8
;-----set BPL1PTH/BPL1TPL in bplcop---
lea.l	screen,a1  ; write screen address into a1
lea.l	bplcop,a2  ; write bplcop address into a2
move.l	a1,d1      ; move a1 to d1
swap	d1         ; swap words
move.w	d1,2(a2)   ; write first word into a2+2 (BPL1PTH)
swap	d1         ; swap words
move.w	d1,6(a2)   ; write first word into a2+6 (BPL1PTL)
;-----setup 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	#$8180,$dff096  ; DMACON set PBLEN, COPEN

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	scroll         ; scroll letters

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

move.w	#$0080,$dff096 ; DMACON clear copper
;-----reestablish DMA's and copper---
move.l	$04,a6
move.l	156(a6),a6
move.l	38(a6),$dff080

move.w	#$80a0,$dff096

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

scrollcnt:
dc.w	$0000

charcnt:
dc.w	$0000
;-----scroll subroutine---
scroll:
lea.l	scrollcnt,a1  ; move scrollcnt address into a1
cmp.w	#8,(a1)       ; compare scrollcnt with 8
bne	nochar        ; if not equal goto nochar

clr.w	(a1)          ; set scrollcnt to 0

lea.l	charcnt,a1    ; move charcnt address into a1
move.w	(a1),d1       ; move charcnt value into d1
addq.w	#1,(a1)       ; add 1 to charcnt value - d1 unaffected

lea.l	text,a2       ; move text address into a2
clr.l	d2            ; set d2 to 0 - d2 points to current char
move.b	(a2,d1.w),d2  ; move value in address text+charcnt into d2

cmp.b	#42,d2        ; check if d2 equals 42 (termination sign "*")
bne	notend        ; if not equal goto notend

clr.w	(a1)          ; set charcnt to 0
move.b	#32,d2        ; move 32 into d2 (space " " = 32)

notend:
lea.l	convtab,a1    ; move address of char convertion table into a1
move.b	(a1,d2.b),d2  ; d2 is an offset in the table. Store result in d2
asl.w	#1,d2         ; multiply d2 by two - font is 2 bytes wide - 16 pixels

lea.l	font,a1       ; move font address into a1
add.l	d2,a1         ; add offset d2 to a1 so it points to current letter

lea.l	screen,a2     ; move screen address into a2
add.l	#6944,a2      ; 46 * 150 + 44

moveq	#19,d0        ; use d0 as counter. Font is 20 lines heigh

putcharloop:           ; loop over each horiz line in font
move.w	(a1),(a2)      ; move 16 pixels of current letter into a2
add.l	#64,a1         ; go to next line in current letter font
add.l	#46,a2         ; go to the next line on screen
dbra	d0,putcharloop ; if d0 > -1 goto putcharloop

nochar:
btst	#6,$dff002       ; DMACONR test bit 6 BLTEN
bne	nochar               ; if blitter enabled goto nochar

lea.l	screen,a1        ; move screen address into a1
add.l	#7820,a1         ; add 46*(150+20) end of line 170
; setup blitter
move.l	a1,$dff050          ; BLTAPTH and BLTAPTL set to end of line 170
move.l	a1,$dff054          ; BLTDPTH and BLTDPTL set to end of line 170
move.w	#0,$dff064          ; BLTAMOD set modulo to 0 bytes on A
move.w	#0,$dff066          ; BLTDMOD set modulo to 0 bytes on D
move.l	#$ffffffff,$dff044  ; set BLTAFWM/BLTALWM first and last word mask for A
move.w	#$29f0,$dff040      ; BLTCON0 shift two bits on A, use A,D with D=A
move.w	#$0002,$dff042      ; BLTCON1 enable decending mode
move.w	#$0517,$dff058      ; BLTSIZE height 20 lines, width 23 words. 20 * 64 + 23

lea.l	scrollcnt,a1        ; move scrollcnt address into a1
addq.w	#1,(a1)             ; add 1 to scrollcnt value

rts                        ; return from scroll subroutine

copper:
dc.w	$2c01,$fffe ; wait($01,$2c)
dc.w	$0100,$1200 ; BPLCON0 use 1 bitplane, enable color burst

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	$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	$b80,0

font:
blk.l	$140,0

convtab:
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$1f ;" "
dc.b	$00
dc.b	$00
dc.b	$1b ;Ø
dc.b	$1c ;Å
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$1d ;,
dc.b	$00 ;-
dc.b	$1e ;.
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$00
dc.b	$1a ;Æ
dc.b	$00 ;A
dc.b	$01 ;B
dc.b	$02 ;C
dc.b	$03 ;...
dc.b	$04
dc.b	$05
dc.b	$06
dc.b	$07
dc.b	$08
dc.b	$09
dc.b	$0a
dc.b	$0b
dc.b	$0c
dc.b	$0d
dc.b	$0e
dc.b	$0f
dc.b	$10
dc.b	$11
dc.b	$12
dc.b	$13
dc.b	$14
dc.b	$15
dc.b	$16 ;....
dc.b	$17 ;X
dc.b	$18 ;Y
dc.b	$19 ;Z
dc.b	$00
dc.b	$00
dc.b	$00

text:
dc.b	"DETTE ER EN TEST AV EN SCROLL P$ AMIGA....    *"

Amiga Machine Code Course

Previous post: Amiga Machine Code Letter VI - Blitter 3

Next post: Amiga Machine Code Letter VII - Colorcycling.

Mark Wrobel
Mark Wrobel
Team Lead, developer and mortgage expert