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. |
![]() |
| 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 |