6

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): enter image description here Zero difference with the same image rendered without double emulation.

Thanks for reading this wall of text and code, any hints and/or ideas?

Max Yari
  • 3,617
  • 5
  • 32
  • 56
  • How did that fragment shader even compile? It's missing a [`precision` directive](http://stackoverflow.com/questions/28540290/why-it-is-necessary-to-set-precision-for-the-fragment-shader) for those `float`s. You'll probably want `highp`. – genpfault Mar 20 '17 at 17:27
  • Of course I left a bunch of code off board. It's precision highp float, will put it in code snippet. – Max Yari Mar 20 '17 at 17:29
  • 1
    So adorable when someone votes for closure without any explanations or tips what's wrong with question or how to make it better. – Max Yari Mar 21 '17 at 09:29
  • My guess is that the visible artifacts are caused by the limited accuracy of the pixels. gl_FragCoord is a vec2 and uses thus only 32bit per coordinate. This also explains why all artifacts are little rectangles, for webgl all pixels in a rectangle are the same pixels. – Toonijn Aug 02 '17 at 16:47
  • I have recently run into exactly the same problem, I wonder did you ever solve this? It looks unlikely to be the gl_FragCoord as this has a value between 0.0 and the canvas size. it looks like you then use ds_mult() to get the complex number at this point on the screen. So you should have double precision at this point. My experiments show that the result of all the ds_ funcions result in a vec2 with its .y element always being 0. – user1055212 Mar 16 '18 at 13:13
  • @user1055212 Naw, actually i think i never solved it, partially cos GPU calc at this point was really uncomfortable in browser anyway since it was shutting webgl after GPU being busy for more than ~2sec and splitting it all in to chuncks sounded a bit too much of a PITA for this hobby project... though maybe i'll get back to it one day, so it'll be really great if you'll post the solution to precision issue whenever/if you figure it out. – Max Yari Mar 19 '18 at 16:14

0 Answers0