Emulated double precision in OpenGL ES shader

In the previous post, Generating the Mandelbrot set in an OpenGL ES shader, there was blockiness caused by the low precision of the fragment shader at a zoom level of just 100x. We would like to be able to scale the fractal by factors of millions to explore it properly so this is totally inadequate.

I found a blog post about emulating double precision in GLSL by using two floats to represent a value. I recommend reading it as the author gets some pretty nice results. I tried porting the code and making it work in OpenGL ES, but the my results where far from excellent.

2014-9-4_mandelbrot32014-9-4_mandelbrot4

You can see that there is an actual improvement. The emulation works!

 

2014-9-4_mandelbrot62014-9-4_mandelbrot5 (1)

But at a closer look the improvement is minimal.

I modified the code to account for the lower float precision but it didn’t help. In the Android Emulator it worked much better, but the emulator has a much higher float precision even with single midiump floats.

This is where I realised that even though OpenGL ES doesn’t guarantee high precision floats in the fragment shader, it does in the vertex shader!

The next post in the series shows how to use the precision of the vertex shader to greatly improve the details.

This entry was posted in Uncategorized. Bookmark the permalink.

4 Responses to Emulated double precision in OpenGL ES shader

  1. jonsl says:

    Hello,

    A very interesting article, and I was very eager to read “how to use the precision of the vertex shader to greatly improve the details” article, but I could not find it..

    I have a question: if the fragment shader supports only 16-bit floats, how do I send a ‘double-float’ value to the shader?

    I have a function (using cpu 32-bit floats), and then I set the two floats in the shader:
    static inline vec2 set2d(double a) {
    vec2 b;
    b.x = (float)a;
    b.y = (float)(a – b.x);
    return b;
    }

    But, obviously b.y = (float)(a – b.x); does not calulate the correct value because bx is interpreted as a 16-bit float in the shader, therefore b.y is wrong. How could I fix this?

    Thanks for your great article.

    Like

    • betelge says:

      Hi jonsl,

      It turns out I’ve had two posts just sitting there as unpublished drafts for two years without realizing it. I’ve published them now. One of them goes through how to do this kind of computations in the vertex shader. Thanks for reminding me.

      One way of sending a high precision value to the fragment shader is by passing it to the vertex shader as a highp float uniform, assuming your vertex shader highp precision is high enough. And then in the vertex shader use splitf() and pass those results to the fragment shader as two mediump floats or a mediump vec2.

      Another way is by splitting the high precision value on the CPU and passing it as two float uniforms or one vec2 uniform like your code does. You just need a more complicated set2d() function.

      This should work:

      uniform float split = 1025; // 2^(bits of precision) + 1
      
      vec2 splitf(float a)
      {
         float t, hi;
         t = split * a;
         hi = t - (t-a);
         return vec2(hi, a-hi);
      }

      the split constant performs a bit shift by p bits and should be set to 2^p+1. The value in the example, 1025, works for 10 bit mediump floats. And if you are using Nvidia hardware you might need the following lines in your shader code:

      #pragma optionNV(fastmath off)
      #pragma optionNV(fastprecision off)

      otherwise the GLSL compiler will simplify the hi = t - (t-a) to hi = a.
      You can see the entire source code of the finished app on my GitHub page here.

      Like

      • jonsl says:

        Hi betelge,

        Thank you so much for your response- this is very useful information for me.

        I have suspected for a short time that the reason my precision code is not working correctly is because the glsl compiler is optimising away the some of the code, just as you mention; several psychedelic debug frames later reveal that some of my routines leave the low part of the emulated double-float as zero.

        I am working on iOS, and I was wondering if you happen to know what #pragma equivalents I should use? If you do, I suspect that this would fix my major problem; amazingly, I can find no information about this for iOS shaders.

        I really like your vertex shader splitting trick, very neat! Also, I have highp float at 23 bits of precision in the fragment shader, and I am actually using (1 + 2^(precision+1)) as the split because of the implicit bit in the float (so the split is 1 << 12 + 1)- does this seem reasonable?

        Anyway, thanks for such an informative answer, I was starting to get quite frustrated about this and a fresh perspective has really helped me.

        Thanks,
        Jon

        Like

  2. Pingback: High precision floats in OpenGL ES shaders | Infinite Worlds

Leave a comment