A real-time wireframe demoscene in C++20 and OpenGL 4.1 Core for macOS, Linux and Windows. It renders 59 mathematically unusual objects, from exact 4D polychora to minimal surfaces, strange attractors and procedurally discovered shapes. A CPU effect engine warps them, an HDR post pass composites them over a timed logo show and a procedural art background, and a tracker module drives the motion.
Copyright (c) 2026 Ulf Bertilsson. MIT licensed. Current version: v4.39.
Topics: wireframe demoscene opengl glfw cpp20 4d-geometry procedural-geometry shaders libopenmpt tracker-music creative-coding macos
Contents: Quick start | Feature guide | Screenshots | Controls and options | Build | Tests and CI | Repository layout | Performance | Troubleshooting | Changelog
See also: ARCHITECTURE.md (how it works), docs/EFFECTS.md (scene and effect catalogue), design.md (mathematical provenance), CHANGELOG.md.
brew install cmake glfw sdl2 libopenmpt # macOS; SDL2 + libopenmpt are optional (music)
make run # build and launch with Stars by cs127 (125 BPM)
./build/impossible_wireframe --scene 54 # jump straight to a sceneThe window opens on the logo show; press Right to step through scenes, T to toggle the scroller, Esc to quit.
Each feature below is a separate, independently degradable layer. If a shader fails to compile or the logo pack is missing, the app drops that layer instead of crashing.
| Ids | Family | What it is |
|---|---|---|
| 0, 1, 9 | 4D polychora | Exact 600-cell projection, exact 600-cell face-plane slice, and the dual-derived 120-cell projection, all rotated in 4D and projected to 3D |
| 2-5 | Triply periodic surfaces | Gyroid, Schwarz P, Schwarz D, Neovius, extracted from sampled implicit fields |
| 6, 10, 11, 12 | S3 and hyperbolic art | Hopf fibres, Clifford torus (stereographic projection), a Poincare-ball hyperbolic chamber, a quaternion-Julia boundary slice (visualisations, not canonical honeycombs or exact fractals) |
| 7, 16-24 | Parametric surfaces | Boy surface, Mobius strip, Klein bottle, Enneper, helicoid, catenoid, Dini, pseudosphere, Roman surface, cross-cap |
| 8, 15 | Procedural | Animated superformula; a deterministic seeded harmonic "discovered object" that re-rolls over time |
| 13, 25-28 | Curves and knots | Lissajous knot, torus knot, Viviani curve, spherical spiral, hypotrochoid knot |
| 14, 29-31 | Chaos | Lorenz, Duffing, Rossler and Thomas attractor trajectories |
| 32-40 | Fractal and algebraic | Sierpinski tetrahedron, heart, Barth-like, tanglecube, Chmutov-like, Cayley cubic, Kummer-like, Goursat, blob lattice |
| 41-50 | "Unknown lab" ports | Ten procedural wire objects (supercage crown, knot-bundle reactor, phyllotaxis vortex, ruled singularity fan, prime-lobed lantern, aperiodic orbit nest, superformula organism, knot lattice, golden spiral skeleton, twisted ruled shell) |
| 51-56 | Showpieces | Geodesic dome, tube trefoil, DNA helix, atom orbits, wave terrain, Platonic compound. Clean, readable objects with a periodic "Pure form" window |
| 57-58 | Golden orbits | Ported from uber256-lab: GALAXY (de Jong attractor) and CLIFF (Clifford attractor) as glowing 3D orbit clouds with a golden glimmer. Never warped in auto mode; their own parameters morph |
Expensive scenes are cached and rebuilt only at their own update rate (for example the 600-cell at 60 Hz, the discovered object at 1/8 Hz), not at the display refresh. data/object_catalog.csv records provenance for every object. Select a scene with Left/Right or --scene N; Space returns to automatic beat and bar sequencing.
A CPU vertex-warper system reshapes the active mesh every frame.
- 80 effects displace, twist, subdivide, bridge or fracture the mesh (swirls, lensing, shatter, wormhole, quasicrystal and so on). Full list in docs/EFFECTS.md.
- 16 "unknown" effects (64-79) go beyond plain warps: a Hopf rotation through S3, a Klein fold, a hyperbolic Moebius drift in the Poincare ball, Thomas-attractor flow, a Keplerian galactic disk, harmonic bloom, FCC crystal snapping, growing tendrils, a counter-rotating shell cage, barbed wire, a torus attractor, a Moebius map, a soliton wave, quaternion squaring, Lissajous satellites and audio-driven spectral harmonics. Six new recipes use them: Hopf Fibration Storm, Hyperbolic Drift, Keplerian Galaxy, Barbed Crystal, Soliton Garden, Quaternion Bloom.
- 30 curated recipes chain up to six effects, for example Singularity Bloom, Impossible Cathedral, Quantum Shatter, Wormhole Lattice, Black Star, Wireframe Supernova.
- Mutation: in auto mode each scene gets a recipe (rotated by scene index) plus two deterministic procedural stages for per-scene variety.
Pre-rolls the seed. - Audio driven: bass, mid and treble bands from the music (or synthetic sines without audio) modulate the amounts, and
--bpmsyncs the beat pulse. - Fail-safe and bounded: a stage that empties the mesh is skipped, and one that produces non-finite vertices is undone and ends the chain, so the frame always has a valid mesh. Every stage stays within a 60,000-edge budget: copy and echo effects size themselves to fit, and anything still above is thinned evenly.
- Pure form: showpiece scenes (51-56) skip mutation and show with no warp for part of each cycle so the object itself can be read.
Twenty full-screen UBER cards are the opening backdrop. The cycle is time-driven and independent of the scene sequencer, 14.5 s per card:
| Phase | Duration | What happens |
|---|---|---|
| Fade in | 1.0 s | Card eases up from black with a slow Ken Burns zoom |
| Hold | 3.5 s | Card at full brightness; wires and exposure are trimmed slightly so the logo stays rich and the wireframe stays visible |
| Fade out | 1.0 s | Card eases back to black |
| Black break | 9.0 s | Pure wireframe over the procedural art background (below) |
Cards load from UBER_Fullscreen_Logo_Pack/ next to the executable (or --logos DIR). The logo is drawn first into the HDR buffer, so the wires glow additively on top.
When the logo is away, the post shader fades in a living procedural background. Everything is uneven and drifting: light pools brighten some regions while others sink into darkness, and the whole field breathes with the beat.
- Neon ink filaments: domain-warped ridged noise.
- Cracked glass: Voronoi seams that light up on the beat.
- Kaleidoscope rose rings: 8-way mirrored interference.
- Orbit-trap Julia fractal, op-art moire and Turing spots: surface in slow waves.
- 3D metaball organism: a raymarched cluster of five metaballs that melt into each other, breathe with the music and are lit with diffuse, fresnel and specular shading plus a volumetric halo.
Scenes 57 and 58 port the two strange-attractor intros from uber256-lab (205 and 210 byte DOS programs):
- The maps are the originals: de Jong
x' = sin(a y) - cos(b x), y' = sin(c x) - cos(d y)with a=1.4, b=-2.3, c=2.4, d=-2.1, and Cliffordx' = sin(a y) + c cos(a x), y' = sin(b x) + d cos(b y)with a=-1.4, b=1.6, c=1.0, d=0.7, from the same starting points. A bounded companion coordinatezgives the cloud depth without touching x and y, so seen face-on it is the original picture. - Hits become light: each of about 14,000 orbit points is a short dash aligned with the attractor's filaments (found by tracking a neighbouring orbit, which converges to the unstable direction). Additive blending with depth testing off means dense regions glow, like the original's "brightness = number of hits".
- Morph and trail: a and d drift with the originals' speed (0.0006 and 0.0004 per frame at about 70 Hz), bounded by a sine so the shape never decays into noise. A sparser orbit from 1.6 s earlier is the fading trail.
- Golden glimmer: gold dashes in two shades, each twinkling on its own phase, a shimmer band sweeping across the cloud, scattered four-point glints, and gold horizontal-plus-vertical streaks in the post pass. Screen warps, colour grading and the rainbow tint are switched off for these scenes, and the art background is dimmed.
- Facing the camera: the clouds are flat-ish, so the scene cancels the renderer's constant spin and keeps a gentle sway instead.
Golden Galaxy at 6 s (spray of arcs), during a logo hold, and at 36 s (closed into shells); Golden Cliff over a logo card.
During black breaks, every 6 seconds one warp takes the screen with a smooth envelope: swirl, radial ripples, kaleidoscope fold, mosaic crunch, datamosh block shift, liquid wobble or mirror horizon. On top: continuous hue drift, solarize flashes on the downbeat and neon posterize bursts. All of it fades out when a logo is on screen so the picture is never smeared.
Applied to the combined logo, wireframe and travellers frame in an RGBA16F HDR target: gravitational lensing, a refractive shockwave ring, chromatic aberration, barrel breathing, an SDF nested-triangle sculpture, holographic interference, beat-gated glitch slices, anamorphic blue lens streaks, radial light rays from bright wires, bloom, scanlines, film grain, vignette, a soft ground shadow and an ACES filmic tone map. Logo-aware gain keeps wires readable and the logo unwashed.
Small octahedra (one per lane) weave back and forth through the wireframe, depth sorted so they pass in front of and behind the mesh.
A beat-synced 5x7 pixel-font ticker along the bottom shows the scene index, name, provenance, the active recipe and the BPM, for example [14] LORENZ ATTRACTOR - numerical trajectory * Black Star 132BPM. It flashes on each downbeat and scrolls at a constant 90 px/s. Toggle with T or start without it using --no-scroller.
SDL2 and libopenmpt play the default soundtrack, Stars by cs127 (CC BY 4.0, see Music), and split it into bass (<350 Hz), mid and treble (>3 kHz) bands in the audio callback. The bands drive glow, bloom, effect amounts and the background. Without audio the demo runs on a deterministic BPM clock (Timeline). Use --music PATH for your own module or WAV; the tempo is read from the module unless you pass --bpm. The ticker ends with the music credit.
--screenshot PATH --frames N captures frame N headlessly (used for every image in this README), --scene N jumps to a scene, --quality F renders the HDR target and the art pass at a fraction of the resolution, --no-post bypasses the FX chain, --stats prints fps and CPU time per frame every 5 seconds, --no-vsync uncaps the frame rate for benchmarking. Clean shutdown on Ctrl-C.
New unknown-effect recipes (left to right, top to bottom: Hopf Fibration Storm, Hyperbolic Drift, Keplerian Galaxy, Barbed Crystal, Soliton Garden, Quaternion Bloom; geodesic dome, --no-post):
| Key | Action |
|---|---|
| Left / Right | Previous / next scene (enters manual mode) |
| Space | Back to automatic beat and bar sequencing |
| T | Toggle the text scroller |
| R | Toggle effect-recipe mode (auto per-scene recipe + mutation, or one fixed curated recipe) |
| Up / Down | In recipe mode, cycle the 30 recipes |
| P | Re-roll the mutation seed |
| Esc | Quit |
| Option | Meaning |
|---|---|
--scene N |
Start on scene N (0-58) in manual mode |
--recipe N |
Start in recipe mode with curated recipe N (0-29) |
--bpm N |
Tempo; default is the module's own tempo (Stars: 125), else 132. Also syncs the post-pass beat pulse |
--music PATH |
Play a tracker module or wav |
--logos DIR |
Directory of UBER_*_1920x1080.jpg cards |
--window WxH |
Initial window size (320x200 to 3840x2160, default 1440x900) |
--fullscreen |
Full-screen on the primary monitor (overrides --window), vsynced |
--stats |
Print fps and CPU time per frame (scene, effects, upload) every 5 seconds |
--no-vsync |
Do not wait for the display refresh (benchmarking) |
--quality F |
Render the HDR target and the art pass at F of the screen resolution (0.25-1.0); 0.5 gives about +35% fps |
--no-post |
Skip the screen-space FX (tone-map only); fastest |
--no-scroller |
Start with the ticker off |
--screenshot PATH |
Save a PNG |
--frames N |
Exit after N frames; with --screenshot, capture on frame N |
--version, -h |
Print version / help |
The build system is CMake. A Makefile wraps it for convenience — the
simplest path is just:
make # configure (if needed) + build (Release, parallel)
make test # build + run the geometry/scroller tests
make run # build + launch (Stars by cs127, tempo from the module)
make run-silicon# Silicon Dancer by Drozerix
make run-pulse # launch with the CC0 Wireframe Pulse track
make diag # dump the resolved toolchain (SDK, GLFW, SDL2, openmpt)
make clean # remove the build/ directory
make reconfigure# wipe build/ and reconfigure (recover from a stale cache)The Makefile auto-reconfigures when CMakeLists.txt or the build type changes
(a stamp file tracks this), so a stale cache rarely needs manual cleanup. To
pin a specific macOS SDK or enable strict warnings:
make reconfigure CMAKE_EXTRA=-DIW_SYSROOT=$(xcrun --show-sdk-path)
make CMAKE_EXTRA=-DIW_WARNINGS_AS_ERRORS=ONOr use CMake directly (macOS via Homebrew):
brew install cmake glfw
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
ctest --test-dir build --output-on-failure
./build/impossible_wireframeOptional audio playback (libopenmpt + SDL2) is detected automatically; on
macOS add brew install sdl2 libopenmpt. Linux: install a C++20 compiler,
CMake, OpenGL development headers and GLFW3 development package, then use the
same commands (or make).
macOS build notes
- If you hit
No rule to make target '.../MacOSX.sdk/System/Library/Frameworks/OpenGL.framework', yourbuild/was configured against an SDK path that no longer exists (stale CMakeCache). Just runmake reconfigure(it wipesbuild/and reconfigures) — the Makefile'sconfiguretarget does this automatically now.- If you hit
tapi error: malformed file .../libSystem.B.tbd ... unknown architecture(orOpenGL.tbd,libc++.tbd) — even during CMake's compiler check — your activeldcan't parse the selected SDK's.tbdstubs. This is usually a stale/mismatched CLT SDK (a removed CLT install, or a CLT newer than the installed Xcode/ld). Fix:make reconfigure(CMakeLists now pinsCMAKE_OSX_SYSROOTtoxcrun --show-sdk-pathandCMAKE_OSX_ARCHITECTURESto the host arch beforeproject()). If a different SDK is needed:make reconfigure CMAKE_EXTRA=-DIW_SYSROOT=$(xcrun --show-sdk-path). As a last resort, align the toolchain:sudo xcode-select -s /Library/Developer/CommandLineTools(or an Xcode path) so the ld and SDK come from the same install.- On Apple the project links a real
libOpenGL.dylibwhen available, else-framework OpenGL(never theOpenGL::GLSDK-baked path), and adds the OpenGL include dir so<OpenGL/gl3.h>resolves without relying on toolchain-default SDK search.
If OpenGL/GLFW are absent, CMake still builds iw_geometry and geometry_tests, allowing headless CI validation.
make test builds and runs geometry_tests (about 8 s). Project targets compile with -Wall -Wextra -Wpedantic -Werror (/W4 /WX on MSVC). The suite checks:
- canonical V/E/F counts for the tesseract, 16-cell, 24-cell and 600-cell, and V/E for the 120-cell;
- every procedural family and all 59 scenes for finite vertices, valid indices, no self or duplicate edges;
- every effect recipe (scenes 0, 1, 2, 5, 11, 26 and every 8th, times all recipes) for mesh validity;
- timeline beat and bar math, the
--windowparser, the scroller code strip and shader conventions.
GitHub Actions (.github/workflows/ci.yml) builds and tests on macOS 15, Ubuntu 24.04 and Windows 2022 for every push. On Windows an in-tree OpenGL loader (include/GLWin32.hpp) fetches the GL 4.1 functions through glfwGetProcAddress, since opengl32.dll only exports GL 1.1. CI builds and tests it; it has not been run on a physical Windows machine yet.
| Path | Contents |
|---|---|
src/, include/ |
C++ sources and headers (see ARCHITECTURE.md) |
shaders/ |
GLSL 4.10: wire, background, traveler, scroller, post |
tests/ |
geometry_tests.cpp |
data/ |
object_catalog.csv object provenance |
assets/music/ |
Bundled tracker module, generated wav, fetch script, licence notes |
UBER_Fullscreen_Logo_Pack/ |
The 20 logo cards and their source artwork |
docs/ |
Effect catalogue, lab notes, audit results, screenshots |
archive/ |
Historical source bundles, for provenance only |
tools/ |
make_manifest.py (SHA-256 manifest) |
thirdparty/ |
stb_image, stb_image_write |
Measured on an Apple M1 Pro at Retina resolution (1728x1080 window, 3456x2160 pixels), v4.38 against the current code:
| Before | After | |
|---|---|---|
| Scenes whose CPU work (scene + effects + upload) averaged over 16.7 ms | 25 of 59 | 0 |
| Worst scene, CPU per frame (Tanglecube) | 256 ms avg, 2.9 s peak | 3.2 ms avg |
| Edges after the effect chain | up to 404,000 | at most 60,000 |
| Simplest scene, GPU-bound frame rate (uncapped) | 34 fps | 167 fps |
| Schwarz D / Tanglecube frame rate | 3.8 / 2.9 fps | 109 / 106 fps |
--quality 0.5 speed-up |
none | about +35% |
What changed:
- Effects: duplicate-edge removal sorts packed 64-bit keys instead of filling a
std::set, and only runs after the four effects that can create duplicates; multiplying effects respect a 60,000-edge budget. - Scenes: rebuilds (the implicit surfaces regenerate 4-6 times a second at 70-300 ms each) run on worker threads (
SceneSystem::meshAsync); the render loop never waits: it shows the previous tick, or the previous scene, until the new mesh is ready, and the next scene and both neighbours are built ahead of time. A stress run at 600 BPM (83 scene changes in a minute) peaks at 8.9 ms CPU per frame. - Upload: an O(edges) renderability check instead of a full duplicate scan, edges uploaded directly as index pairs, and buffer orphaning so an update never waits for the GPU.
- GPU: the procedural background and art layer moved to their own pass (
shaders/art.frag) at half resolution, skipped entirely while a logo card covers the screen. It used to cost about 70% of every frame, visible or not. - Vsync is on in full-screen too;
--no-vsyncis for benchmarks.
Guards: the tests run every scene through the auto recipe and fail if the result exceeds the edge budget or is not renderable, check every effect's output with the full validator, and check that the asynchronous fetch converges to the synchronous mesh.
Default soundtrack: "Stars (4ch. version)" by cs127, a 4-channel ProTracker module (4:21, 125 BPM, genre "Demo Style"), from The Mod Archive, module 201917. Licensed under Creative Commons Attribution 4.0 (CC BY 4.0) and bundled unmodified as assets/music/cs127_-_stars_4ch.mod. cs127 later finished the track as a longer IT version, Stars (module 201894). Thank you, cs127.
Why this track: it is a demo-style Amiga MOD, like the cracktros this project comes from; its steady 125 BPM groove suits the beat-synced scene changes; its title fits the golden galaxy scenes; and its licence allows bundling with credit.
Second in the playlist: "Digital Rendezvous" by Drozerix, a 20-channel FastTracker 2 module (2:44, 138 BPM, genre Chiptune, member rating 10/10) from The Mod Archive, module 180821, Public Domain, bundled unmodified as assets/music/drozerix_-_digital_rendezvous.xm.
The demo plays the two as a playlist (Stars, then Digital Rendezvous, then round again). At each change the ticker credit switches and the visuals lock to the new track's tempo without a jump in the beat count. Pass --music several times for your own playlist.
Also bundled:
- "Silicon Dancer" by Drozerix (MOD, Public Domain, Mod Archive 209692):
make run-silicon. - "Wireframe Pulse" (WAV, CC0, made for this project):
make run-pulse.
The visuals lock to each module's own tempo (tempo x 6 / speed, folded into 70-180 BPM because fast modules use 8 rows per beat) unless you pass --bpm. The ticker shows the credit of the track that is playing.
- Shader edits have no effect: shaders are copied to
build/shadersat build time; runmakeafter editing. - Static GLFW link errors on macOS: CMake searches
glfwandglfw3and adds the Apple frameworks when linking statically. - No audio: install SDL2 and libopenmpt (
brew install sdl2 libopenmpt); otherwise the BPM clock is used. - Low frame rate: run with
--statsto see whether the CPU or the GPU is the limit, then try--quality 0.5(GPU) or--no-post(skips all screen effects). See Performance. - Logo cards not found: run from the repo root, or pass
--logos DIR.
The full history is in CHANGELOG.md; each version is also a GitHub release. v4.29 and v4.30 are superseded by v4.31.

















