BBC BASIC for Windows
« Julia sets in Assembler »

Welcome Guest. Please Login or Register.
Apr 5th, 2018, 10:19pm



ATTENTION MEMBERS: Conforums will be closing it doors and discontinuing its service on April 15, 2018.
Ad-Free has been deactivated. Outstanding Ad-Free credits will be reimbursed to respective payment methods.

If you require a dump of the post on your message board, please come to the support board and request it.


Thank you Conforums members.

BBC BASIC for Windows Resources
Online BBC BASIC for Windows documentation
BBC BASIC for Windows Beginners' Tutorial
BBC BASIC Home Page
BBC BASIC on Rosetta Code
BBC BASIC discussion group
BBC BASIC for Windows Programmers' Reference

« Previous Topic | Next Topic »
Pages: 1  Notify Send Topic Print
 thread  Author  Topic: Julia sets in Assembler  (Read 685 times)
DDRM
Administrator
ImageImageImageImageImage


member is offline

Avatar




PM

Gender: Male
Posts: 321
xx 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

 

User IP Logged

Zaphod
Guest
xx 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!

User IP Logged

michael
Senior Member
ImageImageImageImage


member is offline

Avatar




PM


Posts: 335
xx 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?
User IP Logged

I like making program generators and like reinventing the wheel
Zaphod
Guest
xx 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. wink

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?
User IP Logged

DDRM
Administrator
ImageImageImageImageImage


member is offline

Avatar




PM

Gender: Male
Posts: 321
xx 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.
grin

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
User IP Logged

DDRM
Administrator
ImageImageImageImageImage


member is offline

Avatar




PM

Gender: Male
Posts: 321
xx 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
 

User IP Logged

Zaphod
Guest
xx 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.
User IP Logged

KenDown
Full Member
ImageImageImage


member is offline

Avatar




PM


Posts: 181
xx 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.
User IP Logged

DDRM
Administrator
ImageImageImageImageImage


member is offline

Avatar




PM

Gender: Male
Posts: 321
xx 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
User IP Logged

KenDown
Full Member
ImageImageImage


member is offline

Avatar




PM


Posts: 181
xx 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.
User IP Logged

Ric
Full Member
ImageImageImage


member is offline

Avatar




PM

Gender: Male
Posts: 136
xx 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 grin grin

Ric
User IP Logged

It's always possible, but not necessarily how you first thought. Chin up and try again.
Pages: 1  Notify Send Topic Print
« Previous Topic | Next Topic »

| |

This forum powered for FREE by Conforums ©
Terms of Service | Privacy Policy | Conforums Support | Parental Controls