Rasmus.krats.se

Reminiscing this and that, on the web since 1995

Julia fractals in Rust wasm

Published tagged , , , , .

I like to write programs to render fracals, and especially Julia set fractals. I also like the Rust programming language. Among other advantages it happens to be great for compiling to wasm, the new format for executing code in a web browser. And I like to code stuff for the webb. So how come I haven't written a wasm Julia renderer in Rust yet?

Well, now I have.

WebAssembly seems to be all the rage currently. While I certainly wouldn't mind replacing all javascript code with rust, that seems impractical for things running in the browser, as everything about the DOM and event handling still have to go through javascript bindings. As it is a good thing to keep javascript minimal for progressive enhancement, that makes the code contain very little more than event bindings anyway.

But then I read Making really tiny WebAssembly graphics demos by Cliff L. Biffle and got inspired. In this case, the required contact area between the wasm code and the javascript runtime is minimal: There is a shared buffer to draw the pixels and a function to call to render a frame, and that's it.

If your browser supports wasm, you should see an animated jula set here.

Rust has a bit of a bad reputation for creating large binaries, but that is not true for wasm binaries. The fractal renderer on this page is 907 bytes (not kilobytes). A lot smaller than a jpeg of a single frame.

Performance and cheating

Wasm is a bit faster than javascript, but to get a reasonably sized Julia image animating nicely on as low cpu usage as possible, some trickery i still usefull.

The first is obvious: The julia set is symetric, so I can draw two pixels, (x,y) and (-x,-y), for each one I calculate.

Also, requestAnimationFrame will typically trigger at 60 Hz (as long as a frame can render in 16.6 ms or less). On my machine, it is fast enough, so I get 60 fps, but then one core is pretty much occupied and the fan starts to sound. If I draw a new frame only on every other call, I get 30 fps and no fan, which I find nicer. On a slower machine, it may gake upwards of 33 ms to draw a frame, and then I skip 1/60 s, giving an effektive 20 fps, which I also think is quite ok.

I also draw a relatively small image (only 360 by 270 pixels), the html canvas element automatically scales it up to the width given by css.

The last optimization requires a bit of explanation:

The Julia set is closely related to the Mandelbrot set. The parameter that I'm animating here is a point in the complex plane. If that point is inside the Mandelbrot set, then by definition the origo of that Julia image is inside the Julia set.

But not only that; if origo is inside the set, there will be a continous area around the origo that is inside the set, and otherwise there will be no areas in the set (there may be some points approaching infinite iteration, but iteration counts will fall steeply beside them, so the number of pixels with very high iteration count will be small). But the usefull thing here is that when there is a continous area reaching maximum iteration counts, an image as scaled out as this wont be noticably distracted by settig a lower max iteration value. So either a high maximum iteration count won't be too computationally expensive, or it isn't really interesting (in an animation like this). So I start by checking if my c value is inside the Mandelbrot set, and set a lower or higher max iteration value accordingly.

If you are interested, the code is available on github..

Write a comment

Your name (or pseudonym).

Not published, except as gravatar.

Your homepage / presentation.

No formatting, except that an empty line is interpreted as a paragraph break.