![]() |
Forum Index : Microcontroller and PC projects : Fractal Images
Page 1 of 3 ![]() ![]() |
|||||
Author | Message | ||||
Geoffg![]() Guru ![]() Joined: 06/06/2011 Location: AustraliaPosts: 3292 |
I have had a bit of fun experimenting with some fractal image programs on the Micromite with a colour LCD. These were originally posted by Loki for the original monochrome Maximite back in 2012. Julia set fractal on a 2.2" ILI9341 LCD: Mandelbrot set fractal on a 7" SSD1963 LCD: The programs (modified to run on the Micromite or Micromite Plus) are listed below. Be warned, they can take a LONG time to finish: 'JULIA.BAS - Draws Julia set fractal images
'by loki Dim As Integer MAXIT, COUNT Cls 'Specify initial values RealOffset = -1.30 ImaginOffset = -0.95 '------------------------------------------------* 'Set the Julia set constant [eg C = -1.2 + 0.8i] CRealVal = -0.78 CImagVal = -0.20 '------------------------------------------------* MAXIT=80 'max iterations GAP = MM.VRes / MM.HRes SIZE = 2.50 XDelta = SIZE / MM.HRes YDelta = (SIZE * GAP) / MM.VRes 'Loop processing - visit every pixel For X = 0 To (MM.HRes - 1) CX = X * Xdelta + RealOffset For Y = 0 To (MM.VRes - 1) CY = Y * YDelta + ImaginOffset Zr = CX Zi = CY COUNT = 0 'Begin Iteration loop Do While (( COUNT <= MAXIT ) And (( Zr * Zr + Zi * Zi ) < 4 )) new_Zr = Zr * Zr - Zi * Zi + CRealVal new_Zi = 2 * Zr * Zi + CImagVal Zr = new_Zr Zi = new_Zi COUNT = COUNT + 1 Loop PIXEL X, Y, (COUNT and 4) * 4177920 + (COUNT and 2) * 32640 + (COUNT and 1) * 255) Next Y Next X 'MANDELBROT.BAS - Draws mandelbrot set fractal images
' by loki Dim As Integer MAXIT, COUNT 'Specify initial values RealOffset = -2.0 ImaginOffset = -0.85 MAXIT=70 'max iterations GAP = MM.VRES / MM.HRES SIZE = 2.8 XDelta = SIZE / MM.HRES YDelta = (SIZE * GAP) / MM.VRES CLS 'Loop processing - visit every pixel in screen area and base colour of pixel ' on the number of iterations required to escape boundary conditions ' If count hits max iterations then pixel hasn't escaped and is part of the set (the 'inner sea') FOR X = 0 TO (MM.HRES - 1) Cx = X * Xdelta + RealOffset FOR Y = 0 TO (MM.VRES - 1) Cy = Y * YDelta + ImaginOffset Zr = 0.0 Zi = 0.0 COUNT = 0 'Begin Iteration loop, checking boundary conditions on each loop DO WHILE (( COUNT <= MAXIT ) AND (( Zr * Zr + Zi * Zi ) <= 4 )) new_Zr = Zr * Zr - Zi * Zi + Cx new_Zi = 2 * Zr * Zi + Cy Zr = new_Zr Zi = new_Zi COUNT = COUNT + 1 LOOP PIXEL X, Y, (COUNT and 4) * 4177920 + (COUNT and 2) * 32640 + (COUNT and 1) * 255) NEXT Y NEXT X Geoff Graham - http://geoffg.net |
||||
WhiteWizzard Guru ![]() Joined: 05/04/2013 Location: United KingdomPosts: 2944 |
The bottom image is the first thing I tried on a 2.4" SPI display when matherp's code first came out. It would be interesting to see the speed difference on a MM+ with a 800x480 SSD display (something else just added to my to-do list!) Geoff: How long did the first image take to create (and on what 'spec' MM)?? |
||||
twofingers![]() Guru ![]() Joined: 02/06/2014 Location: GermanyPosts: 1593 |
Thanks Geoff! Something to play with ... ![]() JULIA.BAS takes 519688ms on a uM2/2.4 LCD (CPU 48). Maybe it needs some CFunction optimizing ... ![]() Regards Michael causality ≠ correlation ≠ coincidence |
||||
Geoffg![]() Guru ![]() Joined: 06/06/2011 Location: AustraliaPosts: 3292 |
The first image (Julia) took about 10 minutes (as reported by twofingers) on a MM MkII with a 2.2" ILI9341 LCD. The second using a MM+ (100MHz) on a 7" SSD1963 LCD took an hour or two (I did not time it). The more pixels the longer it takes. Geoff Graham - http://geoffg.net |
||||
loki Newbie ![]() Joined: 23/12/2011 Location: AustraliaPosts: 7 |
Thank you Geoff for your work on these. It gave me a warm and fuzzy feeling knowing others are having a bit of fun with fractals ![]() I've been offline the last few years due to my wife being very ill with breast cancer, and in fact she passed away 12 mths ago so I haven't had a lot of time for hobbies, but I'm slowly getting back in to it. I have one or two projects in mind for the maximite/micromite and if they succeed will share them on the backshed. I'd like to take this oppurtunity to thank the backshed and all of the contributors to it, as it has helped me to avoid becoming depressed as I've followed the various discussions and projects and kept me interested in others, and what they're working on. In the meantime I've been working on a Windows fractal program in C# that was until recently using the SetPixel() command to colour each pixel. I've been able to speed things up by writing the pre-prepared array of colour bytes directly to the bitmap memory location, then calling the Invalidate() method on the picture box control (that has the bitmap bound to it), which causes Windows to invalidate the entire surface of the control thus forcing it to be redrawn. Don't know if this approach could be used on the mites or not. Is video memory directly accessible in any way, rather than use the PIXEL command? Is it possible to create a new api in mmbasic that would be analagous to microsoft's DirectX api? thanks again cheers rob (loki) |
||||
Geoffg![]() Guru ![]() Joined: 06/06/2011 Location: AustraliaPosts: 3292 |
In the Colour Maximite you can write direct to the video memory buffer and you can get the address of this buffer using PEEK. In the Micromites each pixel is sent over a SPI or parallel bus to the display which buffers it there. But, when experimenting with your programs I found that the limitation was the speed of the interpreter, not the display. I bet that it would be the same even if you were writing in C In the Micromite you can load C routines using the CSub/CFunction facility. It is not an easy environment (eg, it is difficult using floats) but it would be fast. Geoff Geoff Graham - http://geoffg.net |
||||
vegipete![]() Guru ![]() Joined: 29/01/2013 Location: CanadaPosts: 1132 |
Fascinating. I do a certain amount of embedded programming, often on 8 and 16 bit PICs. With their limited cpu power, attention must be paid to coding efficiency. Complex math is to be avoided, although for some chips, multiplication isn't too costly. So I looked at the sample code above, saw that certain multiplications were performed twice and wanted to see if the speed would change if the multiplications were performed once and the results re-used. Here's what I did to the Julia.BAS program: 'Loop processing - visit every pixel
Yup, it changed. It actually got slower! The original took 5:50 on my MM+, my changed code took 6:50, a whole minute slower. Hmmm.
For X = 0 To (MM.HRes - 1) CX = X * Xdelta + RealOffset For Y = 0 To (MM.VRes - 1) CY = Y * YDelta + ImaginOffset Zr = CX Zi = CY ZZrr = Zr * Zr '**************** added ZZii = Zi * Zi '**************** added COUNT = 0 'Begin Iteration loop DO '**************** changed loop type 'Do While (( COUNT <= MAXIT ) And (( Zr * Zr + Zi * Zi ) < 4 )) 'new_Zr = Zr * Zr - Zi * Zi + CRealVal new_Zr = ZZrr - ZZii + CRealVal '**************** changed new_Zi = 2 * Zr * Zi + CImagVal Zr = new_Zr Zi = new_Zi COUNT = COUNT + 1 ZZrr = Zr * Zr '**************** added ZZii = Zi * Zi '**************** added 'Loop '**************** changed loop type LOOP until ((COUNT > MAXIT) or (( ZZrr + ZZii ) > 4 )) PIXEL X, Y, (COUNT and 4) * 4177920 + (COUNT and 2) * 32640 + (COUNT and 1) * 255) Next Y Next X OK, a quick test. I moved 2 comment lines that I added outside the DO LOOP UNTIL. That took off 10 seconds! All, right, next I removed every comment I had added. That took off another 13 seconds, down to 6:27. But that is still a long way to go to get back to 5:50! Wow. Next I'll try reducing my modified program down to the same number of lines in the working loop as the orignal... ========================= Rob, best wishes. Visit Vegipete's *Mite Library for cool programs. |
||||
WhiteWizzard Guru ![]() Joined: 05/04/2013 Location: United KingdomPosts: 2944 |
@vegipete Something that immediately sprung to mind while reading your post was the fact that in some 'old' versions of BASIC that I have used, program execution was improved by using short(er) variable names. This use to really help in most scenarios (although debugging then became a bit trickier). I'm guessing things are 'tokenised' in MMBasic, but will raise the question anyway: will using single-letter variable names result in a faster running program?? WW |
||||
matherp Guru ![]() Joined: 11/12/2012 Location: United KingdomPosts: 10310 |
This was drawn much much faster - 10 seconds ![]() I've converted the JULIA code to a CSUB as an exercise. This allows you to set the four parameters CRealVal, CImagVal, RealOffset, ImaginOffset in the call to the CSUB. Note that because of a limitation in CFunctions and CSUBs you have to call JULIA with variables rather than literals. In the code I've included all the example parameters from loki's original post and the program steps through each one in turn taking about 10 seconds each on a ILI9341 driven by a MM+. The code should work on any Micromite with any of the supported display types I would like to increase the colour resolution by altering the way the RGB code is constructed in the DrawPixel statement but so far my attempts have failed - any suggestions appreciated. [code] OPTION EXPLICIT OPTION DEFAULT NONE DIM FLOAT RealOffset = -1.30 ,ImaginOffset = -0.95, CRealVal ,CImagVal DIM INTEGER i,NPatterns=13 DATA -0.71,0.35 DATA -0.64,0.42 DATA -0.61,0.44 DATA -0.59,0.46 DATA -0.59,-0.66 DATA -0.60,-0.66 DATA -0.80,0.39 DATA -0.62,-0.45 DATA -0.58,-0.65 DATA -0.71,-0.52 DATA -0.73,-0.22 DATA -0.78,-0.20 DATA 0.29,-0.49 for i=1 to Npatterns RealOffset = -1.30 ImaginOffset = -0.95 read CRealVal, CImagVal julia(CRealVal, CImagVal, RealOffset, ImaginOffset) NEXT i end CSUB julia 00000000 27BDFFA8 AFBF0054 AFBE0050 AFB7004C AFB60048 AFB50044 AFB40040 AFB3003C AFB20038 AFB10034 AFB00030 AFA40058 AFA5005C AFA60060 AFA70064 3C109D00 8E02009C 0040F809 3C044020 00408821 8E02009C 0040F809 3C044080 0040B821 8E02009C 0040F809 3C044000 AFA20018 8E130064 8E030098 8E020080 8C640000 0040F809 00002821 00409021 8E030094 8E020080 8C640000 0040F809 00002821 02402021 0260F809 00402821 00409821 8E120064 8E030094 8E020080 8C640000 0040F809 00002821 02202021 0240F809 00402821 AFA2002C 8E120064 8E020058 02202021 0040F809 02602821 00408821 8E030098 8E020080 8C640000 0040F809 00002821 02202021 0240F809 00402821 AFA20028 8E020094 8C420000 10400085 3C119D00 AFA00020 241EFFFF 8E32005C 8E300058 8E220080 8FA40020 0040F809 00042FC3 00402021 0200F809 8FA5002C 00402021 8FA20060 0240F809 8C450000 AFA20024 8E220098 8C420000 10400069 8FA20020 AFA0001C 8E32005C 8E300058 8E220080 8FA4001C 0040F809 00042FC3 00402021 0200F809 8FA50028 00402021 8FA30064 0240F809 8C650000 00409021 8FB30024 10000025 0000A021 8E350060 8E220058 02602021 0040F809 02602821 00408021 8E220058 02402021 0040F809 02402821 02002021 02A0F809 00402821 00402021 8FA20058 02C0F809 8C450000 0040B021 8E35005C 8E300058 02602021 0200F809 02402821 8FA40018 0200F809 00402821 00402021 8FA3005C 02A0F809 8C650000 26940001 24030051 52830017 8E230048 00409021 02C09821 8E360068 8E35005C 8E220058 02602021 0040F809 02602821 00408021 8E220058 02402021 0040F809 02402821 02002021 02A0F809 00402821 00402021 02C0F809 02E02821 505EFFCA 8E36005C 8E230048 32820002 000221C0 000213C0 00442023 32820004 00022B80 00021580 00451023 00821021 32940001 00142200 0094A023 0054A021 AFB40010 8C620000 8FA40020 8FA5001C 00803021 0040F809 00A03821 8FA2001C 24420001 AFA2001C 8E220098 8C420000 8FA3001C 0062102B 5440FF9C 8E32005C 8FA20020 24420001 AFA20020 8E220094 8C420000 8FA30020 0062102B 5440FF80 8E32005C 8FBF0054 8FBE0050 8FB7004C 8FB60048 8FB50044 8FB40040 8FB3003C 8FB20038 8FB10034 8FB00030 03E00008 27BD0058 End CSUB [/code] The C code is attached so you can see how easy it was to do the port, just a question of converting the operators to function calls: [code] void julia(float *CRealVal, float *CImagVal, float *RealOffset, float *ImaginOffset){ long MAXIT=80,COUNT,X,Y; float XDelta, YDelta, CX, CY, Zr, Zi, new_Zr, new_Zi, GAP; float SIZE=LoadFloat(0x40200000), four=LoadFloat(0x40800000), two=LoadFloat(0x40000000); GAP=FDiv(IntToFloat(VRes),IntToFloat(HRes)); XDelta=FDiv(SIZE,IntToFloat(HRes)); YDelta=FDiv(FMul(SIZE, GAP),IntToFloat(VRes)); for(X=0;X<HRes;X++){ CX=FAdd(FMul(IntToFloat(X),XDelta),*RealOffset); for(Y=0;Y<VRes;Y++){ CY=FAdd(FMul(IntToFloat(Y),YDelta),*ImaginOffset); Zr=CX; Zi=CY; COUNT=0; while((COUNT<=MAXIT) && (FCmp(FAdd(FMul(Zr,Zr),FMul(Zi,Zi)),four)==-1)){ new_Zr=FAdd(FSub(FMul(Zr,Zr),FMul(Zi,Zi)),*CRealVal); new_Zi=FAdd(FMul(two,FMul(Zr,Zi)),*CImagVal); Zr=new_Zr; Zi=new_Zi; COUNT++; } DrawPixel(X,Y,(COUNT & 4)*4177920 + (COUNT & 2)*32640 + (COUNT & 1)*255); } } } [/code] |
||||
isochronic Guru ![]() Joined: 21/01/2012 Location: AustraliaPosts: 689 |
I ( a distant observer ) gather that something like CY = Y * YDelta + ImaginOffset
is CY=FAdd(FMul(IntToFloat(Y),YDelta),*ImaginOffset); ?
Are the FAdd, FMul etc part of the CX32 libraries ? Juicy !! |
||||
Grogster![]() Admin Group ![]() Joined: 31/12/2012 Location: New ZealandPosts: 9610 |
...bow down to matherp - the Cfunction God... ![]() ![]() ![]() Smoke makes things work. When the smoke gets out, it stops! |
||||
matherp Guru ![]() Joined: 11/12/2012 Location: United KingdomPosts: 10310 |
No, these are functions that Geoff has included in the MM firmware that can be called from CFunctions. They get round the fact that the XC32 compiler doesn't generate properly position independent code. All these helper functions are defined in cfunctions.h which Geoff distributes with the Micromite firmware. Here is CSUB Mandelbrot: 20 seconds on a MM+/ILI9341 [code] OPTION EXPLICIT OPTION DEFAULT NONE DIM FLOAT RealOffset = -2.0 ,ImaginOffset = -0.95, size=2.8 timer=0 mandelbrot(size, RealOffset, ImaginOffset) print timer end CSUB mandelbrot 00000000 27BDFFA8 AFBF0054 AFBE0050 AFB7004C AFB60048 AFB50044 AFB40040 AFB3003C AFB20038 AFB10034 AFB00030 00808821 AFA5005C AFA60060 3C109D00 8E02009C 0040F809 3C044080 0040F021 8E02009C 0040F809 3C044000 AFA2001C 8E130064 8E030098 8E020080 8C640000 0040F809 00002821 00409021 8E030094 8E020080 8C640000 0040F809 00002821 02402021 0260F809 00402821 0040A021 8E130064 8E320000 8E030094 8E020080 8C640000 0040F809 00002821 02402021 0260F809 00402821 AFA2002C 8E120064 8E020058 8E240000 0040F809 02802821 00408821 8E030098 8E020080 8C640000 0040F809 00002821 02202021 0240F809 00402821 AFA20028 8E020094 8C420000 1040008A 3C119D00 AFA00024 8E32005C 8E300058 8E220080 8FA40024 0040F809 00042FC3 00402021 0200F809 8FA5002C 00402021 8FA2005C 0240F809 8C450000 AFA20018 8E220098 8C420000 1040006F 8FA20024 AFA00020 8E32005C 8E300058 8E220080 8FA40020 0040F809 00042FC3 00402021 0200F809 8FA50028 00402021 8FA30060 0240F809 8C650000 0040B821 8E22009C 0040F809 00002021 00409021 8E22009C 0040F809 00002021 00409821 10000023 0000A021 8E350060 8E220058 02402021 0040F809 02402821 00408021 8E220058 02602021 0040F809 02602821 02002021 02A0F809 00402821 00402021 02C0F809 8FA50018 0040B021 8E35005C 8E300058 02402021 0200F809 02602821 8FA4001C 0200F809 00402821 00402021 02A0F809 02E02821 26940001 24030047 52830018 8E230048 00409821 02C09021 8E360068 8E35005C 8E220058 02402021 0040F809 02402821 00408021 8E220058 02602021 0040F809 02602821 02002021 02A0F809 00402821 00402021 02C0F809 03C02821 2403FFFF 5043FFCB 8E36005C 8E230048 32820002 000221C0 000213C0 00442023 32820004 00022B80 00021580 00451023 00821021 32940001 00142200 0094A023 0054A021 AFB40010 8C620000 8FA40024 8FA50020 00803021 0040F809 00A03821 8FA20020 24420001 AFA20020 8E220098 8C420000 8FA30020 0062102B 5440FF96 8E32005C 8FA20024 24420001 AFA20024 8E220094 8C420000 8FA30024 0062102B 5440FF7A 8E32005C 8FBF0054 8FBE0050 8FB7004C 8FB60048 8FB50044 8FB40040 8FB3003C 8FB20038 8FB10034 8FB00030 03E00008 27BD0058 End CSUB [/code] |
||||
twofingers![]() Guru ![]() Joined: 02/06/2014 Location: GermanyPosts: 1593 |
Thanks peter! ![]() I like it! This are the times (in ms) for Julia on a MM2@48Mhz: 23006 33838 37001 30370 11832 11428 10773 31263 12742 10660 32165 16738 35920 For my old MMbasic 4.7b32 i had to convert the CSub to a CFunction. ![]() ![]() ed: but it is actually sufficient to rename CSUB to CFunction. edit: Mandelbrot takes 31432ms on a MM2@48Mhz (BP170 w. 240x320LCD) Regards Michael causality ≠ correlation ≠ coincidence |
||||
twofingers![]() Guru ![]() Joined: 02/06/2014 Location: GermanyPosts: 1593 |
Interesting question! ![]() As expected: Size matters (the smaller, the faster)! MMBasic 4.7B32 MM2@48Mhz ' for declared integers
Dim integer i, This, This_is_a_very_very_long_name Timer=0 For i = 1 To 1000000 Next i Print Timer End Timer=0 For This = 1 To 1000000 Next This Print Timer Timer=0 For This_is_a_very_very_long_name = 1 To 1000000 Next This_is_a_very_very_long_name Print Timer end 'Results with dim integer '1: 21243ms '2: 27156ms '3: 75555ms Timer=0 Do i=i+1 Loop While i<1000000 Print Timer End ''Result for "DO.LOOP": 89624ms ' for declared integers
Dim integer i, This, This_is_a_very_very_long_name Timer=0 For i = 1 To 1000000:Next i Print Timer End Timer=0 For This = 1 To 1000000:Next This Print Timer Timer=0 For This_is_a_very_very_long_name = 1 To 1000000:Next This_is_a_very_very_long_name Print Timer end 'Results 'with dim integer and one line (For i = 1 To 1000000:Next i) '1: 21096ms '2: 27009ms '3: 75407ms ' for floats (ie without "DIM")
Dim float i, This, This_is_a_very_very_long_name Timer=0 For i = 1 To 1000000 Next i Print Timer End Timer=0 For This = 1 To 1000000 Next This Print Timer Timer=0 For This_is_a_very_very_long_name = 1 To 1000000 Next This_is_a_very_very_long_name Print Timer end 'Results '1: 23633ms '2: 29547ms '3: 77944ms ' with dim integer and one line (For i = 1 To 1000000:Next)
'NEXT without var_name Dim integer i, This, This_is_a_very_very_long_name Timer=0 For i = 1 To 1000000:Next Print Timer End 'Result: 14511ms The fastest is the "for:next" with short var_name and without the "next var". The slowest is the "do:loop" Regards Michael causality ≠ correlation ≠ coincidence |
||||
WhiteWizzard Guru ![]() Joined: 05/04/2013 Location: United KingdomPosts: 2944 |
@twofingers - Great little experiments you conducted there! I am shocked at the time difference between: Timer=0 For i = 1 To 1000000:Next i Print Timer End (21.096s) and: Timer=0 For i = 1 To 1000000:Next Print Timer End (14.511s) Thats 6.5s saving by not declaring the variable name in the NEXT statement!! ![]() |
||||
twofingers![]() Guru ![]() Joined: 02/06/2014 Location: GermanyPosts: 1593 |
Yes, but I think it is somehow brutal (kind of illegal?). Sometimes make it the difference between doable and not feasible. causality ≠ correlation ≠ coincidence |
||||
TassyJim![]() Guru ![]() Joined: 07/08/2011 Location: AustraliaPosts: 6283 |
A few more speed observations for the plain 'basic' version. With my MM+ and 7inch display, the Julia set took 1979 seconds. Change CY = Y * YDelta + ImaginOffset
Zr = CX Zi = CY to Zi = Y * YDelta + ImaginOffset
Zr = CX and new_Zr = Zr * Zr - Zi * Zi + CRealVal
new_Zi = 2 * Zr * Zi + CImagVal Zr = new_Zr Zi = new_Zi to new_Zr = Zr * Zr - Zi * Zi + CRealVal
Zi = 2 * Zr * Zi + CImagVal Zr = new_Zr By eliminating two lines of unnecessary assignments, the time reduced to 1791 seconds. It makes the code less readable but with a significant speed improvement. I am 20 minutes into another test run which is looking promising. Jim VK7JH MMedit |
||||
TassyJim![]() Guru ![]() Joined: 07/08/2011 Location: AustraliaPosts: 6283 |
Last play. Changed the DO... LOOP to FOR... NEXT FOR X = 0 TO (MM.HRES-1) CX = X * Xdelta + RealOffset FOR Y = 0 TO (MM.VRES-1) Zi = Y * YDelta + ImaginOffset Zr = CX 'Begin Iteration loop FOR COUNT = 0 TO MAXIT IF ( Zr * Zr + Zi * Zi ) >= 4 THEN EXIT FOR new_Zr = Zr * Zr - Zi * Zi + CRealVal Zi = 2 * Zr * Zi + CImagVal Zr = new_Zr NEXT COUNT PIXEL X, Y, (COUNT AND 4) * 4177920 + (COUNT AND 2) * 32640 + (COUNT AND 1) * 255) NEXT Y NEXT X Time down to 1616 seconds Jim VK7JH MMedit |
||||
vegipete![]() Guru ![]() Joined: 29/01/2013 Location: CanadaPosts: 1132 |
Nice, Jim. On my MM+ with 320x240 ILI9341 screen, your last version takes 286 seconds. By knocking the variable names off the NEXT statements, it drops to 277.5 seconds. Visit Vegipete's *Mite Library for cool programs. |
||||
WhiteWizzard Guru ![]() Joined: 05/04/2013 Location: United KingdomPosts: 2944 |
@TasseyJim, Out of interest, what CPU are you set to? Also, as two fingers tests showed a reduction without the NEXT variable name, have you tried this (note vegipete saw a reduction in his post just now) Should get one last slight improvement ![]() WW |
||||
Page 1 of 3 ![]() ![]() |
![]() |
![]() |
The Back Shed's forum code is written, and hosted, in Australia. | © JAQ Software 2025 |