Author |
Topic: Julia sets in Assembler (Read 685 times) |
|
DDRM
Administrator
member is offline


Gender: 
Posts: 321
|
 |
Julia sets in Assembler
« Thread started on: Apr 15th, 2016, 08:34am » |
|
Inspired by Ric's experiments with writing a real-time 3d renderer, I've been revisiting the assembler, and particularly the floating point instructions. It's actually surprisingly easy once you get your head around the way the stack of "registers" works, and once Richard had banged into my head that I had to leave the stack pointer at the top again when I bailed out! It's also blindingly fast.
I wanted to make a viewer that would allow you to explore the Julia sets associated with the area around the Mandelbrot set. This is quite a complex (in joke) area, which I won't attempt to explain, but basically points within the Mandelbrot set lead to a connected set of points which stay close to home, while points far away fly rapidly off to infinity. At the borders are some beautiful and complex patterns if we render the time to escape into colours.
If you run this code, it will open a window (initially) showing the area from (-2,-1.25i) to (2,1.25i). As you move the mouse around over it you will see the Julia set (for the function z(i+1)=z(i)^2+c) associated with that point. Pressing C will toggle between a colour and a grey scale palette.
If you scroll with the mouse wheel, two things will happen. (i) the image will magnify or shrink by 2 for each "click" of the wheel (ii) the position of the image will be frozen. You can then use the mouse (left button) to drag the image around. It is re-rendering as you do this!
To unlock the image again (or to lock it initially), press F. Be careful what zoom you are at, and where your mouse is when you do this, or you may find yourself in the middle of a huge, boring area! I leave it to the user to add a "reset to the start" function... or you can always quit (Q) and re-rerun.
Hope that's interesting/amusing to someone...
D
PS Yes, I know it has a few naming infractions, but really only in the assembler code...
PPS If you change the two instructions near the start of the assembler code: fld tbyte [^ci] fld tbyte [^cr]
to
fld tbyte [^ni] fld tbyte [^nr]
You will get the Mandelbrot set itself, and can explore that in the same way.
Code:
MODE 8
xres=@vdu%!208
yres=@vdu%!212
minx=-2.0
maxx=2.0
xrange=maxx-minx
xstep=xrange/xres
miny=-1.25
maxy=1.25
yrange=maxy-miny
ystep=yrange/yres
REM Reserve space for the assembly routine itself, making sure it's in its own 2K block to avoid cache thrashing.
size% = 2048
DIM code% NOTEND AND 2047, code% size%-1
REM These are just dummy variables, while setting up the assembler
nr=3.5
ni=1.5
cr=0.29
ci=0.01
mag%=0
temp=0.0
col%=TRUE
fixed%=FALSE
PROCAssJulia
REM Set up a DIBsection for screen output, so we can write data to it directly
DIM BITMAPINFOHEADER{Size%, Width%, Height%, Planes{l&,h&}, BitCount{l&,h&}, \
\ Compression%, SizeImage%, XPelsPerMeter%, YPelsPerMeter%, \
\ ClrUsed%, ClrImportant%}
DIM bmi{Header{} = BITMAPINFOHEADER{}, Palette%(255)}
bmi.Header.Size% = DIM(BITMAPINFOHEADER{})
bmi.Header.Width% = @vdu%!208
bmi.Header.Height% = @vdu%!212
bmi.Header.Planes.l& = 1
bmi.Header.BitCount.l& = 8
REM We've made an 8 bit per pixel bitmap definition: define a palette to go with it
SYS "CreateDIBSection", @memhdc%, bmi{}, 0, ^bits%, 0, 0 TO hbitmap%
IF hbitmap% = 0 ERROR 100, "Couldn't create DIBSection"
SYS "SelectObject", @memhdc%, hbitmap% TO oldhbm%
SYS "DeleteObject", oldhbm%
PROCSetColour(col%)
CLS
bytesperpixel% = bmi.Header.BitCount.l& DIV 8
bytesperline% = ((bmi.Header.Width% * bytesperpixel%) + 3) AND -4
REPEAT
MOUSE x%,y%,z%
IF NOT fixed% THEN
cr=minx+x%*xstep/2
ci=miny+y%*ystep/2
PRINT TAB(0,0);cr,ci
ELSE
IF z%>0 THEN
tx%=x%
ty%=y%
REPEAT MOUSE x%,y%,z% UNTIL z%=0
minx+=(tx%-x%)*xstep/2
maxx+=(tx%-x%)*xstep/2
miny+=(ty%-y%)*ystep/2
maxy+=(ty%-y%)*ystep/2
ENDIF
ENDIF
CALL code%
SYS "InvalidateRect", @hwnd%, 0, 0
q$=INKEY$(0)
CASE q$ OF
WHEN "c","C":col%=NOT col%:PROCSetColour(col%)
WHEN "f","F": fixed%=NOT fixed%
WHEN CHR$(140):
fixed%=TRUE
minx+=xrange/4
maxx-=xrange/4
xrange=maxx-minx
xstep=xrange/xres
miny+=yrange/4
maxy-=yrange/4
yrange=maxy-miny
ystep=yrange/yres
WHEN CHR$(141):
fixed%=TRUE
minx-=xrange/4
maxx+=xrange/4
xrange=maxx-minx
xstep=xrange/xres
miny-=yrange/4
maxy+=yrange/4
yrange=maxy-miny
ystep=yrange/yres
ENDCASE
UNTIL q$="q" OR q$="Q"
QUIT
:
DEFPROCSetColour(col%)
LOCAL i%,r%,g%,b%
IF col% THEN
FOR i% = 0 TO 255
r% = (i% MOD 16)*16
g% = (i% MOD 64)*4
b% = 255-i%/2
bmi.Palette%(i%) = b% + (g% << 8) + (r% << 16)
NEXT
ELSE
FOR i% = 0 TO 255
bmi.Palette%(i%) = i% + (i% << 8) + (i% << 16)
NEXT
ENDIF
SYS "SetDIBColorTable", @memhdc%, 0, 256, ^bmi.Palette%(0)
ENDPROC
:
DEFPROCAssJulia
LOCAL opt%
FOR opt%=0 TO 2 STEP 2
P%=code%
[
OPT opt%
mov esi,[^yres]
mov edx,[^bits%]
fld tbyte [^miny]
fstp tbyte [^ni]
.yloop
mov ecx,[^xres]
fld tbyte [^minx]
fstp tbyte [^nr]
.xloop
mov eax,0
fld tbyte [^ci]
fld tbyte [^cr]
fld tbyte [^nr]
fst st3
fmul st0,st0
fstp st4 ;After this we have cr,ci,nr and nr2 in ST0-3, and 2 pushes
fld tbyte [^ni]
fst st5
fmul st0,st0
fstp st6 ;After this we have cr,ci,nr, nr2,ni and ni2 in ST0-5, and 2 pushes
.lpt
;OK now we should be able to calculate the new values of nr, ni
fld st2
fadd st0,st0
fmul st0,st5
fadd st0,st2
fstp st5 ;we should be in the same place, but with the new value of ni
fld st3
fsub st0,st6
fadd st0,st1
fstp st3 ;After this we now have cr,ci, the new nr, the old nr2, the new ni, and the old ni2 in ST0-5, and 2 pushes
fld st2
fmul st0,st0
fstp st4 ;now with new nr2, still 2 pushes
fld st4
fmul st0,st0
fst st6 ;now with new ni2, now 3 pushes
fadd st0,st4 ;gives magnitude squared
fistp dword [^mag%] ;store it as an integer, so it can be read into a standard register and compared, Back to 2 pushes
inc eax
mov ebx,[^mag%]
cmp ebx,4
ja esc
cmp eax,255
jb lpt
.esc
;Now we have the problem of popping the stack twice
fstp tbyte [^temp] ;should pop cr into temp
fstp tbyte [^temp] ;should pop ci into temp
mov byte [edx],al
add edx,[^bytesperpixel%]
fld tbyte [^nr]
fld tbyte [^xstep]
faddp st1,st0
fstp tbyte [^nr]
dec ecx
jnz near xloop
fld tbyte [^ni]
fld tbyte [^ystep]
faddp st1,st0
fstp tbyte [^ni]
dec esi
jnz near yloop
ret
]
NEXT opt%
ENDPROC
|
|
Logged
|
|
|
|
Zaphod
Guest
|
 |
Re: Julia sets in Assembler
« Reply #1 on: Apr 15th, 2016, 12:44pm » |
|
I have always been fascinated by fractals and this is magnificent. Thanks for sharing it. And boy is it fast!
|
|
Logged
|
|
|
|
michael
Senior Member
member is offline


Posts: 335
|
 |
Re: Julia sets in Assembler
« Reply #2 on: Apr 15th, 2016, 9:56pm » |
|
That's fantastic.. I only played with ASM programming for a short bit in the early 80s on my TRS-80. At the time it just didn't have enough attention grab to lure me in. I think I had a ASM book and a cartridge of some sort for the interface for the programs. ( and limited.. I played with it for a very short time)
Its nice to see a post like that.. I would like to see more assembly language examples.. PLEASE!! Maybe assembly snippets that do simple draws?
|
|
Logged
|
I like making program generators and like reinventing the wheel
|
|
|
Zaphod
Guest
|
 |
Re: Julia sets in Assembler
« Reply #3 on: Apr 16th, 2016, 7:17pm » |
|
Can I just comment that the program is heavy on CPU time as it rewrites the display even if the input variables haven't changed. So having ripped into you, Dave, I will leave you to do the decent thing. 
I am having great fun with it.
How would you toggle between the Julia set and Mandlebrot set from the user interface? Is that something that could be added?
|
|
Logged
|
|
|
|
DDRM
Administrator
member is offline


Gender: 
Posts: 321
|
 |
Re: Julia sets in Assembler
« Reply #4 on: Apr 17th, 2016, 5:57pm » |
|
HI Zaphod,
I'm sure it is within your powers to save the mouse coordinates and only call the assembler if they have changed, so I'll leave it to you to do the decent thing yourself. 
With regard to making the switch from Julia sets to Mandelbrot: not straightforward (the value of nr and ni is different for every point in the Mandelbrot calculation, and that is done within the assembler code, while the value of cr and ci is fixed for a given Julia set). I could do it by setting a flag somewhere and testing it, then making the appropriate assignment, but of course that would slow down the assembler all the time - but that may not matter much in the grand scheme of things. I'll have a go. It's not in the innermost loop.
Best wishes,
D
|
|
Logged
|
|
|
|
DDRM
Administrator
member is offline


Gender: 
Posts: 321
|
 |
Re: Julia sets in Assembler
« Reply #5 on: Apr 17th, 2016, 7:02pm » |
|
Hi Zaphod (and anyone else who cares...)
OK, I have done your bidding. This version includes two new controls: you can press J to go to Julia sets (and reset to the starting field of view), or M to go to the Mandelbrot set (and reset the field of view).
I also implemented all sorts of tests to avoid running the assembler if nothing had changed - but still got heavy CPU usage. Of course, all it really needed was to put a WAIT 0 in the main loop of the BASIC bit... but I've left the other bits in, because I can't be bothered to take them out again...
Best wishes,
D
Code:
MODE 8
xres=@vdu%!208
yres=@vdu%!212
julia%=1:PROCSetfieldJ
REM Reserve space for the assembly routine itself, making sure it's in its own 2K block to avoid cache thrashing.
size% = 2048
DIM code% NOTEND AND 2047, code% size%-1
REM These are just dummy variables, while setting up the assembler
nr=3.5
ni=1.5
cr=0.29
ci=0.01
mag%=0
temp=0.0
col%=TRUE
fixed%=FALSE
PROCAssJulia
REM Set up a DIBsection for screen output, so we can write data to it directly
DIM BITMAPINFOHEADER{Size%, Width%, Height%, Planes{l&,h&}, BitCount{l&,h&}, \
\ Compression%, SizeImage%, XPelsPerMeter%, YPelsPerMeter%, \
\ ClrUsed%, ClrImportant%}
DIM bmi{Header{} = BITMAPINFOHEADER{}, Palette%(255)}
bmi.Header.Size% = DIM(BITMAPINFOHEADER{})
bmi.Header.Width% = @vdu%!208
bmi.Header.Height% = @vdu%!212
bmi.Header.Planes.l& = 1
bmi.Header.BitCount.l& = 8
REM We've made an 8 bit per pixel bitmap definition: define a palette to go with it
SYS "CreateDIBSection", @memhdc%, bmi{}, 0, ^bits%, 0, 0 TO hbitmap%
IF hbitmap% = 0 ERROR 100, "Couldn't create DIBSection"
SYS "SelectObject", @memhdc%, hbitmap% TO oldhbm%
SYS "DeleteObject", oldhbm%
PROCSetColour(col%)
CLS
OFF
bytesperpixel% = bmi.Header.BitCount.l& DIV 8
bytesperline% = ((bmi.Header.Width% * bytesperpixel%) + 3) AND -4
tcr=-5
tci=-5
REPEAT
WAIT 0
MOUSE x%,y%,z%
IF (NOT fixed%) AND julia% THEN
cr=minx+x%*xstep/2
ci=miny+y%*ystep/2
IF cr<>tcr OR ci<>tci THEN changed%=TRUE
PRINT TAB(0,0);cr,ci
ELSE
IF z%>0 THEN
tx%=x%
ty%=y%
REPEAT MOUSE x%,y%,z% UNTIL z%=0
minx+=(tx%-x%)*xstep/2
maxx+=(tx%-x%)*xstep/2
miny+=(ty%-y%)*ystep/2
maxy+=(ty%-y%)*ystep/2
changed%=TRUE
ENDIF
ENDIF
IF changed% THEN
CALL code%
changed%=FALSE
tcr=cr
tci=ci
SYS "InvalidateRect", @hwnd%, 0, 0
ENDIF
q$=INKEY$(0)
CASE q$ OF
WHEN "c","C":col%=NOT col%:PROCSetColour(col%):changed%=TRUE
WHEN "f","F": fixed%=NOT fixed%
WHEN "j","J": julia%=1:PROCSetfieldJ
WHEN "m","M": julia%=0:PROCSetfieldM
WHEN CHR$(140):
fixed%=TRUE
minx+=xrange/4
maxx-=xrange/4
xrange=maxx-minx
xstep=xrange/xres
miny+=yrange/4
maxy-=yrange/4
yrange=maxy-miny
ystep=yrange/yres
changed%=TRUE
WHEN CHR$(141):
fixed%=TRUE
minx-=xrange/4
maxx+=xrange/4
xrange=maxx-minx
xstep=xrange/xres
miny-=yrange/4
maxy+=yrange/4
yrange=maxy-miny
ystep=yrange/yres
changed%=TRUE
ENDCASE
UNTIL q$="q" OR q$="Q"
QUIT
:
DEFPROCSetColour(col%)
LOCAL i%,r%,g%,b%
IF col% THEN
FOR i% = 0 TO 255
r% = (i% MOD 16)*16
g% = (i% MOD 64)*4
b% = 255-i%/2
bmi.Palette%(i%) = b% + (g% << 8) + (r% << 16)
NEXT
ELSE
FOR i% = 0 TO 255
bmi.Palette%(i%) = i% + (i% << 8) + (i% << 16)
NEXT
ENDIF
SYS "SetDIBColorTable", @memhdc%, 0, 256, ^bmi.Palette%(0)
ENDPROC
:
DEFPROCSetfieldJ
minx=-2.0
maxx=2.0
xrange=maxx-minx
xstep=xrange/xres
miny=-1.25
maxy=1.25
yrange=maxy-miny
ystep=yrange/yres
changed%=TRUE
fixed%=FALSE
ENDPROC
:
DEFPROCSetfieldM
minx=-2.0
maxx=0.5
xrange=maxx-minx
xstep=xrange/xres
miny=-1.25
maxy=1.25
yrange=maxy-miny
ystep=yrange/yres
changed%=TRUE
fixed%=FALSE
ENDPROC
:
DEFPROCAssJulia
LOCAL opt%
FOR opt%=0 TO 2 STEP 2
P%=code%
[
OPT opt%
mov esi,[^yres]
mov edx,[^bits%]
fld tbyte [^miny]
fstp tbyte [^ni]
.yloop
mov ecx,[^xres]
fld tbyte [^minx]
fstp tbyte [^nr]
.xloop
mov eax,[^julia%]
cmp eax,0
jz mand
fld tbyte [^ci]
fld tbyte [^cr]
jmp inloop
.mand
fld tbyte [^ni]
fld tbyte [^nr]
.inloop
mov eax,0
fld tbyte [^nr]
fst st3
fmul st0,st0
fstp st4 ;After this we have cr,ci,nr and nr2 in ST0-3, and 2 pushes
fld tbyte [^ni]
fst st5
fmul st0,st0
fstp st6 ;After this we have cr,ci,nr, nr2,ni and ni2 in ST0-5, and 2 pushes
.lpt
;OK now we should be able to calculate the new values of nr, ni
fld st2
fadd st0,st0
fmul st0,st5
fadd st0,st2
fstp st5 ;we should be in the same place, but with the new value of ni
fld st3
fsub st0,st6
fadd st0,st1
fstp st3 ;After this we now have cr,ci, the new nr, the old nr2, the new ni, and the old ni2 in ST0-5, and 2 pushes
fld st2
fmul st0,st0
fstp st4 ;now with new nr2, still 2 pushes
fld st4
fmul st0,st0
fst st6 ;now with new ni2, now 3 pushes
fadd st0,st4 ;gives magnitude squared
fistp dword [^mag%] ;store it as an integer, so it can be read into a standard register and compared, Back to 2 pushes
inc eax
mov ebx,[^mag%]
cmp ebx,4
ja esc
cmp eax,255
jb lpt
.esc
;Now we have the problem of popping the stack twice
fstp tbyte [^temp] ;should pop cr into temp
fstp tbyte [^temp] ;should pop ci into temp
mov byte [edx],al
add edx,[^bytesperpixel%]
fld tbyte [^nr]
fld tbyte [^xstep]
faddp st1,st0
fstp tbyte [^nr]
dec ecx
jnz near xloop
fld tbyte [^ni]
fld tbyte [^ystep]
faddp st1,st0
fstp tbyte [^ni]
dec esi
jnz near yloop
ret
]
NEXT opt%
ENDPROC
|
|
Logged
|
|
|
|
Zaphod
Guest
|
 |
Re: Julia sets in Assembler
« Reply #6 on: Apr 18th, 2016, 12:33am » |
|
Why, thank you!
The BASIC stuff yes, I fixed that to my satisfaction and added key movement and zoom controls and higher precision starting point selection via the keys, and put in a refresh flag etc. I did not want to post that as it looks like one up man ship which others have done in the past. Thanks for redoing that.
But the assembler, not within my capabilities so thanks again for that part. I found that I could go 98 levels of zoom on one set, truly remarkable. Very well done.
And I very much hope others are interested as it is a great example of what BB4W can do in the right hands.
|
|
Logged
|
|
|
|
KenDown
Full Member
member is offline


Posts: 181
|
 |
Re: Julia sets in Assembler
« Reply #7 on: Jun 1st, 2016, 07:56am » |
|
Am I doing something wrong? I get the Mandelbrot set, but I can't dig down into it. It just sits there, no matter how I move the mouse or which button I press.
|
|
Logged
|
|
|
|
DDRM
Administrator
member is offline


Gender: 
Posts: 321
|
 |
Re: Julia sets in Assembler
« Reply #8 on: Jun 6th, 2016, 10:57am » |
|
Hi Kendall,
Sorry for the delay, I've been away, without net access. Fantastic!
Umm, don't know, it works for me!
Pressing M switches to the Mandelbrot set - I guess that is working?
Dragging (with left or right button) moves it about, and scrolling the scroll wheel magnifies or zooms out.
Unlike for the Julia sets just moving the mouse doesn't do anything - there's no logical equivalent to implement.
The scroll wheel handling is the WHEN CHR$(140) (and 141) sections: if you don't have, or don't like, a scroll wheel you could amend this to 138/139 to use the arrow keys instead.
Best wishes,
D
|
|
Logged
|
|
|
|
KenDown
Full Member
member is offline


Posts: 181
|
 |
Re: Julia sets in Assembler
« Reply #9 on: Jun 6th, 2016, 7:36pm » |
|
Ah, thanks. I wasn't using the wheel. Perhaps a few instructions wouldn't go astray, as it does work once you know how.
|
|
Logged
|
|
|
|
Ric
Full Member
member is offline


Gender: 
Posts: 136
|
 |
Re: Julia sets in Assembler
« Reply #10 on: Jun 15th, 2016, 08:41am » |
|
Fantastic DDRM,
Nice to see you have dabbled in fpasm again. Although this is more than dabbled. The results are great and in real time too. (not sure what julias or mandlebrots are tho.)
Keep dabbling 
Ric
|
|
Logged
|
It's always possible, but not necessarily how you first thought. Chin up and try again.
|
|
|
|