Mandelbrot v1 for Amiga

 
This is very simple program, I just wanted to refresh my forgotten understanding of Amiga programming. It was written for Kickstart 1.3, as I used to have Amiga 500, but it surely works with other versions of AmigaOS as well. It opens the windows, resizes it to the desired size, draws Mandelbrot set to it, pixel by pixel, using fast floating point math. The window could be closed at any time during or after calculation.
When I have time and energy I can add version 2 or 3 with fixed point math or rectangular algorithm.
Below you can see two versions of the program window, one with my WB colors setting and the other with standard WB 1.x palette.
 
The ASM source for Maxon Assembler, Amiga executable and icon (from Maxon ASM) could be downloaded here.
 
 
picture  picture
 
Short explanation of the code:
 
Basic definitions (line 1)
Instead of including definitions I put the used symbolic names right into the source code, just to see what I use.

Startup code (line 72)
This is more or less standard startup code for Amiga programs. It checks whether it was started from Workbench or CLI and acts accordingly.

The main program (line 114)
This part starts with two subroutines:
ExecWait for waiting for the message like NEWSIZE or CLOSEWINDOW (line 119), getting it and replying to it. It has an extra entry point GetReplyMsg (line 128), if we just want to peek the message and not wait for it, like if we need to check, whether user wants to close the window in the middle of calculation.
The other one, DrawPoint (line 142), draws the pixel with desired color WB palette 0 - WB palette 3. This subroutine also recalculates the coordinates, as on Amiga the user can draw to an entire surface of a window, including gadget or border areas. There is one type of window (GimmeZeroZero) which addresses this issue, but it takes more system resources.
The idea was kind of isolate all (possible) OS calls here and put the application logic to the next section.
The entry point of the whole application logic (line 153) is right in this section. This part takes care of opening and closing the necessary libraries (intuition, graphics and mathffp), opening and resizing the main application window and final waiting when user closes it. And of course it calls the main calculation.
The reason why the window is resized is that I wanted to ensure that the drawable area is of desired size (APP_WIDTH * APP_HEIGHT). One unusual quirk is that SizeWindow is done asynchronically on Amiga OS.

Calculation itself (line 252)
The only subroutine here is GetPointColor (line 256) which chooses one of four palette colors depending on number of iterations.
The calculation itself (line 276) is done in floating point via mathffp.library. This library is faster than its IEEE counterparts, on the other hand it cannot get use of math coprocessor (if present).
As the format used by mathffp.library is Motorola FFT: MMMMMMMM MMMMMMMM MMMMMMMM SEEEEEEE, where M is the mantissa, S is the sign bit and E is the exponent, I can do a small optimalization trick, which is to increase the least important byte by one, instead of multiplication the whole number by 2.0.
The calculated area is x=<-2.25,0.75>, y=<-1.125,1.125>, ratio is 4:3, so the y axis must be incremented twice as much as x axis.
The algorithm for calculation the number of iterations is below. The first iteration where zr=0 and zi=0 can be safely skipped as it always gives the same result. Therefore I begin here with iter=1 and zr=cr and zi=ci.
iter=1;
zr=cr;
zi=ci;
do {
    zr2 = zr * zr;
    zi2 = zi * zi;
    if (zr2 + zi2 >= 4.0) break;
    zi = 2.0 * zr * zi;
    zr = zr2 - zi2;
    zr += cr;
    zi += ci;
    iter++;
} while (true);
 
Commented source in color follows...
 
001  APP_WIDTH           equ 320  ; width of drawable canvas
002  APP_HEIGHT          equ 121  ; height of drawable canvas
003  MAX_ITER            equ 75   ; max iterations of Mandebrot
004
005  SysBase             equ  4   ; base of exec.library
006
007  ; exec offsets
008  _LVOForbid          equ -132 ; no params
009  _LVOFindTask        equ -294 ; a1=name
010  _LVOWait            equ -318 ; d0=signalSet
011  _LVOGetMsg          equ -372 ; a0=port
012  _LVOReplyMsg        equ -378 ; a1=message
013  _LVOWaitPort        equ -384 ; a0=port
014  _LVOCloseLibrary    equ -414 ; a1=library
015  _LVOOpenLibrary     equ -552 ; a1=libname, d0=version
016
017  ; intuition offsets
018  _LVOCloseWindow     equ -72  ; a1=window
019  _LVOOpenWindow      equ -204 ; a0=newWindow
020  _LVOSizeWindow      equ -288 ; a0=window, d0=dX, d1=dY
021
022  ; graphics offsets
023  _LVOWritePixel      equ -324 ; a1=RPort, d0=X, d1=Y
024  _LVOSetAPen         equ -342 ; a1=RPort, d0=pen
025
026  ; mathffp offsets
027  _LVOSPFlt           equ -36  ; d0=(float)d0
028  _LVOSPCmp           equ -42  ; d0=1 if d1>d0, d0=-1 if d1<d0, d0=0 if d0=d1
029  _LVOSPAdd           equ -66  ; d0=d0+d1
030  _LVOSPSub           equ -72  ; d0=d0-d1
031  _LVOSPMul           equ -78  ; d0=d0*d1
032  _LVOSPDiv           equ -84  ; d0=d0/d1
033
034  ; messagePort structure offset
035  MP_SIGBIT           equ $0f
036
037  ; process structure offsets
038  pr_MsgPort          equ $5c
039  pr_CLI              equ $ac
040
041  ; window structure offsets
042  wd_RPort            equ $32
043  wd_BorderLeft       equ $36
044  wd_BorderTop        equ $37
045  wd_BorderRight      equ $38
046  wd_BorderBottom     equ $39
047  wd_UserPort         equ $56
048
049  ; window structure constants (gadgets)
050  WINDOWDRAG          equ 2
051  WINDOWDEPTH         equ 4
052  WINDOWCLOSE         equ 8
053
054  ; IDCMP (Intuition Device Communication Message Port) flags
055  ; event messages which we may receive
056  NEWSIZE             equ $0002
057  CLOSEWINDOW         equ $0200
058
059  ; new window should be on WorkBench screen
060  WBENCHSCREEN        equ 1
061
062  ; extra flags
063  SMART_REFRESH       equ 0
064  ACTIVATE            equ $1000
065
066  ; ffp constants needed for calculation
067  FFP__2_25           equ $900000C2 ; -2.25
068  FFP__1_125          equ $900000C1 ; -1.125
069  FFP_3               equ $C0000042 ; 3.0
070  FFP_4               equ $80000043 ; 4.0
071
072  ; ====================
073  ; === Startup code ===
074  ; ====================
075
076                  move.l  SysBase.w,a6        ; a good way how to start :-)
077                  movem.l d0/a0,-(sp)         ; save command line
078
079                  ; Test from where the program was run (WB or CLI)
080                  sub.l   a1,a1               ; a1=0 => own task
081                  jsr     _LVOFindTask(a6)    ; where are we?
082                  move.l  d0,a4               ; save address
083                  tst.l   pr_CLI(a4)          ; called from WorkBench?
084                  beq.s   fromWorkbench       ; if so
085
086                  ; Start from CLI
087                  movem.l (sp)+,d0/a0         ; get the command line params
088                  bra.s   run                 ; and run the program
089
090                  ; Start from Workbench
091  fromWorkbench   lea     pr_MsgPort(a4),a0
092                  jsr     _LVOWaitPort(a6)    ; wait for start message
093                  lea     pr_MsgPort(a4),a0   ; it's here
094                  jsr     _LVOGetMsg(a6)      ; take it
095                  move.l  d0,_WBenchMsg       ; save it
096                  movem.l (sp)+,d0/a0         ; restore the stack
097
098  run             bsr.s   _main               ; run the main program
099
100                  move.l  d0,-(sp)            ; save the return code
101
102                  tst.l   _WBenchMsg          ; check the WB message
103                  beq.s   _exit               ; zero => we're called from CLI
104
105                  jsr     _LVOForbid(a6)      ; no interrupts between reply
106                  move.l  _WBenchMsg(pc),a1   ; and return to the OS
107                  jsr     _LVOReplyMsg(a6)    ; reply to the message
108
109  _exit           move.l  (sp)+,d0            ; restore the return code
110                  rts                         ; and return to the OS
111
112  _WBenchMsg      dc.l    0                   ; preset the value to zero
113
114  ; ===================
115  ; === Our program ===
116  ; ===================
117
118                  ; wait for any message to our window
119  ExecWait        move.l  windowptr(pc),a0    ; Window structure pointer
120                  move.l  wd_UserPort(a0),a0  ; get its MessagePort pointer
121                  move.b  MP_SIGBIT(a0),d1    ; get signal bit position
122                  moveq   #0,d0               ; convert it to mask
123                  bset    d1,d0
124                  move.l  SysBase.w,a6
125                  jsr     _LVOWait(a6)        ; wait and return
126
127                  ; another entry point without waiting
128  GetReplyMsg     move.l  windowptr,a0        ; our window
129                  move.l  wd_UserPort(a0),a0  ; its user port
130                  move.l  SysBase.w,a6
131                  jsr     _LVOGetMsg(a6)      ; any message
132                  tst.l   d0                  ; received?
133                  beq.s   GetReplyMsg_0       ; no => normal return
134
135                  move.l  d0,a1               ; if so then reply to it
136                  jsr     _LVOReplyMsg(a6)
137                  moveq   #1,d0               ; and set the result
138
139  GetReplyMsg_0   rts
140
141                  ; draw point, a2=x, a3=x, d0=color
142  DrawPoint       move.l  RPort(pc),a1
143                  move.l  _GfxBase(pc),a6
144                  jsr     _LVOSetAPen(a6)     ; set pen color (1 of 4)
145                  move.l  RPort(pc),a1
146                  move.l  a2,d0               ; pos_x
147                  add.w   BorderLeft(pc),d0
148                  move.l  a3,d1               ; pos_y
149                  add.w   BorderTop(pc),d1
150                  jmp     _LVOWritePixel(a6)  ; write pixel and return
151
152                  ; open intuition.library
153  _main           move.l  SysBase.w,a6        ; not really needed, it's there
154                  lea     intname(pc),a1      ; from startup code
155                  moveq   #0,d0
156                  jsr     _LVOOpenLibrary(a6)
157                  tst.l   d0
158                  beq     quit
159                  move.l  d0,_IntuitionBase   ; save the pointer
160
161                  ; open graphics.library
162                  lea     grafname(pc),a1
163                  moveq   #0,d0
164                  jsr     _LVOOpenLibrary(a6)
165                  tst.l   d0
166                  beq     closeint
167                  move.l  d0,_GfxBase         ; save the pointer
168
169                  ; open mathffp.library
170                  lea     mathname(pc),a1
171                  moveq   #0,d0
172                  jsr     _LVOOpenLibrary(a6)
173                  tst.l   d0
174                  beq     closegraf
175                  move.l  d0,_MathBase        ; save the pointer
176
177                  ; open the window
178                  lea     windowdef(pc),a0    ; newwindow structure
179                  move.l  _IntuitionBase(pc),a6
180                  jsr     _LVOOpenWindow(a6)  ; open the window
181                  tst.l   d0                  ; error?
182                  beq     closemath           ; yes => close the program
183                  move.l  d0,windowptr        ; save the window pointer
184                  move.l  d0,a0
185                  move.l  wd_RPort(a0),RPort  ; and its rastport
186
187                  moveq   #0,d0               ; size of window frame
188                  move.b  wd_BorderLeft(a0),d0
189                  move.w  d0,BorderLeft       ; save wd_BorderLeft
190                  add.b   wd_BorderRight(a0),d0
191
192                  moveq   #0,d1
193                  move.b  wd_BorderTop(a0),d1
194                  move.w  d1,BorderTop        ; save wd_BorderTop
195                  add.b   wd_BorderBottom(a0),d1
196
197                  ; resize the window so that the inner part (canvas) where we
198                  ; can draw is of desired size APP_WIDTH * APP_HEIGHT
199                  move.l  windowptr(pc),a0    ; SizeWindow is asynchronous
200                  jsr     _LVOSizeWindow(a6)  ; we have to wait when it is done
201                  bsr     ExecWait            ; so wait for NEWSIZE message
202
203                  bsr     CalcMandelbrot      ; perform the main calculation
204
205                  move.l  windowptr(pc),a0    ; close the window
206                  move.l  _IntuitionBase(pc),a6
207                  jsr     _LVOCloseWindow(a6)
208
209  closemath       move.l  SysBase.w,a6        ; close the libraries
210                  move.l  _MathBase(pc),a1
211                  jsr     _LVOCloseLibrary(a6)
212
213  closegraf       move.l  _GfxBase(pc),a1
214                  jsr     _LVOCloseLibrary(a6)
215
216  closeint        move.l  _IntuitionBase(pc),a1
217                  jsr     _LVOCloseLibrary(a6)
218
219  quit            moveq   #0,d0               ; no error code
220                  rts                         ; and finish
221
222  windowdef       dc.w    100,20              ; left top corner
223                  dc.w    APP_WIDTH,APP_HEIGHT; width and height
224                  dc.b    -1,-1               ; colors
225                  dc.l    CLOSEWINDOW!NEWSIZE ; IDCMP flags
226                  dc.l    W_Gadgets!W_Extras  ; window flags
227                  dc.l    0                   ; no user gadgets
228                  dc.l    0                   ; standard check mark
229                  dc.l    W_Title             ; window title
230                  dc.l    0                   ; no custom screen
231                  dc.l    0                   ; no SuperBitmap
232                  dc.w    100,20              ; minimal size
233                  dc.w    640,200             ; maximal size
234                  dc.w    WBENCHSCREEN        ; use Workbenche screen
235
236  W_Gadgets       equ     WINDOWDRAG!WINDOWDEPTH!WINDOWCLOSE
237  W_Extras        equ     SMART_REFRESH!ACTIVATE
238  W_Title         dc.b    ' Mandelbrot v1 ',0
239
240  intname         dc.b    'intuition.library',0
241  grafname        dc.b    'graphics.library',0,0
242  mathname        dc.b    'mathffp.library',0
243
244  _IntuitionBase  ds.l    1                   ; opened libraries
245  _GfxBase        ds.l    1
246  _MathBase       ds.l    1
247  windowptr       ds.l    1                   ; our window
248  RPort           ds.l    1                   ; rastport of our window
249  BorderLeft      ds.w    1                   ; x axis shift
250  BorderTop       ds.w    1                   ; y axis shift
251
252  ; ==============================
253  ; === Mandelbrot calculation ===
254  ; ==============================
255
256  GetPointColor   moveq   #0,d0
257                  cmp.w   #MAX_ITER,d2        ; iter==MAX_ITER?
258                  beq.s   Color_Set           ; then use pen 0
259                  divu    #3,d2
260                  swap    d2                  ; otherwise use
261                  move.w  d2,d0               ; pen iter%3+1
262                  addq.b  #1,d0
263  Color_Set       rts
264
265                  ; register usage:
266                  ; D0 - clobbered   A0 - clobbered
267                  ; D1 - clobbered   A1 - clobbered
268                  ; D2 - iter        A2 - pos_x
269                  ; D3 - x_step      A3 - pos_y
270                  ; D4 - zr2         A4 - cr
271                  ; D5 - zi2         A5 - ci
272                  ; D6 - zr          A6 - _MathBase
273                  ; D7 - zi          A7 - USP
274
275                  ; main loop
276  CalcMandelbrot  move.l  _MathBase(pc),a6
277                  move.l  #APP_WIDTH,d0
278                  jsr     _LVOSPFlt(a6)
279                  move.l  d0,d1
280                  move.l  #FFP_3,d0
281                  jsr     _LVOSPDiv(a6)
282                  move.l  d0,d3               ; x_step=3.0/APP_WIDTH
283
284                  sub.l   a3,a3               ; pos_y=0
285
286  loop_Y          move.l  a3,d0
287                  jsr     _LVOSPFlt(a6)
288                  move.l  d3,d1
289                  addq.b  #1,d0               ; pos_y*2
290                  jsr     _LVOSPMul(a6)
291                  move.l  #FFP__1_125,d1
292                  jsr     _LVOSPAdd(a6)
293                  move.l  d0,a5               ; ci=2*pos_y*3.0/width-1.125
294
295                  sub.l   a2,a2               ; pos_x=0
296
297  loop_X          move.l  a2,d0
298                  jsr     _LVOSPFlt(a6)
299                  move.l  d3,d1
300                  jsr     _LVOSPMul(a6)
301                  move.l  #FFP__2_25,d1
302                  jsr     _LVOSPAdd(a6)
303                  move.l  d0,a4               ; cr=pos_x*3.0/width-2.25
304
305                  moveq   #1,d2               ; cycle with iter==0 skipped
306                  move.l  a4,d6               ; zr=cr
307                  move.l  a5,d7               ; zi=ci
308
309  loop_iter       move.l  d6,d0
310                  move.l  d6,d1
311                  jsr     _LVOSPMul(a6)
312                  move.l  d0,d4               ; zr2=zr*zr
313
314                  move.l  d7,d0
315                  move.l  d7,d1
316                  jsr     _LVOSPMul(a6)
317                  move.l  d0,d5               ; zi2=zi*zi
318
319                  move.l  d4,d0
320                  move.l  d5,d1
321                  jsr     _LVOSPAdd(a6)       ; zr2+zi2
322                  move.l  #FFP_4,d1
323                  jsr     _LVOSPCmp(a6)       ; zr2+zi2>=4.0
324                  tst.l   d0
325                  bmi.s   quit_iter           ; yes => quit iteration
326
327                  move.l  d6,d0
328                  move.l  d7,d1
329                  jsr     _LVOSPMul(a6)       ; zr*zi
330                  addq.b  #1,d0               ; 2.0*zr*zi
331                  move.l  d0,d7               ; zi=2.0*zr*zi
332
333                  move.l  d4,d0
334                  move.l  d5,d1
335                  jsr     _LVOSPSub(a6)
336                  move.l  d0,d6               ; zr=zr2-zi2
337
338                  move.l  d6,d0
339                  move.l  a4,d1
340                  jsr     _LVOSPAdd(a6)
341                  move.l  d0,d6               ; zr+=cr
342
343                  move.l  d7,d0
344                  move.l  a5,d1
345                  jsr     _LVOSPAdd(a6)
346                  move.l  d0,d7               ; zi+=ci
347
348                  addq.w  #1,d2               ; iter++
349                  cmp.w   #MAX_ITER,d2
350                  bne.s   loop_iter
351
352  quit_iter       bsr     GetPointColor       ; convert iter to 4 colors
353                  bsr     DrawPoint           ; and draw the point
354                  move.l  _MathBase(pc),a6    ; restore _MathBase
355
356                  addq.w  #1,a2               ; pos_x++
357                  cmp.w   #APP_WIDTH,a2
358                  bne     loop_X
359
360                  bsr.    GetReplyMsg
361                  bne.s   fini                ; CLOSEWINDOW message received
362                  move.l  _MathBase(pc),a6    ; restore _MathBase
363
364                  addq.w  #1,a3               ; pos_y++
365                  cmp.w   #APP_HEIGHT,a3
366                  bne     loop_Y
367
368                  bsr     ExecWait            ; wait for CLOSEWINDOW message
369
370  fini            rts
371
372                  END