While playing arround with Mandelbrot set
generation in WebGl
I've inevitably stumbled on a small accuracy of OpenGl
's 32bit float
. Yet, new hope was born when I found this great article.
Carefully, I took functions from aforementioned source and added double
deconstruction on JS side.
To my understanding JS has 64bit doubles
for floating point numbers, so my idea was to calculate some preparational data on JS side, send it to GPU as pairs of floats and proceed to mandelbrot loop.
Sadly, at the end I saw literally zero difference in resulted image, and I don't really know where is my point of failure.
JS:
function doubleToFloat(d) {
return new Float32Array([d])[0];
};
function splitDouble(dbl) { //splits JS number to array of 2 32bit floats
var arr = [];
arr[0] = doubleToFloat(dbl);
arr[1] = doubleToFloat(dbl - arr[0]);
//console.log(dbl, arr);
arr = new Float32Array(arr);
dlog(dbl,arr);
return arr;
};
///Somewhere inside rendering function - binding data to webGl
var planeH = 4/settings.magnification;
var planeMult = planeH/canvasH; //screen to complex plane dimensions multiplier
var planeW = canvasW*planeMult;
var planeWOffset = planeW/2; //offset to align plane's 0:0 at screen's center
var planeHOffset = planeH/2;
bindUniforms(program, {
WIDTH: canvasW,
HEIGHT: canvasH,
CENTER_X: {value:splitDouble(settings.centerX),type:"2fv"},
CENTER_Y: {value:splitDouble(settings.centerY),type:"2fv"},
MAGNIFICATION: settings.magnification,
PLANE_W_OFFSET: {value:splitDouble(planeWOffset),type:"2fv"},
PLANE_H_OFFSET: {value:splitDouble(planeHOffset),type:"2fv"},
PLANE_MULT: {value:splitDouble(planeMult),type:"2fv"},
uSampler: {value:texIndex,type:"1i"}
});
--> sent to webGL
WebGL (Fragment Shader):
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
precision mediump int;
const int ITER_LIMIT = <%=iterLimit%>;
uniform float MAGNIFICATION;
uniform vec2 CENTER_X;
uniform vec2 CENTER_Y;
uniform float WIDTH;
uniform float HEIGHT;
uniform vec2 PLANE_W_OFFSET;
uniform vec2 PLANE_H_OFFSET;
uniform vec2 PLANE_MULT;
uniform sampler2D uSampler;
const float threshold = 10.0;
///Double emulation functions///////////////
vec2 ds(float a) {
vec2 z;
z.x = a;
z.y = 0.0;
return z;
}
vec2 ds_add(vec2 dsa, vec2 dsb) {
vec2 dsc;
float t1, t2, e;
t1 = dsa.x + dsb.x;
e = t1 - dsa.x;
t2 = ((dsb.x - e) + (dsa.x - (t1 - e))) + dsa.y + dsb.y;
dsc.x = t1 + t2;
dsc.y = t2 - (dsc.x - t1);
return dsc;
}
vec2 ds_mult(vec2 dsa, vec2 dsb) {
vec2 dsc;
float c11, c21, c2, e, t1, t2;
float a1, a2, b1, b2, cona, conb, split = 8193.;
cona = dsa.x * split;
conb = dsb.x * split;
a1 = cona - (cona - dsa.x);
b1 = conb - (conb - dsb.x);
a2 = dsa.x - a1;
b2 = dsb.x - b1;
c11 = dsa.x * dsb.x;
c21 = a2 * b2 + (a2 * b1 + (a1 * b2 + (a1 * b1 - c11)));
c2 = dsa.x * dsb.y + dsa.y * dsb.x;
t1 = c11 + c2;
e = t1 - c11;
t2 = dsa.y * dsb.y + ((c2 - e) + (c11 - (t1 - e))) + c21;
dsc.x = t1 + t2;
dsc.y = t2 - (dsc.x - t1);
return dsc;
}
vec2 ds_sub(vec2 dsa, vec2 dsb) {
return ds_add(dsa, ds_mult(ds(-1.0),dsb));
}
///End of Double emulation functions/////////
///Inside main()////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
float sqThreshold = threshold*threshold;
vec2 X = ds_mult(ds(gl_FragCoord.x),PLANE_MULT);
vec2 Y = ds_mult(ds(gl_FragCoord.y),PLANE_MULT);
vec2 planeX = ds_add(ds_sub(X,PLANE_W_OFFSET),CENTER_X);
vec2 planeY = ds_sub(ds_sub(Y,PLANE_H_OFFSET),CENTER_Y);
vec4 outcome = vec4(ds(0.0),ds(0.0));
vec4 C = vec4(planeX,planeY);
int iters = 0;
for (int i = 0; i < ITER_LIMIT; i++) {
iters = i;
vec2 sqRe = ds_mult(outcome.xy,outcome.xy);
vec2 sqIm = ds_mult(outcome.zw,outcome.zw);
vec4 Zsq = vec4(
ds_sub(sqRe,sqIm),
ds_mult(ds(2.0),ds_mult(outcome.xy,outcome.zw))
);
outcome.xy = ds_add(Zsq.xy,C.xy);
outcome.zw = ds_add(Zsq.zw,C.zw);
vec2 sqSumm = ds_add(ds_mult(outcome.xy,outcome.xy),ds_mult(outcome.zw,outcome.zw));
if (sqSumm.x > sqThreshold) break;
};
--> Proceed to coloring
Result (location/maginfication data on the right):
Zero difference with the same image rendered without double emulation.
Thanks for reading this wall of text and code, any hints and/or ideas?