fractal
fractal copied to clipboard
♾️ A multithreaded fractal renderer in Rust
fractal.rs
A multithreaded fractal renderer in Rust

Online release
The live wasm-compiled release is accessible here. Due to some rust wasm compiler limitations, the web version is single threaded and therefore slower than the native version
How to run
Navigate to the cloned repo and execute
cargo run --release
the Mandelbrot set
The Mandelbrot set is the set of complex numbers $c$ for which the iterated sequence $z_{n + 1} = z_{n^2} + c; z_0 = 0$ does not diverge in modulus to infinity, i.e. remains bounded.
Computationally, for every pixel of the rendering area, the above equation is iterated a fixed number of times, and a pixel color is associated with the rate of divergence of the sequence. Typically the black color is associated with a converging point (whose norm stays within some fixed threshold after the iteration count), and a coloring scheme is chosen to clarify the renderings
Optimisations
Multiple optimisations can be implemented to speed up the sequence interation. Choosing $z = x + i y$ and $c = x_0 + i y_0$, we can expand
$$z^2 = x^2 - y^2 + 2 i x y$$ $$x = Re(x^2 + c) = x^2 - y^2 + x_0$$ $$y = Im(x^2 + c) = 2 x y + y_0$$
Further, we can pre-compute $z^2$ since it will be used in both the iterations and the exit condition checks. A pixel iteration can then be written that way
let mut z = Complex::zero();
let mut z_sqr = Complex::zero();
let mut iter = 0;
loop {
z.i = 2. * z.r * z.i + c.i;
z.r = z_sqr.r - z_sqr.i + c.r;
z_sqr.r = z.r * z.r;
z_sqr.i = z.i * z.i;
iter += 1;
if iter >= max_iter || z_sqr.r + z_sqr.i > escape_radius_sqr {
break;
}
}
Coloring
Mutlistage multithreaded renderer
For deep zoom levels, the number of iterations has to be increased to maintain an appropriate level of details, which makes for slower rendering time and reduces the exploration smoothness. Multistage rendering works by splitting up a rendering task in $n$ stages, each of which doubles the rendering area up to the screen size. The time required to render the $n^{th}$ stage is $\frac{1}{2^n}$ the time it takes to render the full screen size. Since:
$$\sum_{k=1}^{\inf} \frac{1}{2^k} = 1$$
Regardless of the number of stages, the rendering time is bounded to double the rendering time of the final stage.
The fractal rendering is trivially parallelizable, since every pixel color can be computed independently of each other. The renderer maintains a thread pool sharing the work at every stage by rendering a fraction of the stage's pixels. Every animation frame, the canvas pauses the threads execution and interleaves their pixel buffers onto the canvas buffer to smoothly display progress.

Limitations
The iterator uses f64
to compute the complex series, which leads to numerical precision issues for deep zoom levels. Typically after a zoom multiplier of $10^{15}$, numerical limits start to degrade the renders

Web assembly compilation
The renderer can be compiled to wasm by installing and running wasm-pack
wasm-pack build --target web --release
The wasm
compiler doesn't support yet the std::thread
library, therefore a single thread is used on web. For other targets, the thread count is set automatically to the cpu count