Skip to main content

Mandelbrot with WebGPU

· 3 min read
Louis Roehrs
Architect

Web GPU Mandlebrot

This Mandlebrot set is being displayed in your browser using your GPU. This is not a movie. It is being calculated and rendered in realtime. If you only see a black square, try a different browser that supports WebGL and be sure to turn on the WebGL support for your browser. If only Benoit Mandlebrot, an IBMer, had a GPU on which to run his JavaScript code.


We can build a gpu kernal which simply provides the GPU with the code in JavaScript to tell the GPU what color to set the pixel at the given x, y coordinate and bind it to an HTML canvas.

Get this. It will even run on an AppleWatch!


Full Screen

Here is the code. This was written pre-AI coding tools. Set up an HTML page like so.

index.html

<!DOCTYPE html>
<style>
#mycanvas {
-webkit-transform: translate3d(0, 0, 0);
-moz-transform: translate3d(0, 0, 0);
-ms-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 1);
background-color:transparent;
}
.site-inner {
margin: 0 auto;
max-width:3000px;
position: relative;
}

</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.js"></script>
<!-- <script src="http://34.212.196.99/wp-content/uploads/2017/07/gpu.js"></script> -->
<script src="gpu-new.js"></script>
<!-- <script src="http://34.212.196.99/wp-content/uploads/2017/07/mandlebrot.js"></script> -->
<script src="mandlebrot.js"></script>
This Mandlebrot set is being displayed in your browser using your GPU. This is not a movie. If you only see a black square, try a different browser that supports WebGL and be sure to turn on the WebGL support for your browser. If only Benoit Mandlebrot, an IBMer, had a GPU on which to run his JavaScript code...
<BR>
<small style="display:inline-block">
Mandelbrot Iterations<span id='nm'></span>
<BR>
Frames Per Second <span id='fpsm'></span>
</small>
<div id="mycanvas"></div>

Grab the gpu-new.js library from here.

Add the mandelbrot.js below.

mandlebrot.js

const gpu = new GPU();


var rendermandlebrot = gpu.createKernel(function(it,x,y,s) {
var j =0;
var zr = 0.0;
var zi = 0.0;
var maxiter = it;
var zr2 = 0.0;
var zi2 = 0.0;
var newzr = 0.0;
var myx = ((this.thread.x +x) / s);
var myy = ((this.thread.y +y) / s);
for (var i = 0; i < maxiter; i++) {
if (( zr2 + zi2) < 4) {
//3+5i sqared = 9 -25 + 15i
newzr = zr2 - zi2 + myx;
zi = 2 * zr * zi + myy;
zr = newzr;
zr2 = zr * zr;
zi2 = zi * zi;
j++;
}
}
if (j > maxiter-3)
{
this.color(0,0,0,0);
} else {
this.color((j%256)/256,(j%64)/64,(j%16)/16,0);
}
},
/*
.dimensions([300,300])
.graphical(true)
.loopMaxIterations(20000)
.mode('gpu');
*/
{
output:[300, 300],
graphical:true,
loopMaxIterations:20000,
mode:"gpu"
});


$('document').ready ( function () {
var start = Date.now();
var stop = 0;
var times = [];
function frame() {
now = Date.now() - start;
times.push(now);
fps = window.document.getElementById('fpsm');
fps.textContent = Math.floor(1000/(now - times[times.length-2]));

iters = window.document.getElementById('nm');
iters.textContent = stop + ":" + (4100+810*15+stop*15) +":"+ (-1000-800*5-stop*7)+ ":" + (14000+800*40+stop*40);
rendermandlebrot(stop, 4100+810*15+stop*15, -1000-800*5-stop*7, 14000+800*40+stop*40);

var canvas = rendermandlebrot.getCanvas();

document.getElementById('mycanvas').appendChild(canvas);
if (stop++ < 6000) { //0000) {
window.requestAnimationFrame(frame);
}
}
window.requestAnimationFrame(frame);

});