GitHub Source

I ported a couple popular Shadertoy shaders to run on the CPU:

Sea Scape, by Alex Alekseev (


Creation, by Danilo Guanabara (


Tunnel, by Inigo Quilez (


The math escapes me, but I used this exercise as an introduction to C++17 (or portions thereof). The shaders request a single uint32_t pointer from SDL2 and then proceeds to divide the screen into as many horizontal rows as there are logical cores on your CPU. Each render row is rendered in tandem with the others.

The performance is strong for the creation and tunnel shaders, averaging 60 FPS with vertical sync, but the seascape shader runs at a steady 0.5 FPS, even without antialiasing.

A cool exercise nonetheless. I have never seen water rendered on the CPU looking this good, but at this point, even on an i5-3320M with 4 logical cores, this renderer may as well be a ray tracer with its 0.5 FPS render speed.

Anyway, the full source is here. The creation and tunnel shaders are totally worth studying. The setup for the creation shader goes something like:

#include "softshader.hh"

static uint32_t shade(const ss::V2 coord)
    const auto per = coord / ss::res;
    auto c = ss::V3 {};
    auto l = 0.f;
    auto z = ss::uptime();
    for(int i = 0; i < 3; i++)
        const auto p = (per - 0.5f) * ss::V2 { ss::res.x / ss::res.y, 1.f };
        l = ss::length(p);
        z += 0.07f;
        const auto uv = per + p / l * (ss::sin(z) + 1.f) * ss::abs(ss::sin(l * 9.f - z * 2.f));
        const auto cc = ss::length(ss::abs(ss::mod(uv, 1.f) - 0.5f));
        c[i] = (cc == 0.f) ? 1.f : (0.01f / cc);
    const auto v = c / l;
    return v.color(ss::uptime());

int main()

As you can see, in true Shadertoy fashion, softshader’s transfer function takes a 2D coordinate point as its input and returns a 32bit color value. All the multhreading for this transfer function is handled internally by the softshader header include.

As for the seascape shader, maybe I will one day learn and share the math that powers it.