Skip to main content

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.