5. Horizontal scroller

Dowload LHA-package.

Time for a new tutorial. This time we will make a horizontal scroller for text.
The font used is a free font from the internet, but slightly modified. Each character in the font-bitmap is 32px wide and 25px high. Usually you'll find real Amiga-fonts that are 32x32px in size, but it really doesn't make much difference in the code, jos the height calculations would be slightly different.

Most of the code is for getting characters from the text-string, and manipulation of bytevalue = 0, which indicates a speedchange. The code supports 15 speeds and speed 0, wich means that the scroller will pause for a certain time. The scroller could be done using Hshifting, and then only moving whole words or longwords according to speed, but here I wanted to utilize the Blitter for scrolling, and as the Blitter incorporates a barrelshifter, that doesn't use any extra time when copying data, I opted to use this as a pixelshifter instead of Hshifthing.
Why, you ask. Well, this ay we still have the Hshifting property unused, and it can still be used to make different effects on the scrolltext (this will perhaps be shown in a later tutorial).

The BLITTER

The Blitter in the Amiga is a powerful auxilary-processor that can move (or rather copy) data from one position in to another position, but both source and destination HAVE to be in ChipMem!
The Blitter is powerfull, but can be a little hard to tame. I will not describe every aspect of the Blitter here, as I rather would like to point you in the right direction to get all info about the Blitter here: http://amigadev.elowar.com/read/ADCD_2.1/Hardware_Manual_guide/node0118.html
I will put up a page for the Blitter later on, when it comes time to make a sine-scroller (or rather a function-scroller).
In this demo the Blitter is used to shift the scroller area a certain amount of bits/pixels to the left, according to scrollspeed. Each bitplane has to be moved/copied separately, although this can be spedup using a little more Blitter-friendly bitplane setups (more about this at some point).

In this demo we are using Blitter Nasty (or Blitter Priority), set in DMACON bit 10. This will stall the CPU from manipulating data in ChipMem, and as all Blitter-registers are in ChipMem we don't have to do a Wait-for-Blitter-routine. Without BlitterNasty we would have to wait for the Blitter to finish its task before assigning new values to its registers, failing to do a wait, will in most cases make the system crash!

Blitter-setup:
Here we only do a standard copy, from source-A to destination-D, in logical terms: D=A, so we use channels A and D (of A, B, C and D). This gives us a Blitter minterm of $f0 and $9 for ch. A and D, these values have to be used in the BLTCON0, and to make things a little harder the barrelshiftvalue of source-A goes into the same register as well.

I'm now using syntax-highlighting in HTML (using CSS and JavaScript) as I had some problems with exporting good enough HTML-code from my editors. Thanks to Den at English Amiga Board for the M68k-highlighter. This will make it a lot easier to paste sourcecode into the tutorials.

; Scrolly-1

DMACONR		EQU		$dff002
ADKCONR		EQU		$dff010
INTENAR		EQU		$dff01c
INTREQR		EQU		$dff01e

DMACON		EQU		$dff096
ADKCON		EQU		$dff09e
INTENA		EQU		$dff09a
INTREQ		EQU		$dff09c

BLTCON0 	EQU		$dff040
BLTCON1 	EQU		$dff042
BLTAFWM     EQU		$dff044
BLTALWM     EQU		$dff046
BLTCPTH   	EQU		$dff048
BLTBPTH   	EQU		$dff04C
BLTAPTH   	EQU		$dff050
BLTDPTH   	EQU		$dff054
BLTSIZE     EQU		$dff058
BLTBMOD     EQU		$dff062
BLTCMOD     EQU		$dff060
BLTAMOD     EQU		$dff064
BLTDMOD     EQU		$dff066

	incdir "windows:amigavikke/"

; Optimizations could easily be made to the small/tight loops in the code by using incremental 
; addressing (An)+ or decremental addressing -(An) and REPT  / ERPT

init:
; store hardware registers, store view- and copperaddresses, load blank view, wait 2x for top of frame, 
; own blitter, wait for blitter AND finally forbid multitasking!
; all this just to be able to exit gracely

	; store data in hardwareregisters ORed with $8000 
	; (bit 15 is a write-set bit when values are written back into the system)
	move.w	DMACONR,d0
	or.w #$8000,d0
	move.w d0,olddmareq
	move.w	INTENAR,d0
	or.w #$8000,d0
	move.w d0,oldintena
	move.w	INTREQR,d0
	or.w #$8000,d0
	move.w d0,oldintreq
	move.w	ADKCONR,d0
	or.w #$8000,d0
	move.w d0,oldadkcon

	move.l	$4,a6 				; execBase ==> a6
	move.l	#gfxname,a1 		; pointer to gfxname ==> a1 : used in openLibrary
	moveq	#0,d0 				; d0 = 0 any version of graphics.library will do
	jsr	-552(a6)				; d0 = openLibrary(a1,d0)
	move.l	d0,gfxbase 			; store the returned pointer ==> gfxbase
	move.l 	d0,a6 				; d0 ==> a6 : a6 used as addressing base below
	move.l 	34(a6),oldview 		; store old Viewport
	move.l 	38(a6),oldcopper 	; store old Copperlist

	move.l #0,a1
	jsr -222(a6)	; LoadView
	jsr -270(a6)	; WaitTOF
	jsr -270(a6)	; WaitTOF
	jsr -456(a6)	; OwnBlitter
	jsr -228(a6)	; WaitBlit
	move.l	$4,a6
	jsr -132(a6)	; Forbid

; end exit gracely preparations!

	; clear Bitplanes from garbage - very slow routine! should be done with the Blitter, or unrolled loop
	move.w #352/8*200/4,d0 	; d0 is a counter for number of longwords to get cleared
	move.l #bpl0a,a0 	; bpl0a => a0
	move.l #bpl1a,a1 	; bpl1a => a1
	move.l #bpl0b,a2 	; bpl0b => a2
	move.l #bpl1b,a3 	; bpl1b => a3
	screen_clear:
		move.l #0,(a0)+	; #0 => (a0), and increment a0 to next longword (a0=a0+4)
		move.l #0,(a1)+	; #0 => (a1), and increment a1 to next longword (a1=a1+4)
		move.l #0,(a2)+	; #0 => (a2), and increment a2 to next longword (a2=a2+4)
		move.l #0,(a3)+	; #0 => (a3), and increment a3 to next longword (a3=a3+4)
		subq.w #1,d0
		bne screen_clear
		
; setup displayhardware to show a 352x200px 2 bitplanes playfield, with zero horizontal scroll and 4 modulos
	move.w	#$2200,$dff100				; 2 bitplane lowres
	move.w	#$0000,$dff102				; horizontal scroll 0
	move.w	#$0004,$dff108				; odd modulo 4
	move.w	#$0004,$dff10a				; even modulo 4
	move.w	#$2c81,$dff08e				; DIWSTRT - topleft corner (2c81)
	move.w	#$f4d1,$dff090				; DIVSTOP - bottomright corner (f4d1)
	move.w	#$0038,$dff092				; DDFSTRT - max overscan $0018 ; standard 0038 & 00d0
	move.w	#$00d0,$dff094				; DDFSTOP - max overscan $00d8 ; max overscan: 368x283px in PAL
	move.w 	#%1000010111000000,DMACON	; DMA set ON
	move.w 	#%0000000000111111,DMACON	; DMA set OFF
	move.w 	#%1100000000000000,INTENA	; IRQ set ON
	move.w 	#%0011111111111111,INTENA	; IRQ set OFF


mainloop:
; increase framecounter by 1
	move.l frame,d0
	addq.l #1,d0
	move.l d0,frame

	move.l haltend,d1
	cmp.l d0,d1
	bne do_not_reset_halt
	move.b oldscrollspeed,scrollspeed
	move.b #0,halt_status
	do_not_reset_halt:


; make copperlist
; doubblebuffering of copperlists, defined at copper1 and copper2, chosen by LSB in framecounter
; copper (and a6) will hold the address to the copperlist we will write to (not the one currently in use)
	and.l #1,d0
	bne usecopper2
	move.l #copper1,a6
	bra usecopper1
	usecopper2:
	move.l #copper2,a6
	usecopper1:
	move.l a6,copper

	cmp.b #1,halt_status
	bne halt_is_off
	move.l #1,d0
	halt_is_off:

	and.l #1,d0
	bne useBplB
	move.l #bpl0a,bpl0
	move.l #bpl1a,bpl1
	move.l #bpl0b,bpl0x
	move.l #bpl1b,bpl1x
	bra useBplA
	useBplB:
	move.l #bpl0b,bpl0
	move.l #bpl1b,bpl1
	move.l #bpl0a,bpl0x
	move.l #bpl1a,bpl1x
	useBplA:

	; bitplane 0
	move.l bpl0,d0
	move.w #$00e2,(a6)+	; LO-bits of start of bitplane
	move.w d0,(a6)+		; go into $dff0e2
	swap d0
	move.w #$00e0,(a6)+	; HI-bits of start of bitplane
	move.w d0,(a6)+		; go into $dff0e0

	; bitplane 1
	move.l bpl1,d0
	move.w #$00e6,(a6)+	; LO-bits of start of bitplane
	move.w d0,(a6)+		; go into $dff0e6
	swap d0
	move.w #$00e4,(a6)+	; HI-bits of start of bitplane
	move.w d0,(a6)+		; go into $dff0e4


	move.l #$01800000,(a6)+
	move.l #$01820000,(a6)+
	move.l #$01840226,(a6)+
	move.l #$01860448,(a6)+
	
	move.l #0,d7
	move.l #coppercolors,a5
	copper_colors:
		move.w d7,d6
		add.w #100+$2c,d6
		lsl.l #8,d6
		add.w #$07,d6
		move.w d6,(a6)+
		move.w #$fffe,(a6)+
		move.w #$0182,(a6)+
		move.l d7,d6
		lsl.l #1,d6
		move.w (a5,d6),(a6)+
		addq #1,d7
		cmp.l #25,d7
		bcs copper_colors


	; end of copperlist (copperlist ALWAYS ends with WAIT $fffffffe)
	move.l #$fffffffe,(a6)+ 		; end copperlist


	move.l #0,d0 					; empty registers because of byte handling
	move.l #0,d1
	move.l #0,d2
	move.l #0,d3
	move.l #0,d4
	move.l #0,d5
	move.b scrollspeed,d0 			; scrollspeed ==> d0
	move.b scrollx,d1 				; counter for actual x-shift position
	move.b scrollflag,d2 			; this flag is needed because of 32px wide font
	add.b d0,d1 					; scrollx = scrollx + scrollspeed
	cmp.b #16,d1 
	bcc .10
	sub.b #16,d1 					; scrollx >=16 then scrollx=scrollx-16
	addq #1,d2 						; and increment scrollflag by 1
	.10:
	move.b d1,scrollx 				; save scrollx-value
	move.b d2,scrollflag 			; save scrollflag-value
	cmp.b #2,d2  					; if flag=2, then time to add next character into scroller
	bne scroll 						; otherwise just scroll the scroller
	; next character
	move.l #scrolltext,a0 			; scrolltext ==> a0
	move.l #font,a1 				; font-image ==> a1
	move.l #charoffsets,a2 			; character offsets ==> a2
	move.l bpl0,a5
	add.l #100*44+40,a5
	move.l a5,a3 					; bpl0 line 100 + offset ==> a3
	move.l bpl1,a5
	add.l #100*44+40,a5
	move.l a5,a4 					; bpl1 line 100 + offset ==> a4
	move.w scrollcounter,d3 		; scrollcounter
	get_next_char:
	add.w #1,d3 					; scrollcounter++
	move.w d3,scrollcounter 		; and store it
	move.b (a0,d3.w),d4 			; next character in scrolly (ASCII)
	cmp.b #-1,d4 					; if -1, then restart scrolly from beginning
	bne not_EOT
	move.w #-1,d3 					; set counter at -1, it will get added with +1 so it really is 0
	move.w d3,scrollcounter 		; and store it
	bra get_next_char
	not_EOT:
	cmp.b #0,d4 					; if d4=0, change speed
	bne no_speedchange
	add.w #1,d3 					; scrollcounter++
	move.w d3,scrollcounter 		; and store it
	move.l #0,d4
	move.b (a0,d3.w),d4 			; next character in scrolly (byte)
	cmp.b #0,d4 					; if d4=0, halt
	bne change_speed
	add.w #1,d3 					; scrollcounter++
	move.w d3,scrollcounter 		; and store it
	move.b (a0,d3.w),d4 			; next character in scrolly (byte)
	move.b d0,oldscrollspeed 		; oldscrollspeed = actual scrollspeed
	move.b #0,scrollspeed 			; scrollspeed = 0
	lsl.l #6,d4 					; d4*2^6 (2^6=64, 64 frames = 64/50 sec = 1,28 s) 
	move.l frame,d0
	add.l d4,d0
	move.l d0,haltend 				; set haltend to actual frame + delay
	move.b #1,halt_status 			; set halt_status = 1
	bra get_next_char
	change_speed:
	move.b d4,d0 					; new scrollspeed ==> d0
	move.b d0,scrollspeed 			; save scrollspeed for next frame
	no_speedchange:
	cmp.b #97,d4 					; if ASCII >= 97 (97 == 'a')
	bcs under97
	sub.b #32,d4 					; subtract 32 from ASCII ('a' ==> 'A' .. 'z' ==> 'Z')
	under97:
	cmp.b #91,d4 					; maximum ASCII = 90, if over get next
	bcc get_next_char
	cmp.b #32,d4 					; minimum ASCII = 32, if under get next
	bcs get_next_char				
	sub.b #32,d4 					; first character in bitmap: ASCII 32
	lsl.b #1,d4 					; *2, byte => word-alignment
	move.w (a2,d4.w),d5 			; charoffset for character
	move.l #25,d7 					; loop 25 times (25 lines high font)
	copy_char: 						; this copy-routine could be done using the Blitter,
									; but the speedgain would not be very large, as the
									; setup for the blit will in itself be many word/longword-moves
									; done with the CPU the fontdata can reside in FastMem as well
									; as ChipMem. 
	move.l d5,d6
	move.l (a1,d6.l),(a3) 			; font + offset ==> bpl0
	add.l #40*150*1,d6
	move.l (a1,d6.l),(a4) 			; font + offset ==> bpl1
	add.l #40,a1 					; font-image width: 320px/8bits = 40 bytes
	add.l #44,a3 					; bpl0, next line
	add.l #44,a4 					; bpl1, next line
	subq #1,d7
	bne copy_char
	move.b #0,scrollflag 			; empty scrollflag
	
	scroll:
	move.l #0,d0
	move.b scrollspeed,d0 			; get scrollspeed 
	cmp.b #0,d0
	beq testMouseButton
	move.l #16,d1
	sub.l d0,d1 					; 16 - scrollspeed
	lsl.l #8,d1 					; shift left 12 bitpositions (8+4)
	lsl.l #4,d1 					; as shiftvalue is in bits[12,15]
	add.l #$9f0,d1 					; set usage of sources and minterm
									; $9 = use sources A and D 
									; $f0 = minterm = A

	; move scrolly to the left
	move.l bpl0,a5 					; source: bpl0
	add.l #44*100,a5 				; pixelline 100
	move.l a5,BLTAPTH 				; BLTAPTH - A pointer
	move.l bpl0x,a5 				; destination: bpl0x
	add.l #44*100-2,a5 				; pixelline 100, and -2 bytes because of shifting
	move.l a5,BLTDPTH 				; BLTDPTH - D pointer
	move.w	#0,BLTAMOD 				; BLTAMOD - A modulo = 0
	move.w	#0,BLTDMOD 				; BLTDMOD - D modulo = 0
	move.w	d1,BLTCON0 				; BLTCON0 - set previously generated controlbits
	move.w	#0,BLTCON1 				; BLTCON1 - not used here
	move.w	#$ffff,BLTAFWM 			; BLTAFWM - mask $ffff
	move.w	#$ffff,BLTALWM 			; BLTALWM - mask $ffff
	move.w	#26<<6+22,BLTSIZE 		; BLTSIZE - 26 lines * 22 words + START BLIT

	; we are using BlitterNasty, so we don't have to wait for the Blitter here,
	; the CPU is stalled from access to ChipRAM until the Blitter is ready.
	; without BlitterNasty a WaitBlitter-routine must be used!!!

	move.l bpl1,a5 					; source: bpl1
	add.l #44*100,a5 				; pixelline 100
	move.l a5,BLTAPTH 				; BLTAPTH - A pointer
	move.l bpl1x,a5 				; destination: bpl1x
	add.l #44*100-2,a5 				; pixelline 100 -2 bytes because of shifting
	move.l a5,BLTDPTH 				; BLTDPTH - D pointer
									; BLTAMOD, BLTDMOD, BLTCON0, BLTCON1, BLTALWM and BLTAFWM
									; unchanged ==> same as in the previous blit!
	move.w	#26<<6+22,BLTSIZE 		; BLTSIZE - 26 lines * 22 words + START BLIT
	
testMouseButton:
	; if mousebutton/joystick 1  or 2 pressed then exit
	btst.b #6,$bfe001
	beq exit
	btst.b #7,$bfe001
	beq exit

; display is ready, or atleast we have done everything we wanted and the copper continues on its own
; we have to wait for Vertical Blanking before making the next frame

waitVB:
	move.l $dff004,d0
	and.l #$1ff00,d0
	cmp.l #303<<8,d0
	bne waitVB

	; use next copperlist - as we are using doubblebuffering on copperlists we now take the new one into use
	move.l copper,d0
	move.l d0,$dff080
	bra mainloop

exit:
; exit gracely - reverse everything done in init
	move.w #$7fff,DMACON 		; set bits[0,14] = 0 (bit15 is a set/clear bit)
	move.w	olddmareq,DMACON 	; and set bits[0,14] as they were at init
	move.w #$7fff,INTENA 		; set bits[0,14] = 0 (bit15 is a set/clear bit)
	move.w	oldintena,INTENA 	; and set bits[0,14] as they were at init
	move.w #$7fff,INTREQ 		; set bits[0,14] = 0 (bit15 is a set/clear bit)
	move.w	oldintreq,INTREQ 	; and set bits[0,14] as they were at init
	move.w #$7fff,ADKCON 		; set bits[0,14] = 0 (bit15 is a set/clear bit)
	move.w	oldadkcon,ADKCON 	; and set bits[0,14] as they were at init

	move.l	oldcopper,$dff080 	; load old Copperlist
	; graphics.library calls
	move.l 	gfxbase,a6 	; gfxBase ==> a6
	move.l 	oldview,a1 	; oldView ==> a1 (used in LoadView)
	jsr -222(a6)		; LoadView : load back the view at start of program
	jsr -270(a6)		; WaitTOF : Wait for Top Of Frame to get everything synced up
	jsr -270(a6)		; WaitTOF : (2 times for interlaced screens)
	jsr -228(a6)		; WaitBlit : wait for Blitter to finish running task (if any)
	jsr -462(a6)		; DisownBlitter : release Blitter to system
	; exec.library calls
	move.l	$4,a6 		; execBase ==> a6
	move.l	gfxbase,a1 	; gfxBase ==> a1 (used in closeLibrary)
	jsr -414(a6) 		; closeLibrary : close graphics.library
	jsr -138(a6)		; Permit multitasking

	; end program
	rts


; *******************************************************************************
; *******************************************************************************
; DATA
; *******************************************************************************
; *******************************************************************************


; storage for 32-bit addresses and data
	CNOP 0,4
oldview:	dc.l 0
oldcopper:	dc.l 0
gfxbase:	dc.l 0
frame:		dc.l 0
copper:		dc.l 0
haltend:	dc.l 0

; storage for 16-bit data
	CNOP 0,4
olddmareq:	dc.w 0
oldintreq:	dc.w 0
oldintena:	dc.w 0
oldadkcon:	dc.w 0

	CNOP 0,4
; storage for 8-bit data and text
gfxname:		dc.b 'graphics.library',0
author:			dc.b 'AmigaVikke',0
scrollx:		dc.b 0 				; startvalue = 0
scrollspeed:	dc.b 4 				; scrollspeed, 4 is a good value to start with
scrollflag:		dc.b 0 				; used as a flag to indicate if it is time to copy char
oldscrollspeed:	dc.b 0 				; used to return from HALT
halt_status:	dc.b 0 				; 0 = no halt, 1 = halt
scrolltext:		dc.b 'This is AmigaVikke writing at the keyboard... this is how the old demos '
				dc.b 'told the reader who in the group was writing, '
				dc.b 'and more importantly who was sending greetings. So here we go: Greetings to '
				dc.b '..........',0,8,'.......... ',0,4,'everyone ',0,0,5,' following these tutorials!!!! '
				dc.b 'r e s t a r t i n g ... in ... 3 ... 2 ... 1 ... ',0,4
				dc.b -1,0 			; -1 marks the end of the text and the scoller restarts
				; 0 indictes a speedchange, the next byte is the speed, ex: 0,2 = speed 2, 0,6 = speed 6
				; 0,0 = speed 0 = HALT!!! the next byte indicates for how many seconds, 
				; ex: 0,0,5 = halt 5*1,28 sec.
				; 0,[1,f] = speed [$1,$f]
				; 0,0,[0,f] = speed 0, halt [$0,$f] seconds.


	; Charoffset is a table for telling where each character starts
	; each character is 4 bytes wide and 25 lines high, and there are 10 chars per line
	; thus the chars o charline 1 starts at 0, 4, 8 etc bytes
	; on charline 2, we get an additional offset of 40*25 = 1000
	; (40 bytes per line * 25 lines high)
	; then the next charline is 1000*2 = 2000 etc
	; NOTICE! These values depend on the charset used!

	CNOP 0,4
charoffsets:	dc.w 	   0,    4,    8,   12,   16,   20,   24,   28,   32,   36
				dc.w	1000, 1004, 1008, 1012, 1016, 1020, 1024, 1028, 1032, 1036
				dc.w	2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036
				dc.w	3000, 3004, 3008, 3012, 3016, 3020, 3024, 3028, 3032, 3036
				dc.w	4000, 4004, 4008, 4012, 4016, 4020, 4024, 4028, 4032, 4036
				dc.w	5000, 5004, 5008, 5012, 5016, 5020, 5024, 5028, 5032, 5036

	CNOP 0,4
charcounter:	dc.w 0 		; 16bit wide ==> max 2^16 chars in scrolly
scrollcounter:	dc.w -1 	; start = -1

coppercolors:	dc.w $001,$002,$003,$004,$005,$006,$007,$008,$00a,$00b,$00c,$00e,$00f
				dc.w $00e,$00c,$00b,$00a,$008,$007,$006,$005,$004,$003,$002,$001

	Section ChipRAM,Data_c

; bitplanes aligned to 32-bit
	CNOP 0,4
bpl0:	dc.l 0 				; pointer to actual bitplane 0 (double buffering bpl0a / bpl0b)
bpl1:	dc.l 0 				; pointer to actual bitplane 1 (double buffering bpl1a / bpl1b)
bpl0x:	dc.l 0 				; pointer to buffered bitplane 0
bpl1x:	dc.l 0 				; pointer to buffered bitplane 1 
bpl0a:	blk.b 352/8*200,0
bpl1a:	blk.b 352/8*200,0
bpl0b:	blk.b 352/8*200,0
bpl1b:	blk.b 352/8*200,0

; datalists aligned to 32-bit
	CNOP 0,4
copper1:	
			dc.l $ffffffe 	; CHIPMEM!
			blk.l 2047,0 	; CHIPMEM!
	CNOP 0,4
copper2:	
			dc.l $ffffffe 	; CHIPMEM!
			blk.l 2047,0 	; CHIPMEM!

	CNOP 0,4
font:	incbin "font.raw" 	; 10x5 chars each 32px wide, 25px high (start: ascii 32)

Comments.
 Sodapop
Great tutorial as usual !!! Keep them coming !!!!
Published on Apr 11, 2013, from France