gigatron/rom/Contrib/psr/multiply/mandelbrot/Mandelbrot.gcl
2025-01-28 19:17:01 +03:00

357 lines
9.0 KiB
Plaintext

{-----------------------------------------------------------------------+
| |
| Mandelbrot fractal |
| |
+-----------------------------------------------------------------------}
gcl0x
{
Plot the Mandelbrot set
Originally by Marcel. This version is adapted to use SYS_MultiplyBytes_120
In order to make the multiplication as fast as possible we use 8 fraction
bits instead of 7.
}
{ multiply-shift ((A*B)>>8) }
{
Uses Karatsuba's algorithm, which only computes three partial products,
the product of the two high-bytes, the product of the two low-bytes, and
a product that turns out to be (in our case)
high-of-A * low-of-B
+ low-of-A * high-of-B
- high-of-A * high-of-B
- low-of-A * low-of-B
In other words, we can produce
high-of-A * low-of-B + low-of-A * high-of-B
(the sum of the two partial products we didn't calculate)
by adding on the products we do. Nice.
The setup to find the multiplicands needed is a bit fiddly,
and would be slow in vCPU, so we use a SYS function to do it.
This leaves sysFn pointing to SYS_MultiplyBytes_120, so the whole thing is easy.
}
[def
{Extract sign and absolute values}
0 sign=
{0}A- [if>0 A= 1 sign=]
0 B- [if>0 B= sign 1^ sign=]
{ Karatsuba multiplication }
\SYS_KaratsubaPrepare_54 _sysFn=
A _sysArgs0=
B 54!!
[if=0 120!! C=
else 120!! C= 0 C- C=]
{ Find product of low bytes }
<A, \sysArgs0.
<B, \sysArgs1.
120!! \sysArgs5: C+ C=
{ multiply high-byte of A by high-byte of B, left-shift by 8 and add to C }
>A, \sysArgs0.
>B, \sysArgs1.
98!! \sysArgs7. C+ _sysArgs6+ C=
{Apply sign to return value }
sign [if<>0 0 C- else C]
ret
] MulShift8=
{-----------------------------------------------------------------------+
|}\vLR>++ ret{ RAM page 3 |
+-----------------------------------------------------------------------}
$0300:
[def {CalcSet}
push
$800 Pen= {Start of video}
120 Height= 160 Width= len=
LastPixel= {Anything well-defined}
0 DY=
Step DX=
[do
{Draw white pixel while busy here}
63 Pen.
{Update clock}
UpdateClock!
\SYS_MultiplyBytes_120 \sysFn:
LastPixel [if=0
{Check if we are inside one of the main bulbs for
a quick bailout (Wikipedia)
(x+1)^2 + y^2 < 1/16}
Y0 A= B= MulShift8! YY=
256 X0+ A= B= MulShift8! YY+ 16- [if<0 0
else
{q*(q + x - 1/4) < 1/4*y^2, where q = (x - 1/4)^2 + y^2}
X0 64- A= B= MulShift8! YY+ {q}
A= X0+ 64- B= MulShift8! tmp=
tmp+ tmp= tmp+ tmp= {*4} YY- [if<0 0
else
{Otherwise run the escape time algorithm}
CalcPixel!
]
]
else
{No shortcut}
CalcPixel!
]
LastPixel= Pen. {Plot pixel}
len 1- [if<=0
{Turn right}
DY tmp= DX DY= 0 tmp- DX=
{Length of next segment, either horizontal or vertical}
DX [if<>0 Width 1- Width= else Height 1- Height=]
]
{Break when reaching a zero-length segment}
len= if>0
{Step in the fractal plane}
X0 DX+ X0=
Y0 DY+ Y0=
{Matching step in video frame}
DX [if<0 Pen 1- Pen=]
DX [if>0 Pen 1+ Pen=]
DY [if<0 -$100 Pen+ Pen=]
DY [if>0 $100 Pen+ Pen=]
loop]
{60 \soundTimer. {For debugging}}
GrayOut!
pop ret
] CalcSet=
{-----------------------------------------------------------------------+
|}\vLR>++ ret{ RAM page 4 |
+-----------------------------------------------------------------------}
$0400:
[def {GrayOut}
push
$800 Pen=
120 Height= 160 Width= len=
1 DX= 0 DY=
[do
\SYS_LSRW1_48 \sysFn:
0 Level=
32 [do
i=
Pen, 63& i- [if>=0 Pen. i 21& [if<>0 1 else 2] Level+ Level=]
i 48!! {ShiftRight}
if>0loop]
{Level in 0..9}
Pen. {Paint it black}
Level [if<>0
{Level in 1..9}
{Ordered dithering}
Pen>, 1& i= i+ i= {2 * bit 0 of y}
Pen 1& i+ i= {+ bit 0 of x}
[def 0# 2# {Bayer matrix (Wikipedia)}
3# 1#]
i+ peek Level+ 1- Level=
{Level in 0..11}
{Map intensity level to actual gray color (multiple of 1+4+16)}
[do Level 3- if>=0 Level= Pen, 21+ Pen. loop]
]
{Advance to next pixel}
len 1- [if<=0
{Turn right}
DY tmp= DX DY= 0 tmp- DX=
{Length of next segment, either horizontal or vertical}
DX [if<>0 Width 1- Width= else Height 1- Height=]
]
{Break when reaching a zero-length segment}
len= if>0
{Matching step in video frame}
DX [if<0 Pen 1- Pen=]
DX [if>0 Pen 1+ Pen=]
DY [if<0 -$100 Pen+ Pen=]
DY [if>0 $100 Pen+ Pen=]
{Update clock}
UpdateClock!
loop]
pop ret
] GrayOut=
$201a Separator= {High: separator character, Low: XOR when blinking}
{-----------------------------------------------------------------------+
|}\vLR>++ ret{ RAM page 5 |
+-----------------------------------------------------------------------}
$0500:
{Update 24-hour clock}
[def
\frameCount,
LastFrame- 255& Elapsed=
60- [if<0
\buttonState, 128& [if<>0 ret]
15 {Yellow}
else
63 {White}]
Color=
{1 second has elapsed, or button A was pressed}
push
Clock0 Elapsed+ Clock0=
LastFrame Elapsed+ LastFrame=
-3599 Clock0+ [if>=0
Clock0=
1 AddMinutes!
]
{Change separator between on/off ($3a/$20)}
Separator>, Separator^ Separator>.
{Check if user is adjusting the time}
\buttonState, 119^ {A+Up} [if=0 Clock0= +1 else
12^ {A+Down} [if=0 Clock0= -1 else
6^ {A+Left} [if=0 Clock0= -60 else
3^ {A+Right} [if=0 Clock0= +60 else 0]]]]
[if<>0
AddMinutes! {Adjust minutes/hours clock}
0 Clock0= {Reset seconds/subseconds clock}
$3a00 Separator= {Stop blinking once adjusted}
]
\buttonState, 15| \buttonState. {Reset arrow presses, but not the others}
{Convert to HH:MM:SS and draw on screen}
$445c Pos=
Clock1 Value= 600 NextDigit! {10-hour digit}
60 NextDigit! { 1-hour digit}
Separator>, PrintChar! {Colon or space}
10 NextDigit! {10-minute digit}
1 NextDigit! { 1-minute digit}
pop ret
] UpdateClock=
[def {PrintChar}
32- i= \font32up fontData= {All of '0'-'9' and ':' are in the first page}
i 2<< i+ {Multiply by 5}
fontData+ fontData= {Add to page address to reach bitmap data}
$800 Pos+ q= {Where to stop the inner drawing loop}
{Draw 5 vertical slices: 5 using font data}
0 \sysArgs0. {Black}
Color \sysArgs1.
Pos \sysArgs4:
6+ Pos=
\SYS_VDrawBits_134 \sysFn:
$fb i= [do
fontData 0? fontData<++ \sysArgs2. 134!!
\sysArgs4<++
i<++ i if<>0loop]
ret
] PrintChar=
{-----------------------------------------------------------------------+
|}\vLR>++ ret{ RAM page 6 |
+-----------------------------------------------------------------------}
$0600:
[def {NextDigit -- Value Radix}
push
Radix=
$30 Digit=
Value Radix- [if>=0
[do Value= Digit<++ Radix- if>=0loop]
]
Digit PrintChar!
pop ret
] NextDigit=
[def {AddMinutes}
Clock1+ Clock1=
[if<0 1440 Clock1+ Clock1=]
-1440 Clock1+ [if>=0 Clock1=]
ret
] AddMinutes=
{ Calculate color for (X0,Y0) using the escape time algorithm }
[def {CalcPixel}
push
0 X= XX= Y= YY= i=
[do
i 1+ i= 64^ if<>0 {Break after completing 63 iterations}
{Mandelbrot function: z' := z^2 + c}
X A= Y Y+ B= MulShift8! Y0+ Y= {Y = 2*X*Y + Y0}
XX YY- X0+ X= {X = X^2 - Y^2 + X0}
{Calculate squares}
{X}A= B= MulShift8! XX=
Y A= B= MulShift8! YY=
-$400 XX+ YY+
if<=0loop {else} i {Also break when X^2 + Y^2 > 4}
]
pop ret
] CalcPixel=
{ Main }
0 Clock0= Clock1= {Frames and minutes}
Digit=
\frameCount, LastFrame=
[do
-360 Y0= -640 X0= 6 Step= CalcSet! {Global 1127.446 seconds}
-296 Y0= -196 X0= 2 Step= CalcSet! {Zoom1 2125.648 seconds}
-120 Y0= -512 X0= 2 Step= CalcSet! {Zoom2 2050.341 seconds}
-640 Y0= -720 X0= 18 Step= CalcSet! {Wide 356.482 seconds}
0 Y0= -200 X0= 2 Step= CalcSet! {Zoom3 2337.633 seconds}
-120 Y0= 0 X0= 2 Step= CalcSet! {Zoom4 1409.569 seconds}
loop]
{ Benchmark settings:
+ $1c10 p= {Start of video}
+ 96 Height= 128 Width= len=
+ i 1+ i= 27^ if<>0 {Break after completing 26 iterations}
+ -144 Y0= -256 X0= 3 Step= CalcSet! {Global 736.586 seconds}
}
{-----------------------------------------------------------------------+
| End |
+-----------------------------------------------------------------------}