1 # Mandelbrot set using fixed-point numbers.
4 # $ git clone https://github.com/akkartik/mu
7 # $ ./translate apps/mandelbrot-fixed.mu
8 # Build on other platforms (slow):
9 # $ ./translate_emulated apps/mandelbrot-fixed.mu
11 # $ qemu-system-i386 code.img
13 fn main screen: (addr screen), keyboard: (addr keyboard), data-disk: (addr disk) {
14 # Initially the viewport is centered at 0, 0 in the scene.
17 # Initially the viewport shows a section of the scene 4 units wide.
18 var scene-width-f: int
19 copy-to scene-width-f, 0x400/4
21 mandelbrot screen scene-cx-f, scene-cy-f, scene-width-f
22 # move at an angle slowly towards the edge
23 var adj-f/eax: int <- multiply-fixed scene-width-f, 0x12/0.07
24 subtract-from scene-cx-f, adj-f
25 add-to scene-cy-f, adj-f
26 # slowly shrink the scene width to zoom in
27 var tmp-f/eax: int <- multiply-fixed scene-width-f, 0x80/0.5
28 copy-to scene-width-f, tmp-f
33 # Since they still look like int types, we'll append a '-f' suffix to variable
34 # names to designate fixed-point numbers.
36 fn int-to-fixed in: int -> _/eax: int {
37 var result-f/eax: int <- copy in
38 result-f <- shift-left 8/fixed-precision
41 abort "int-to-fixed: overflow"
46 fn fixed-to-int in-f: int -> _/eax: int {
47 var result/eax: int <- copy in-f
48 result <- shift-right-signed 8/fixed-precision
52 # The process of throwing bits away always adjusts a number towards -infinity.
53 fn test-fixed-conversion {
55 var f/eax: int <- int-to-fixed 0
56 var result/eax: int <- fixed-to-int f
57 check-ints-equal result, 0, "F - test-fixed-conversion - 0"
59 var f/eax: int <- int-to-fixed 1
60 var result/eax: int <- fixed-to-int f
61 check-ints-equal result, 1, "F - test-fixed-conversion - 1"
63 var f/eax: int <- int-to-fixed -1
64 var result/eax: int <- fixed-to-int f
65 check-ints-equal result, -1, "F - test-fixed-conversion - -1"
67 var f/eax: int <- int-to-fixed 1
68 f <- shift-right-signed 1
69 var result/eax: int <- fixed-to-int f
70 check-ints-equal result, 0, "F - test-fixed-conversion - 0.5"
72 var f/eax: int <- int-to-fixed -1
73 f <- shift-right-signed 1
74 var result/eax: int <- fixed-to-int f
75 check-ints-equal result, -1, "F - test-fixed-conversion - -0.5"
77 var f/eax: int <- int-to-fixed 3
78 f <- shift-right-signed 1
79 var result/eax: int <- fixed-to-int f
80 check-ints-equal result, 1, "F - test-fixed-conversion - 1.5"
82 var f/eax: int <- int-to-fixed -3
83 f <- shift-right-signed 1
84 var result/eax: int <- fixed-to-int f
85 check-ints-equal result, -2, "F - test-fixed-conversion - -1.5"
87 var f/eax: int <- int-to-fixed 5
88 f <- shift-right-signed 2
89 var result/eax: int <- fixed-to-int f
90 check-ints-equal result, 1, "F - test-fixed-conversion - 1.25"
92 var f/eax: int <- int-to-fixed -5
93 f <- shift-right-signed 2
94 var result/eax: int <- fixed-to-int f
95 check-ints-equal result, -2, "F - test-fixed-conversion - -1.25"
98 # special routines for multiplying and dividing fixed-point numbers
100 fn multiply-fixed a-f: int, b-f: int -> _/eax: int {
101 var result/eax: int <- copy a-f
102 result <- multiply b-f
104 break-if-not-overflow
105 abort "multiply-fixed: overflow"
107 result <- shift-right-signed 8/fixed-precision
111 fn divide-fixed a-f: int, b-f: int -> _/eax: int {
112 var result-f/eax: int <- copy a-f
113 result-f <- shift-left 8/fixed-precision
115 break-if-not-overflow
116 abort "divide-fixed: overflow"
118 var dummy-remainder/edx: int <- copy 0
119 result-f, dummy-remainder <- integer-divide result-f, b-f
123 # multiplying or dividing by an integer can use existing instructions.
125 # adding and subtracting two fixed-point numbers can use existing instructions.
127 fn mandelbrot screen: (addr screen), scene-cx-f: int, scene-cy-f: int, scene-width-f: int {
128 var a/eax: int <- copy 0
129 var b/ecx: int <- copy 0
130 a, b <- screen-size screen
131 var width/esi: int <- copy a
132 width <- shift-left 3/log2-font-width
133 var height/edi: int <- copy b
134 height <- shift-left 4/log2-font-height
135 var y/ecx: int <- copy 0
139 var imaginary-f/ebx: int <- viewport-to-imaginary-f y, width, height, scene-cy-f, scene-width-f
140 var x/eax: int <- copy 0
144 var real-f/edx: int <- viewport-to-real-f x, width, scene-cx-f, scene-width-f
145 var iterations/esi: int <- mandelbrot-iterations-for-point real-f, imaginary-f, 0x400/max
146 iterations <- shift-right 3
147 var color/edx: int <- copy 0
149 var dummy/eax: int <- copy 0
150 dummy, color <- integer-divide iterations, 0x18/24/size-of-cycle-0
151 color <- add 0x20/cycle-0
153 pixel screen, x, y, color
162 fn mandelbrot-iterations-for-point real-f: int, imaginary-f: int, max: int -> _/esi: int {
163 var x-f/esi: int <- copy 0
164 var y-f/edi: int <- copy 0
165 var iterations/ecx: int <- copy 0
167 var done?/eax: boolean <- mandelbrot-done? x-f, y-f
168 compare done?, 0/false
170 compare iterations, max
172 var x2-f/edx: int <- mandelbrot-x x-f, y-f, real-f
173 var y2-f/ebx: int <- mandelbrot-y x-f, y-f, imaginary-f
176 iterations <- increment
182 fn mandelbrot-done? x-f: int, y-f: int -> _/eax: boolean {
184 var tmp-f/eax: int <- multiply-fixed x-f, x-f
185 var result-f/ecx: int <- copy tmp-f
186 tmp-f <- multiply-fixed y-f, y-f
187 result-f <- add tmp-f
188 compare result-f, 0x400/4
196 fn mandelbrot-x x-f: int, y-f: int, real-f: int -> _/edx: int {
198 var tmp-f/eax: int <- multiply-fixed x-f, x-f
199 var result-f/ecx: int <- copy tmp-f
200 tmp-f <- multiply-fixed y-f, y-f
201 result-f <- subtract tmp-f
202 result-f <- add real-f
206 fn mandelbrot-y x-f: int, y-f: int, imaginary-f: int -> _/ebx: int {
208 var result-f/eax: int <- copy x-f
209 result-f <- shift-left 1/log2
210 result-f <- multiply-fixed result-f, y-f
211 result-f <- add imaginary-f
215 # Scale (x, y) pixel coordinates to a complex plane where the viewport width
216 # ranges from (scene-cx - scene-width/2) to (scene-cx + scene-width/2).
217 # Viewport height just follows the viewport's aspect ratio.
219 fn viewport-to-real-f x: int, width: int, scene-cx-f: int, scene-width-f: int -> _/edx: int {
220 # 0 in the viewport goes to scene-cx - scene-width/2
221 # width in the viewport goes to scene-cx + scene-width/2
223 # x in the viewport goes to (scene-cx - scene-width/2) + x*scene-width/width
224 # At most two numbers being multiplied before a divide, so no risk of overflow.
225 var result-f/eax: int <- int-to-fixed x
226 result-f <- multiply-fixed result-f, scene-width-f
227 var width-f/ecx: int <- copy width
228 width-f <- shift-left 8/fixed-precision
229 result-f <- divide-fixed result-f, width-f
230 result-f <- add scene-cx-f
231 var half-scene-width-f/ecx: int <- copy scene-width-f
232 half-scene-width-f <- shift-right 1
233 result-f <- subtract half-scene-width-f
237 fn viewport-to-imaginary-f y: int, width: int, height: int, scene-cy-f: int, scene-width-f: int -> _/ebx: int {
238 # 0 in the viewport goes to scene-cy - scene-width/2*height/width
239 # height in the viewport goes to scene-cy + scene-width/2*height/width
241 # y in the viewport goes to (scene-cy - scene-width/2*height/width) + y*scene-width/width
242 # At most two numbers being multiplied before a divide, so no risk of overflow.
243 var result-f/eax: int <- int-to-fixed y
244 result-f <- multiply-fixed result-f, scene-width-f
245 var width-f/ecx: int <- copy width
246 width-f <- shift-left 8/fixed-precision
247 result-f <- divide-fixed result-f, width-f
248 result-f <- add scene-cy-f
249 var second-term-f/edx: int <- copy 0
251 var _second-term-f/eax: int <- copy scene-width-f
252 _second-term-f <- shift-right 1
253 var height-f/ebx: int <- copy height
254 height-f <- shift-left 8/fixed-precision
255 _second-term-f <- multiply-fixed _second-term-f, height-f
256 _second-term-f <- divide-fixed _second-term-f, width-f
257 second-term-f <- copy _second-term-f
259 result-f <- subtract second-term-f