A Mandelbrot set renderer (with zoom!) I implemented for fun in Rust.
output2.mp4
Running the project cargo r -r
will create a "slide show" of separate frames in ./data
. These
can then be stitched together with FFmpeg with something like:
ffmpeg -framerate 30 -i 'data/img%03d.png' -pix_fmt yuv420p output.mp4
Note If you're on Windows, you can install FFmpeg via Chocolatey with
choco install ffmpeg -y
Currently, there's no "nice" way for users to do any sort of configuration without editing the code directly. As I worked on this purely for my own fun, I'm not too interested in implementing that.
There's a few things you might want to edit:
What | Where | Use |
---|---|---|
point |
main.rs/create_frames() |
This is the point the video will be zooming towards |
scale_off |
main.rs/create_frames() |
This determines the amount each thread will zoom by. Changing the denominator is an easy way to affect how quickly/slowly the zoom happens |
width |
main.rs |
The output image width. The FFmpeg command I mention works with 1920 and 480 so these are the two I left here for now. Use 480 for testing and 1920 for a nicer render. This can in theory just be whatever number you want (I rendered a 50kp frame at some point) |
COLORS |
colors.rs |
The color pallete. This should be 11 elements, if you want to use more, you probably need to make sure the depth works and is handled correctly. |
MAX_DEPTH |
common.rs |
The maximum depth used in the Mandelbrot calculations. I have not played with this at all 👍 |
My SIMD code relies on a bunch of features that have not landed in stable Rust yet (it didn't
really need to use any of them but I just wanted to try some new stuff out) and thus you need
the Nightly toolchain to build those. They are put behind a simd
feature so that you can still
tinker with the rest of the project in stable Rust.
rustup default nightly
# Might also need a `rustup update`
cargo b -r -F simd
I ran some simple benchmarks with hyperfine and got the following results:
1920x1080p - 30 frames
Benchmark 1: Simple
Time (mean ± σ): 68.014 s ± 1.744 s [User: 66.984 s, System: 0.239 s]
Range (min … max): 66.985 s … 71.085 s 5 runs
Benchmark 2: Parallel (2 threads)
Time (mean ± σ): 41.453 s ± 4.840 s [User: 75.976 s, System: 0.227 s]
Range (min … max): 38.249 s … 49.833 s 5 runs
Benchmark 3: SIMD
Time (mean ± σ): 39.394 s ± 0.522 s [User: 38.457 s, System: 0.203 s]
Range (min … max): 38.724 s … 39.954 s 5 runs
Benchmark 4: Parallel
Time (mean ± σ): 20.054 s ± 1.140 s [User: 123.051 s, System: 0.649 s]
Range (min … max): 18.492 s … 21.706 s 5 runs
Benchmark 5: SIMD + Parallel
Time (mean ± σ): 14.300 s ± 0.235 s [User: 70.323 s, System: 0.490 s]
Range (min … max): 13.912 s … 14.477 s 5 runs
Benchmark 6: SIMD (f64x8) + Parallel
Time (mean ± σ): 10.257 s ± 1.228 s [User: 54.645 s, System: 0.259 s]
Range (min … max): 8.062 s … 10.859 s 5 runs
Towards the ends of both videos in the assets folder, you can see what I presume to be the limitations of 64-bit floating number accuracy. Ways to go past that include using 128-bit floats or multiple-precision floating-point numbers. Neither of these natively support Windows which is why I chose to not go any further.
There's also astro-float
which is written in native Rust
but do keep in mind that it is much slower than using standard floats (as is likely the case with
the 2 other alternatives I mentioned previously, I've just happened to have only used this one).