Who made the world I cannot tell
'Tis made and here I am in hell
My hand, though now my knuckles bleed
I never soiled with such a deed
A.E. Housman
ONE FINAL WORD ABOUT SCROLLING
- or -
HOW TO MINGLE GFA AND MACHINE CODE by Stefan Posthuma
Each year, around the beginning of February, people in Den Bosch
go crazy. They dress up in ridiculous clothes, go to town and go
completely out of their minds. They drink unbelievable amounts of
alcohol, dance, shout, and do other things that normally would be
regarded as utterly ridiculous. They call in 'Carnaval', and I
was in the middle of it last night.
It is 2:30 pm, and my parents have already thrown themselves into
the feasting crowds. I am sitting serenely in my room, still
trying to recover. Two maniacs from Amsterdam visited my
yesterday and since they are not from Den Bosch themselves, they
acted ever more crazy than I did. I hope that girl is allright...
But I was going to tell you about some stuff. Lots of things have
been said about scrolling, and I want to add my part. I also like
to take this opportunity to tell you how to use machine code
effectively from GfA basic. I have been doing it a lot with ST
NEWS so I am quite experienced.
Let's say I want to write a program in GfA and I want to include
a scrolling message. It can be done in GfA, but it never seems to
work out completely, especially when you want to make the program
resolution-independent. So you have to write the scroller in
machine code.
The routine you write must be a regular sub-routine, which ends
with an RTS. You can use all registers and mess around a lot as
long as you return in user mode and don't affect any memory used
to store your GfA program.
If you assemble your source, assemble to a .TOS executable. You
then have to skip the first 28 bytes of code which are not
interesting. (You can use the TOS because there is a branch in
the beginning, but why waste 28 bytes?) To load the code:
OPEN "I",#1,"CODE.TOS"
CODE$=SPACE$(LOF(#1))
SEEK #1,28
BGET #1,VARPTR(CODE$),LOF(#1)-28
CLOSE #1
You can now call the code like this:
CODE%=VARPTR(CODE$)
CALL CODE%
or
RESULT=C:CODE%(Parameter1, Paremeter2,...)
If you want to use machine code in your GfA program, there are
two problems:
- where do I put my machine code?
- how can I pass parameters and/or variables to my routine and
how can my routine give back the values I need?
The first problem can be answered pretty easy. Just create a
string variable large enough to hold your code. The only problem
here is that GfA tends to move strings around memory, so you have
to create position-independent code, which is a bit more of a
hassle than normal code.
The second problem can be solved in two ways:
Passing values by stack is a good one. This means you have to use
the C: instruction to call your code. Let's say you have to pass
three values, 20 (word), 800 (long) and the value of a variable.
Your GfA call would look like this:
CODE%=VARPTR(CODE$)
RESULT%=C:CODE%(20,L:800,X%)
The three values, 20, 800 (the L: prefix denotes a long value)
and X% will now be pushed on stack and your routine will be
called. To pop the values from stack, you use the following code:
MOVE.W 4(SP),D0 ; get first argument in D0
MOVE.L 6(SP),D1 ; get second argument in D1
MOVE.W 10(SP),D2 ; get third argument in D2
You can pass up to 256 parameters this way, which should be
sufficient I daresay.
The value of D0 upon return will be assigned to RESULT% (long).
This means that you can only return one parameter. But just like
in C, you can of course pass pointers to your variables to your
code. Let's say you want the routine to affect X%. The code would
look like this:
RESULT%=C:CODE%(20,L:800,L:VARPTR(X%))
MOVE.W 4(SP),D0 ; get first argument in D0
MOVE.L 6(SP),D1 ; get second argument in D1
MOVE.L 10(SP),A0 ; get pointer to X% in A0
.
.
MOVE.L #200,(A0) ; X%=200
.
.
CLR.L D0 ; 0: all is well
RTS
After calling the routine, X% will be 200.
That about covers the calling by stack. There is another way
though, which I prefer. It involves an array of integers.
If you create an array of let's say six integers, 6 times 4 = 24
consecutive bytes will be reserved somewhere. The Varptr of the
first element of the arry points to the 24 bytes. So if you do
this:
DIM PARM%(5)
PARM%(0)=10
PARM%(1)=6
PARM%(2)=XBIOS(3)
PARM%(3)=XBIOS(2)
PARM%(4)=VARPTR(S$)
PARM%(5)=0
PARM%(6)=0
VOID C:CODE%(L:VARPTR(PARM%(0)))
The address of the parameter table will be passed to your
routine. Now you can read and modify all parameters using the
following code:
MOVE.L 4(SP),A6 ; address of parameter table in A6
MOVE.L (A6),D0 ; first parameter D0
MOVE.L 12(A6),D1 ; fourth parameter in D1
Now you can easily use your parameters, both from GfA and machine
code.
Let's illustrate the above theory with an example. As mentioned
before, we want to create a scrolling message which works in
Monochrome as well as in Medium Res.
After giving it some thought, we will conclude that we need 6
parameters:
Parm%(0) = the address to scroll on the screen. If you want your
scroll to be on line 200 (or 100 in Mid-Res), this will do it:
PARM%(0)=XBIOS(3)+200*80
Parm%(1) = address of 32 byte buffer which is used to scroll the
character into the screen. You can create interesting effects by
tampering with this buffer during the scroll.
BUF$=SPACE$(32)
PARM%(1)=VARPTR(BUF$)
Parm%(2) = address of the scrolling text. The text should be in
normal ASCII and be ended with a null byte.
Parm%(3) = scroll pixel counter. Counts the number of pixels that
have been scrolled. If this reaches -1 (decreased from 7), a new
character should be inserted into the character buffer, and the
counter must be set to 7 again.
Parm%(4) = character number to be scrolled. Add it to the address
of the text and you have the address of the character. Should
be zero when scroll starts.
Parm%(5) = address of font to be used. If you make this -1, the
ROM font will be taken, but you can supply your own font. It has
to be byte-aligned, 8*16 in monochrome and 8*8 in medium res.
Well, time for some machine code:
scroll move.w #4,-(sp)
trap #14
addq.l #2,sp
move.l d0,d1 * resolution in d1
move.l 4(sp),a0 * a0: address of parameter
table
move.l 8(a0),a6 * a6: address of text
move.l 12(a0),d5 * d5: scroll pixel counter
move.l 16(a0),d6 * d6: character number to be
scrolled
cmp.l #-1,20(a0) * font address -1?
bne.s fontok
asl.l #2,d1 * d1 times 4
move.l d1,d6 * save
movem.l a0-a2/d1/d2,-(sp) * save registers
dc.w $a000 * Line-A init
move.l 0(a1,d6.w),a1 * get font address
move.l 76(a1),d6 * font data address
movem.l (sp)+,a0-a2/d1/d2 * get registers back
move.l d6,20(a0) * set font parameter
fontok cmp.b #2,d1 * monochrome?
bne.s scroll0 * no way
move.w #15,d2 * monochrome: 16 lines in a
character
bra.s scroll1
scroll0 move.w #7,d2 * medium: 8 lines in a
character
scroll1 bsr scr * perform the scroll
dbra d5,exit * decrease d5, if >= 0:
branch
* now we have to put a new letter in the buffer
move.l #7,d5 * set pixel counter
clr.l d4 * clear d4
move.b 0(a6,d6.w),d4 * get a character
bne.s scroll2 * not a null byte
clr.l d6 * set d6 to zero
move.b (a6),d4 * get first character
scroll2 addq.l #1,d6 * increase character
counter
move.l 4(a0),a1 * a1: buffer address
move.l 20(a0),a2 * a2: font address
move.w d2,d0 * number of words to
transfer
scroll3 move.b 0(a2,d4.w),(a1)+ * transfer a byte,
increase a1
clr.b (a1)+ * clear next byte
add.l #256,a2 * next character line
dbra d0,scroll3 * decrease d0, >= 0:
branch
* end of our routine, update values and return to GfA
exit move.l d5,12(a0) * update values
move.l d6,16(a0)
rts * back to GfA
* scr
* do the actual scrolling
scr move.l (a0),a1 * address on screen to scroll
move.l 4(a0),a2 * buffer where letter comes
from
cmp.b #2,d1 * monochrome?
beq scr2
* now: the medium res scroller
move.w #7,d0 * 8 scanlines to scroll
scr1 lsl.w (a2)+ * scroll letter buffer
roxl.w 156(a1) * scroll screen
roxl.w 152(a1)
.
.
roxl.w 8(a1)
roxl.w 4(a1)
roxl.w (a1)
add.l #160,a1 * down one scanline
dbra d0,scr1 * repeat
rts
scr2 move.w #15,d0 * 16 scanlines to scroll
scr3 lsl.w (a2)+ * scroll from buffer
roxl.w 78(a1) * into screen
roxl.w 76(a1)
.
.
roxl.w 4(a1)
roxl.w 2(a1)
roxl.w (a1)
add.l #80,a1 * down 1 scanline = 80
bytes
dbra d0,scr3 * next scanline please!
rts
So each time you call this routine, the scroll will advance one
pixel to the left. Put this in a loop with a Vsync and you have a
nice scroller!
Repeat
Vsync
Void C:Code%(L:Varptr(Parm%(0)))
Until Mousek Or Inkey$<>""
Because all scroll parameters are saved, you can make the scroll
disappear and re-appear in the same state. The only thing you
have to do is to GET the part of the screen where the scroll is.
Then do what you like, PUT it back and continue the scroll.
If you use an editor like Tempus to create your scrolling text,
newlines and returns will be in there and they will show up as
little carriage return and bell signs. You must modify the code
to skip these or filter the text first so the returns and
newlines are removed:
OPEN "I",1,"SCROLL.TXT"
OPEN "O",2,"SCROLL.COD"
WHILE NOT EOF(#1)
INPUT #1,X$
BPUT #2,VARPTR(X$),LEN(X$)
WEND
CLOSE #1
CLOSE #2
Disclaimer
The text of the articles is identical to the originals like they appeared
in old ST NEWS issues. Please take into consideration that the author(s)
was (were) a lot younger and less responsible back then. So bad jokes,
bad English, youthful arrogance, insults, bravura, over-crediting and
tastelessness should be taken with at least a grain of salt. Any contact
and/or payment information, as well as deadlines/release dates of any
kind should be regarded as outdated. Due to the fact that these pages are
not actually contained in an Atari executable here, references to scroll
texts, featured demo screens and hidden articles may also be irrelevant.