A moderately-performant, real-time fractal visualizer built with C++23 (my beloved) and OpenGL/WebGL.
try it out at gordienovak.com
- gpu rendering of fractals with side-by-side comparison (e.g. mandelbrot set & julia set -› see connected/non-connected behavior)
- GUI that's slightly hostile but gets the job done
- built-in support for cross-compiling to local machine code and the web.
I used a lot of libraries that made this project waaaay easier:
SDL3as the window provider and OpenGL context-handlerGLADfor OpenGL function loading.Dear ImGuifor the user interface.Boostfor multiprecision floats (currently unused, it's a WIP)SDL_APIa custom library i wrote that handles a lot of the behind-the-scenes SDL gruntwork. currently closed-sourcefstringcustom stack-allocated string library i wrote inC++for low-cost debugging & simple type conversion to UTF8.- uses
#include <charconv>for type conversions.
- uses
C++23for the core of the project. you should totally try c++23 it's so good if you hate yourself.GLSLfor the GPU-side rendering.CMake Languagefor build system builder.HTML5for web-cross compilation shell-fileJavaScriptbecause someone decided it was the best language for the web.WASM (WebAssembly)as the compilation target forC++code.
Apple Clang 17andgcc-15for the local c++23 compilers. its usually good to have twohomebrewas the package manager for maccmakefor building becauseMakefilesare scaryNinjaas the generatoremcmakeas the toolchain specifier forcmakefor setting up build environmentsemmakeas the dependency installer foremscriptenlibraries.
emscripten SDKfor cross-compiling to WASM.CLionas the primary IDE.Visual Studio CodeorZedfor smaller tasks and testing.fsanitize=undefined,addressfor memory leak debugging.
here's my general approach to programming this project in general:
-
compile-time work: Utilize the flexibility of
C++23 constexpr/consteval/constinitto create very low startup times by offloading work to the compile-time interpreter. -
polymorphic fractal behavior: Make Fractal classes polymorphic such that it is easier to add more rendering styles (like GPU & CPU)
-
interface inheritance: inhertance should only occur with abstract base classes that contain no member variables.
-
input flow (single responsibility principle #1): input is propagates clearly through a chain of command:
FractalExplorerdistributes input to appropriateFractalPanels, yetFractalPanelsdecide how to interpret that input. -
data responsibility (single responsibility principle #2): Every class can either be in control of raw data, or in control of other classes that manage raw data. For example,the
FractalCursorclass only exists within aFractalExplorer, so making a separate class seems silly. However, it isolates cursor-related logic from the main logic loop ofFractalExplorerto reduce coupling. -
inplace-allocation: If something can be allocated locally without another heap allocation, it should be done to reduce heap-fragmentation, especially with WASM. This is achieved through the beauty of
C-styleunionstructs. Although unsafe, they are tagged withenums and can skip over checks typically required ofstd::variant<Ts...>. Additionally, by storing everything in localstd::arrays orC-style arrays with maximum limit caps, we can make reasonable estimates about the scope of the program while using about only ~500 bytes of memory per fractal. That's about0.00005%of a gigabyte. -
documentation: attempt to name things such that documentation is not necessary, but attempt to document key types.
-
move-semantics use
C++11move semantics where-ever possible to reduce use-after-free dangers. this includesstd::unique_ptr<T>. -
there's more but im lazy
Here's primarily how the project is structured:
- The
FractalExplorerhandles the "Compile" menu in the bottom right corner and generating new fractals - The
FractalPanelshandle the "Fractal" menu in the top left corner and handle propagating user input to the fractal - The
Fractalclasses actuall handle rendering the fractals
now that i write it out it's not so complicated.
You can't build it yourself right now because it utilizes an API I wrote that's not done yet. (See the absolute path in CmakeLists.txt).
uhhh i'll fix it later.