* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*                                                                           *
*                             LINE DRAWING PROGRAM                          *
*                                                                           *
*                      BY THOMAS J. ESHELMAN, READING, PA.                  *
*                                                                           *
*                                 11/25/88.                                 *
*                                                                           *
*  Purpose:  Draw simple lines by directly communicating with the Blitter.  *
*                                                                           *
*  Assembler of the Day:    HiSoft DevPak 2.08                              *
*                                                                           *
*  Adjust your "include" files and path accordingly.                        *
*                                                                           *
*  Type the file name to start.                                             *
*  To quit, press and hold down the Left Mouse Button.                      *
*                                                                           *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *


   incdir   "fh0:asm_include/"

   include  exec/types.i
   include  exec/exec_lib.i
   include  graphics/gfx.i
   include  graphics/gfxbase.i
   include  graphics/rastport.i
   include  graphics/view.i
   include  graphics/graphics_lib.i
   include  libraries/dosextens.i
   include  libraries/dos_lib.i
   include  workbench/workbench.i
   include  workbench/startup.i
   include  hardware/custom.i


NUMCOLS     EQU   8
PLANEPTR    EQUR  d0

DEPTH       EQU   3                    Two sets of dimensions to remind that
WIDTH       EQU   401                  the display is not the same as the
HEIGHT      EQU   201                  bitmap.

VPWIDTH     EQU   320                  NEVER UTILIZE ENTIRE VIEWPORT.  
VPHEIGHT    EQU   200                  ALWAYS DRAW < size of the bitmap.

MAXWIDTH    EQU   319                  See above remarks.
MAXHEIGHT   EQU   199                  Clipping safety factor.

CIAAPRA     EQU   $bfe001              CIA-A Port register A for the Mouse.



RASSIZE:    MACRO    width, height
      move.l   \1,d1
      add.l    #15,d1
      lsr.l    #3,d1
      and.l    $fffe,d1
      mulu     \2,d1
      move.l   d1,d0
   ENDM



start:

   suba.l      a1,a1                   Asm. doesn't provide the automatic
   CALLEXEC    FindTask                startup code of 'C'.  Hence, we steal
   move.l      d0,mytask               this classic scheme from David Ashley.

   lea         gfxname(pc),a1          See Amazing Computing article.
   moveq       #0,d0                   Let's not reinvent the wheel!
   jsr         _LVOOpenLibrary(a6)
   move.l      d0,GfxBase

   movea.l     d0,a1
   move.l      gb_ActiView(a1),oldview

   lea         dosname(pc),a1
   moveq       #0,d0
   jsr         _LVOOpenLibrary(a6)
   move.l      d0,DosBase

   clr.l       returnmsg

   movea.l     mytask(pc),a4
   tst.l       pr_CLI(a4)
   bne         no_workbench

   lea         pr_MsgPort(a4),a0
   jsr         _LVOWaitPort(a6)        Must wait for WB to give a go-ahead.

   lea         pr_MsgPort(a4),a0
   jsr         _LVOGetMsg(a6)          Received.  Get it off process port.

   move.l      d0,returnmsg            Save it to reply with when closing.

   movea.l     d0,a4
   move.l      sm_ArgList(a4),d0
   beq         no_workbench

   move.l      d0,a4
   move.l      wa_Lock(a4),d1

   movea.l     DosBase(pc),a6
   CALLDOS     CurrentDir

no_workbench:

   bsr         playfields              Set up the obligatory graphics.


*  We will prepare some loops in order to vary the coordinates and color
*  arguments to our draw_line function, as we make multiple function calls.
*  These are employed simply to add some "sparkle" to what otherwise would
*  have been a simple, single line drawn on the disply...even though that is
*  really all we are here for.

*  x1 = d0, y1 = d1, x2 = d2, y2 = d3, color = d4, pattern = d6.
*  Register a5 is available, so let's employ it for coordinate subtraction.

*  As you see from the next two macros, we are taking over the computer.

   CALLGRAF    OwnBlitter
   CALLEXEC    Forbid

   move.w      #$0303,d6               Starting line pattern.
   move.w      #0,loopcounter          See below.

OuterLoop:

   addq.w      #1,loopcounter          Change color every 8 screen cycles.
   cmpi.w      #8,loopcounter
   bne         retaincolor

   move.w      #0,loopcounter
   addq.w      #1,d4                   Increment color register.

retaincolor:

   rol.w       #2,d6                   Vary line pattern.
   move.w      #0,a5                   Clear to begin.

   cmpi.w      #7,d4                   Avoid drawing lines in color 00!
   ble         InnerXloop

   move.w      #1,d4                   Reset color register to 01.

InnerXloop:

   clr.w       d1                      Y1 fixed at 0.
   move.w      #MAXHEIGHT,d3           Y2 fixed.

   move.w      a5,d0                   X1 goes from 0 to MAXWIDTH.
   move.w      #MAXWIDTH,d2            X2 goes from MAXWIDTH to 0.
*                                      Thus they double back on themselves.
   sub.w       a5,d2

   move.w      d4,-(a7)                Yeah well...Can't have everything.
   bsr         draw_line               HEADS UP!  HERE WE GO........

   move.w      (a7)+,d4

   addq.w      #2,a5                   Change line's X  coordinates.
   cmp.w       #MAXWIDTH,a5            Clip.
   ble         InnerXloop

   move.w      #0,a5                   Now for the Y axis changes.

InnerYloop:

   move.w      #MAXWIDTH,d0            X1 is fixed at MAXWIDTH
   clr.w       d2                      X2 is fixed at 0.

   move.w      a5,d1                   Y1 goes from 0 to MAXHEIGHT
   move.w      #MAXHEIGHT,d3           Y2 goes from MAXHEIGHT to 0

   sub.w       a5,d3

   move.w      d4,-(a7)
   bsr         draw_line               HERE WE GO AGAIN.........!

   move.w      (a7)+,d4

   addq.w      #1,a5                   Alter the Y coordinates
   cmp.w       #MAXHEIGHT,a5           Guarantee proper clipping
   ble         InnerYloop

   btst        #6,CIAAPRA              Keep looping until user presses
   bne         OuterLoop               the left Mouse button.

   CALLEXEC    Permit                  Release the computer for other tasks.
   CALLGRAF    DisownBlitter


*                 WE'RE DONE ALREADY.  WOW, THAT WAS QUICK!

closeall:

   move.l      returnmsg(pc),d4        To quit, we must retrieve the message.
   beq         no_workbench2           If none, we  came from the CLI.

   CALLEXEC    Forbid                  May not multitask during the reply.

   move.l      d4,a1
   jsr         _LVOReplyMsg(a6)        Reply to the WorkBench!

no_workbench2:

   lea         bitmap+bm_Planes(pc),a2   Deallocate raster memory.
   moveq       #DEPTH-1,d2


*  We will now release all memory we allocated....and know about 8-)


deallocate_1:
  
   move.l      #WIDTH,d0
   move.l      #HEIGHT,d1
   movea.l     (a2),a0                 Remember....  Dereference!
   move.l      a0,d2
   beq         quit_0

   CALLGRAF    FreeRaster

   adda.l      #4,a2
   dbra        d2,deallocate_1

quit_0:

   movea.l     cm(pc),a0
   move.l      a0,d0
   beq         quit_1
   CALLGRAF    FreeColorMap

quit_1:

   lea         vport(pc),a0
   move.l      a0,d2
   beq         quit_2
   CALLGRAF    FreeVPortCopLists

quit_2:

   lea         view(pc),a0
   movea.l     v_LOFCprList(a0),a0
   move.l      a0,d2
   beq         quit_3
   CALLGRAF    FreeCprList

quit_3:

   movea.l     oldview(pc),a1
   move.l      a1,d2
   beq         quit_4
   CALLGRAF    LoadView

quit_4:

   movea.l     DosBase(pc),a1
   move.l      a1,d2
   beq         quit_5
   CALLEXEC    CloseLibrary

quit_5:

   movea.l     GfxBase(pc),a1
   move.l      a1,d2
   beq         quit_6
   CALLEXEC    CloseLibrary

quit_6:

   rts                                 Return to system.


*   Discussion of this obligatory graphics setup is irrelevant to the
*   purpose of the accompanying article.  This portion could just as
*   easily been written in C, for example.  Assembler produces little
*   practical advantage and does force a little extra typing!  Heck,
*   it even closely resembles C.


playfields:

   lea         view(pc),a1
   CALLGRAF    InitView

   lea         view(pc),a1
   move.l      #vport,v_ViewPort(a1)

   lea         vport(pc),a0
   jsr         _LVOInitVPort(a6)

   moveq       #NUMCOLS,d0
   jsr         _LVOGetColorMap(a6)     Declares/allocates a struct ColorMap
   move.l      d0,cm                   invisibly, and returns pointer to it.

   lea         vport(pc),a0
   move.l      d0,vp_ColorMap(a0)
   move.w      #VPWIDTH,vp_DWidth(a0)
   move.w      #VPHEIGHT,vp_DHeight(a0)

   move.l      #rasinfo,vp_RasInfo(a0)
   move.w      #0,vp_Modes(a0)

   lea         vport(pc),a0
   lea         colortable(pc),a1
   moveq       #NUMCOLS,d0
   jsr         _LVOLoadRGB4(a6)        Loads the struct ColorMap, supra.

   lea         bitmap(pc),a0
   move.l      #DEPTH,d0
   move.l      #WIDTH,d1
   move.l      #HEIGHT,d2
   jsr         _LVOInitBitMap(a6)
 
allocate:

   lea         bitmap+bm_Planes(pc),a2
   moveq       #DEPTH-1,d2

plane:

   move.l      #WIDTH,d0
   move.l      #HEIGHT,d1
   jsr         _LVOAllocRaster(a6)
   move.l      PLANEPTR,(a2)
   beq         closeall

   movea.l     PLANEPTR,a1
   RASSIZE     #WIDTH,#HEIGHT          Macro returns in d0.
   moveq       #0,d1
   jsr         _LVOBltClear(a6)

   adda.l      #4,a2
   dbra        d2,plane

   lea         rasinfo(pc),a0
   move.l      #bitmap,ri_BitMap(a0)
   move.w      #0,ri_RxOffset(a0)
   move.w      #0,ri_RyOffset(a0)
   move.l      #0,ri_Next(a0)

   lea         view(pc),a0
   lea         vport(pc),a1
   jsr         _LVOMakeVPort(a6)

   lea         view(pc),a1
   jsr         _LVOMrgCop(a6)

   lea         rastport(pc),a1
   jsr         _LVOInitRastPort(a6)

   lea         rastport(pc),a1
   move.l      #bitmap,rp_BitMap(a1)

   lea         rastport(pc),a1
   moveq       #0,d0
   jsr         _LVOSetRast(a6)

   lea         view(pc),a1
   jsr         _LVOLoadView(a6)

   rts


*               THIS IS THE ROUTINE THAT YOU CAME TO READ!


draw_line:

   lea      $dff000,a0                    Hardware base.

   move.b   d4,fillcolor                  Save color.  Will reuse d4.

   exg      d0,d2                         x1 to d2.  y1 to d3.
   exg      d1,d3                         x2 to d0.  y2 to d1.

   lea      bitmap,a2                     Recover BitMap.

*  All values to left of the 4th bit in any WORD work out to be a number
*  evenly divisable by 16.  Therefore, such values yield a number of WORDS
*  (or BYTES * 2) when divided by 16.  It follows then that the lower 4
*  bits contain an offset which, when added to these WORD multiples, yield
*  the precise X1 offset to the right of any WORD partition.  This offset
*  can be extracted by ANDing the value of X1 with $F.  Thus, we shall AND
*  d2 (contains X1) with d4 (contains $F) and save it for eventual loading
*  into the upper four bits of BLTCON0 and BLTCON1.

*  WORD and BYTE seem to be used as if they meant the same thing.  The reason
*  is because we eventually arrive at an address by dividing some base value
*  by eight.  This will produce an even number of bytes.  That is the same
*  as saying we have WORD alignment.  Kapeche?

   moveq    #$f,d4
   and.w    d2,d4

   sub.w    d3,d1                         Recover DeltaY in d1.
   mulu     bm_BytesPerRow(a2),d3         Expect 40 or 80.

   sub.w    d2,d0                         Recover DeltaX in d0.
   blt      lineleft                      Line runs right to left.

*  Observe, the above is our sole reference to Cartesian coordinates.

   tst.w    d1                            Test DeltaY.
   blt      line_right_up_wide            Line runs bottom to top.

   cmp.w    d0,d1                         Find Ldelta.
   bge      line_right_down_high          DeltaY is Ldelta.

line_right_down_wide:

*  Since:  x1 < x2; y1 < y2; Ldelta = DeltaX;  Ldelta in d0;  Sdelta in d1;

   moveq    #$11,d7                       BLTCON1 will be 0001 0001.  Code 4
   bra      compute_loads                 Possibility 1 of 8.

line_right_down_high:

*  Since:  x1 < x2; y1 < y2; Ldelta = DeltaY;  Ldelta in d0;  Sdelta in d1;

   moveq    #$1,d7                        BLTCON1 will be 0000 0001.  Code 0
   exg      d1,d0
   bra      compute_loads                 Possibility 2 of 8.

line_right_up_wide:

   neg.w    d1                            Make DeltaY positive.
   cmp.w    d0,d1
   bge      line_right_up_high            Stay if DeltaX is Ldelta.

*  Since:  x1 < x2; y1 > y2; Ldelta = DeltaX;  Ldelta in d0;  Sdelta in d1;

   moveq    #$19,d7                       BLTCON1 will be 0001 1001.  Code 6
   bra      compute_loads                 Possibility 3 of 8.

line_right_up_high:

*  Since:  x1 < x2; y1 > y2; Ldelta = DeltaY;  Ldelta in d0;  Sdelta in d1;

   moveq    #$5,d7                        BLTCON1 will be 0000 0101.  Code 1
   exg      d1,d0
   bra      compute_loads                 Possibility 4 of 8.

lineleft:

   neg.w    d0                            Make DeltaX positive.
   tst.w    d1
   blt      line_left_up_wide             y1 > y2, so branch.

   cmp.w    d0,d1
   bge      line_left_down_high           Ldelta is DeltaY.

line_left_down_wide:

*  Since  x1 > x2; y1 < y2;  Ldelta = Delta X; 

   moveq    #$15,d7                       BLTCON1 will be 0001 0101.  Code 5
   bra      compute_loads                 Possibility 5 of 8.

line_left_down_high:

*  Since:  x1 > x2; y1 < y2;  Ldelta = Delta Y;  Ldelta in d0; Sdelta in d1;

   moveq    #$9,d7                        BLTCON1 will be 0000 1001.  Code 2
   exg      d1,d0
   bra      compute_loads                 Possibility 6 of 8.

line_left_up_wide:

   neg.w    d1                            Make DeltaY positive.
   cmp.w    d0,d1
   bge      line_left_up_high             Ldelta is DeltaY.

*  Since:  x2 < x1; y2 < y1;  Ldelta = DeltaX;

   moveq    #$1d,d7                       BLTCON1 will be 0001 1101.  Code 7
   bra      compute_loads                 Possiblity 7 of 8.

line_left_up_high:

*  Since:  x2 < x1; y2 < y1;  Ldelta = DeltaY;  Ldelta in d0; Sdelta in d1;

   moveq    #$d,d7                        BLTCON1 will be 0000 1101.  Code 3
   exg      d1,d0



*  We now have Ldelta (d0) and Sdelta (d1) isolated.
*  The Octants code bits (4,3,2) are set in d7 for writing to BLTCON1.
*  Observe the LINE Bit (0) is always set.  The SING Bit (1) is reset.
*  X1 is in d2.  Rows * BytesPerRow is in d3 and the shift mask is in d4.


compute_loads:

   add.w    d1,d1                         Compute (2 * SDelta).

*  Dividing X1 by 8 will yield the number of full bytes from the left edge
*  to the starting point of the line.  Observe, always an even number.

   asr.w    #3,d2
   ext.l    d2
   add.l    d2,d3

*  Add this to total number of bytes from 0,0 to the leftmost point of the
*  LINE in which the draw will begin.  Save it.  We eventually add this
*  total sum to the base address of the bitplane under examination and we
*  have the address in which we want to begin the draw!

   move.w   d1,d2                         2 * SDelta
   sub.w    d0,d2                         (2 * Sdelta) - (Ldelta)
   bge      10$                           Nix SIGN bit if 2 * Sdelta > Ldelta

   ori.w    #$40,d7                       Set SIGN bit if Ldelta > 2*Sdelta

10$:

   move.w   d7,d5                         Mere copy BLTCON1 low word.
   movea.w  d2,a3                         Mere copy (2 * Sdelta) - (Ldelta)

*  "Busy-Waits" are evil though simple.  These are so short in duration as
*  to be unworthy of consideration.

busy_wait_1:

   btst     #BBUSY,dmaconr(a0)            Wait for blitter to be free prior
   bne      busy_wait_1                   to any loading.

   move.w   bm_BytesPerRow(a2),bltcmod(a0)
   move.w   d1,bltbmod(a0)                Load (2 * Sdelta).

   move.w   d2,d1                         (2 * Sdelta) - (Ldelta)
   sub.w    d0,d1                         (2 * Sdelta) - (2 * Ldelta)
*                                         We simply subtracted another Ldelta

   move.w   d1,bltamod(a0)                Load (prob. negative) difference.

   moveq    #-1,d1                        All bltafwm bits mandatorily set.
   move.w   d1,bltafwm(a0)

   move.w   #$8000,bltadat(a0)            Mandatory value for any line draw.
   move.w   d6,bltbdat(a0)                Pattern.  If pattern won't vary,
*                                         use a constant here instead.

   addq.w   #1,d0                         Incr Ldelta linelength so that 1st
*                                         and last points both considered.


*  Bits 15-6 of bltsize must contain "height" (the length) value of LDelta.
*  Bits 5-0 contain width.  This is a mandatory value of 2.

   asl.w    #6,d0                         Shift Ldelta to begin with bit 6.

*  Some material has you multiply Ldelta by 64.  Same as a 6 bit left shift.

   addq.w   #2,d0                         Add mandatory width value of 2.
*                                         These values go into BLTSIZE.


*  We are nearly done.  Let's finish the BLTCONs.
*  Here we have the Octant code in d7, and the Shift in d4.  We must do the
*  many seemingly useless swaps in order to "move" new values into some
*  registers.  Recall, Motorola instruction set permits one to "move" only
*  into the LOW order byte, or word.  We can then load both BLTCONs with one
*  LONG move.  Perhaps, you can rewrite this and save an instruction.


   move.w   d4,d2          Copy low order word with the shift nybble to d2.
   swap     d4             Shift now in high order word, 5th nybble of the 8
   asr.l    #4,d4          Nybble shifts -> to m.s. nybble of low order word.
   ori.w    #$0b00,d4      and is replaced w. mandatory USEx bits of 1101.

   swap     d5             Before swap, d5 was SIGN, 0, CODE, SING & LINE.
   move.w   d4,d5          Adds the $0b00 to low order word.

   swap     d5             Now we have the shift mask, USEx bits, and room
*                          for a MinTerm in high word, and a fully prepared
*                          BLTCON1 in the low order word.

   clr.w    d1                            Set d1 to zeros.
   move.b   bm_Depth(a2),d2               Number of planes

   lea      bm_Planes(a2),a2              Pointer to first plane pointer.

planesloop:

   move.l   (a2)+,d7                      Copy and point to next pointer.
   swap     d5                            Empty BLTCON0 byte to lowest order.

   btst.b   d1,fillcolor                  Is color register bit corresponding
*                                         to this plane set?
   beq      10$

   move.b   #$ca,d5                       Standard MinTerm if color bit set.
   bra      load_address                  See article.

10$:

   move.b   #$0a,d5                       Standard MinTerm color bit reset.

load_address:

   swap     d5                            Get BLTCON0 ahead of BLTCON1 in d5.
   add.l    d3,d7                         Calculate exact address start word.

*  Recall from above that d3 contains the number of bytes to be added to the
*  address of bitplane 0,0 to arrive at the address of the start of the line.
*  The blitter takes care of the intra-WORD shift for us.


busy_wait_2:

   btst     #BBUSY,dmaconr(a0)
   bne      busy_wait_2                   Wait again for blitter to be free

   move.l   d5,bltcon0(a0)                DOUBLE LOAD - BLTCON0 and BLTCON1.

*             The above is filling a pint with a quart pot.
*             The overflow goes into the next succeeding WORD: BLTCON1.

   move.w   a3,bltaptl(a0)                2 * SDelta - LDelta goes in here.

*  These two must contain the address of the WORD in which the line starts.

   move.l   d7,bltcpt(a0)                 Starting address of line.
   move.l   d7,bltdpt(a0)                 And in here, as well.

   move.w   d0,bltsize(a0)                Load "size" and start blitter


*  The Amiga uses color indirection.  Ergo, we can compare the active plane
*  with its corresponding bit in the color register chosen.  Plane 1, look
*  to the first bit,  Plane 2, look to the second bit, etc.  When the bit
*  is reset for a given bitplane, we use the minterm of $0a so the dma is
*  turned off, and nothing is "drawn".  When the corresponding bit is set,
*  we call for a minterm of $ca, turning on dma so that the points are
*  made to appear.  Your color table ($RGB) sees to it that the color of
*  choice is the color in which the line is drawn.  You may experiment here
*  with no harm.  The output appearance can vary dramatically.


   addq.w   #1,d1                         Incr. the test color reg. bit.

   cmp.b    d1,d2                         Cf. color number to planes depth.
   bne      planesloop

   rts


*                          Our Simple Data Section


gfxname:       GRAFNAME
dosname:       DOSNAME
colortable:    dc.w  $000,$fff,$f00,$0f0,$00f,$ff0,$d59,$59d

GfxBase:       ds.l   1
DosBase:       ds.l   1
mytask:        ds.l   1
returnmsg:     ds.l   1
cm:            ds.l   1
oldview:       ds.l   1
view:          ds.b   v_SIZEOF
vport:         ds.b   vp_SIZEOF
bitmap:        ds.b   bm_SIZEOF
rastport:      ds.b   rp_SIZEOF
rasinfo:       ds.b   ri_SIZEOF
fillcolor:     ds.w   1
loopcounter:   ds.w   1
                                end


