about summary refs log tree commit diff stats
path: root/apps/mandelbrot-fixed.mu
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2021-07-16 08:09:42 -0700
committerKartik K. Agaram <vc@akkartik.com>2021-07-16 08:28:56 -0700
commit44d26b77c45668c9b0c99894a4294cec004361fe (patch)
tree68a5dcd4971873efd4ce184e9bf9a531c2161813 /apps/mandelbrot-fixed.mu
parentac45f097153afd3a89f43886e4124c5b2c26b98a (diff)
downloadmu-44d26b77c45668c9b0c99894a4294cec004361fe.tar.gz
.
Diffstat (limited to 'apps/mandelbrot-fixed.mu')
-rw-r--r--apps/mandelbrot-fixed.mu262
1 files changed, 262 insertions, 0 deletions
diff --git a/apps/mandelbrot-fixed.mu b/apps/mandelbrot-fixed.mu
new file mode 100644
index 00000000..fc33aae1
--- /dev/null
+++ b/apps/mandelbrot-fixed.mu
@@ -0,0 +1,262 @@
+# Mandelbrot set using fixed-point numbers.
+#
+# Install:
+#   $ git clone https://github.com/akkartik/mu
+#   $ cd mu
+# Build on Linux:
+#   $ ./translate apps/mandelbrot-fixed.mu
+# Build on other platforms (slow):
+#   $ ./translate_emulated apps/mandelbrot-fixed.mu
+# Run:
+#   $ qemu-system-i386 code.img
+
+fn main screen: (addr screen), keyboard: (addr keyboard), data-disk: (addr disk) {
+  # Initially the viewport is centered at 0, 0 in the scene.
+  var scene-cx-f: int
+  var scene-cy-f: int
+  # Initially the viewport shows a section of the scene 4 units wide.
+  var scene-width-f: int
+  copy-to scene-width-f, 0x400/4
+  {
+    mandelbrot screen scene-cx-f, scene-cy-f, scene-width-f
+    # move at an angle slowly towards the edge
+    var adj-f/eax: int <- multiply-fixed scene-width-f, 0x12/0.07
+    subtract-from scene-cx-f, adj-f
+    add-to scene-cy-f, adj-f
+    # slowly shrink the scene width to zoom in
+    var tmp-f/eax: int <- multiply-fixed scene-width-f, 0x80/0.5
+    copy-to scene-width-f, tmp-f
+    loop
+  }
+}
+
+# Since they still look like int types, we'll append a '-f' suffix to variable
+# names to designate fixed-point numbers.
+
+fn int-to-fixed in: int -> _/eax: int {
+  var result-f/eax: int <- copy in
+  result-f <- shift-left 8/fixed-precision
+  {
+    break-if-not-overflow
+    abort "int-to-fixed: overflow"
+  }
+  return result-f
+}
+
+fn fixed-to-int in-f: int -> _/eax: int {
+  var result/eax: int <- copy in-f
+  result <- shift-right-signed 8/fixed-precision
+  return result
+}
+
+# The process of throwing bits away always adjusts a number towards -infinity.
+fn test-fixed-conversion {
+  # 0
+  var f/eax: int <- int-to-fixed 0
+  var result/eax: int <- fixed-to-int f
+  check-ints-equal result, 0, "F - test-fixed-conversion - 0"
+  # 1
+  var f/eax: int <- int-to-fixed 1
+  var result/eax: int <- fixed-to-int f
+  check-ints-equal result, 1, "F - test-fixed-conversion - 1"
+  # -1
+  var f/eax: int <- int-to-fixed -1
+  var result/eax: int <- fixed-to-int f
+  check-ints-equal result, -1, "F - test-fixed-conversion - -1"
+  # 0.5 = 1/2
+  var f/eax: int <- int-to-fixed 1
+  f <- shift-right-signed 1
+  var result/eax: int <- fixed-to-int f
+  check-ints-equal result, 0, "F - test-fixed-conversion - 0.5"
+  # -0.5 = -1/2
+  var f/eax: int <- int-to-fixed -1
+  f <- shift-right-signed 1
+  var result/eax: int <- fixed-to-int f
+  check-ints-equal result, -1, "F - test-fixed-conversion - -0.5"
+  # 1.5 = 3/2
+  var f/eax: int <- int-to-fixed 3
+  f <- shift-right-signed 1
+  var result/eax: int <- fixed-to-int f
+  check-ints-equal result, 1, "F - test-fixed-conversion - 1.5"
+  # -1.5 = -3/2
+  var f/eax: int <- int-to-fixed -3
+  f <- shift-right-signed 1
+  var result/eax: int <- fixed-to-int f
+  check-ints-equal result, -2, "F - test-fixed-conversion - -1.5"
+  # 1.25 = 5/4
+  var f/eax: int <- int-to-fixed 5
+  f <- shift-right-signed 2
+  var result/eax: int <- fixed-to-int f
+  check-ints-equal result, 1, "F - test-fixed-conversion - 1.25"
+  # -1.25 = -5/4
+  var f/eax: int <- int-to-fixed -5
+  f <- shift-right-signed 2
+  var result/eax: int <- fixed-to-int f
+  check-ints-equal result, -2, "F - test-fixed-conversion - -1.25"
+}
+
+# special routines for multiplying and dividing fixed-point numbers
+
+fn multiply-fixed a-f: int, b-f: int -> _/eax: int {
+  var result/eax: int <- copy a-f
+  result <- multiply b-f
+  {
+    break-if-not-overflow
+    abort "multiply-fixed: overflow"
+  }
+  result <- shift-right-signed 8/fixed-precision
+  return result
+}
+
+fn divide-fixed a-f: int, b-f: int -> _/eax: int {
+  var result-f/eax: int <- copy a-f
+  result-f <- shift-left 8/fixed-precision
+  {
+    break-if-not-overflow
+    abort "divide-fixed: overflow"
+  }
+  var dummy-remainder/edx: int <- copy 0
+  result-f, dummy-remainder <- integer-divide result-f, b-f
+  return result-f
+}
+
+# multiplying or dividing by an integer can use existing instructions.
+
+# adding and subtracting two fixed-point numbers can use existing instructions.
+
+fn mandelbrot screen: (addr screen), scene-cx-f: int, scene-cy-f: int, scene-width-f: int {
+  var a/eax: int <- copy 0
+  var b/ecx: int <- copy 0
+  a, b <- screen-size screen
+  var width/esi: int <- copy a
+  width <- shift-left 3/log2-font-width
+  var height/edi: int <- copy b
+  height <- shift-left 4/log2-font-height
+  var y/ecx: int <- copy 0
+  {
+    compare y, height
+    break-if->=
+    var imaginary-f/ebx: int <- viewport-to-imaginary-f y, width, height, scene-cy-f, scene-width-f
+    var x/eax: int <- copy 0
+    {
+      compare x, width
+      break-if->=
+      var real-f/edx: int <- viewport-to-real-f x, width, scene-cx-f, scene-width-f
+      var iterations/esi: int <- mandelbrot-iterations-for-point real-f, imaginary-f, 0x400/max
+      iterations <- shift-right 3
+      var color/edx: int <- copy 0
+      {
+        var dummy/eax: int <- copy 0
+        dummy, color <- integer-divide iterations, 0x18/24/size-of-cycle-0
+        color <- add 0x20/cycle-0
+      }
+      pixel screen, x, y, color
+      x <- increment
+      loop
+    }
+    y <- increment
+    loop
+  }
+}
+
+fn mandelbrot-iterations-for-point real-f: int, imaginary-f: int, max: int -> _/esi: int {
+  var x-f/esi: int <- copy 0
+  var y-f/edi: int <- copy 0
+  var iterations/ecx: int <- copy 0
+  {
+    var done?/eax: boolean <- mandelbrot-done? x-f, y-f
+    compare done?, 0/false
+    break-if-!=
+    compare iterations, max
+    break-if->=
+    var x2-f/edx: int <- mandelbrot-x x-f, y-f, real-f
+    var y2-f/ebx: int <- mandelbrot-y x-f, y-f, imaginary-f
+    x-f <- copy x2-f
+    y-f <- copy y2-f
+    iterations <- increment
+    loop
+  }
+  return iterations
+}
+
+fn mandelbrot-done? x-f: int, y-f: int -> _/eax: boolean {
+  # x*x + y*y > 4
+  var tmp-f/eax: int <- multiply-fixed x-f, x-f
+  var result-f/ecx: int <- copy tmp-f
+  tmp-f <- multiply-fixed y-f, y-f
+  result-f <- add tmp-f
+  compare result-f, 0x400/4
+  {
+    break-if->
+    return 0/false
+  }
+  return 1/true
+}
+
+fn mandelbrot-x x-f: int, y-f: int, real-f: int -> _/edx: int {
+  # x*x - y*y + real
+  var tmp-f/eax: int <- multiply-fixed x-f, x-f
+  var result-f/ecx: int <- copy tmp-f
+  tmp-f <- multiply-fixed y-f, y-f
+  result-f <- subtract tmp-f
+  result-f <- add real-f
+  return result-f
+}
+
+fn mandelbrot-y x-f: int, y-f: int, imaginary-f: int -> _/ebx: int {
+  # 2*x*y + imaginary
+  var result-f/eax: int <- copy x-f
+  result-f <- shift-left 1/log2
+  result-f <- multiply-fixed result-f, y-f
+  result-f <- add imaginary-f
+  return result-f
+}
+
+# Scale (x, y) pixel coordinates to a complex plane where the viewport width
+# ranges from -2 to +2. Viewport height just follows the viewport's aspect
+# ratio.
+
+fn viewport-to-real-f x: int, width: int, scene-cx-f: int, scene-width-f: int -> _/edx: int {
+  # 0 in the viewport       goes to scene-cx - scene-width/2 
+  # width in the viewport   goes to scene-cx + scene-width/2
+  # Therefore:
+  # x in the viewport       goes to (scene-cx - scene-width/2) + x*scene-width/width
+  # At most two numbers being multiplied before a divide, so no risk of overflow.
+  var result-f/eax: int <- int-to-fixed x
+  result-f <- multiply-fixed result-f, scene-width-f
+  var width-f/ecx: int <- copy width
+  width-f <- shift-left 8/fixed-precision
+  result-f <- divide-fixed result-f, width-f
+  result-f <- add scene-cx-f
+  var half-scene-width-f/ecx: int <- copy scene-width-f
+  half-scene-width-f <- shift-right 1
+  result-f <- subtract half-scene-width-f
+  return result-f
+}
+
+fn viewport-to-imaginary-f y: int, width: int, height: int, scene-cy-f: int, scene-width-f: int -> _/ebx: int {
+  # 0 in the viewport       goes to scene-cy - scene-width/2*height/width
+  # height in the viewport  goes to scene-cy + scene-width/2*height/width
+  # Therefore:
+  # y in the viewport       goes to (scene-cy - scene-width/2*height/width) + y*scene-width/width
+  #  scene-cy - scene-width/width * (height/2 + y)
+  # At most two numbers being multiplied before a divide, so no risk of overflow.
+  var result-f/eax: int <- int-to-fixed y
+  result-f <- multiply-fixed result-f, scene-width-f
+  var width-f/ecx: int <- copy width
+  width-f <- shift-left 8/fixed-precision
+  result-f <- divide-fixed result-f, width-f
+  result-f <- add scene-cy-f
+  var second-term-f/edx: int <- copy 0
+  {
+    var _second-term-f/eax: int <- copy scene-width-f
+    _second-term-f <- shift-right 1
+    var height-f/ebx: int <- copy height
+    height-f <- shift-left 8/fixed-precision
+    _second-term-f <- multiply-fixed _second-term-f, height-f
+    _second-term-f <- divide-fixed _second-term-f, width-f
+    second-term-f <- copy _second-term-f
+  }
+  result-f <- subtract second-term-f
+  return result-f
+}