4

I am trying to implement 64-bit arithmetic on the WebGL or WebGL2 shaders based on 32-bit floats. One of the basic functions which is needed there is the function which is splitting any float number into two "non overlapping" floats. The first float contains first half of the original float's fraction bits and the second float contains the second half. Here is the implementation of this function:

precision highp float;
...
...
vec2 split(const float a)
{
  const float split = 4097.0; // 2^12 + 1
  vec2 result;
  float t = a * split; // almost 4097 * a
  float diff = t - a; // almost 4096 * a
  result.x = t - diff; // almost a
  result.y = a - result.x; //very small number 
  return result;
}

This function is working as expected if I pass to it arguments defined in the shader:

precision highp float;
...
...  
float number = 0.1;
vec2 splittedNumber = split(number);
if (splittedNumber.y != 0.0)
{
    // color with white    
    // we step here and see the white screen
}
else
{
   //color with black
}

But whenever the number depends somehow from any uniform everything starts to behave differently:

precision highp float;
uniform float uniformNumber;
...
...
float number = 0.2;
if (uniformNumber > 0.0)    
{
  // uniform number is positive, 
  // so we step here
  number = 0.1;  
}  

vec2 splittedNumber = split(number);
if (splittedNumber.y != 0.0)
{
    // color with white    
}
else
{
   //color with black
   // we step here and see the black screen
}

So in the second situation when the "number" depends on the uniform the split function is somehow became optimized and return back vec2 with the zero y value.

There is a similar question on the stackoverflow on the similar problem in OpenGL Differing floating point behaviour between uniform and constants in GLSL The suggestion there was to use the "precise" modifier inside the function "split". Unfortunately in WebGL/WebGL2 shaders there is no such modifier.

Do you have any suggestion how get rid of the optimizations in my case and implement the "split" function?

David
  • 189
  • 5

1 Answers1

1

Mathematically speaking, both your examples should output black pixels. Because:

float diff = t - a;
result.x = t - diff; // t - diff = t - t + a = a
result.y = a - result.x; // a - a = 0

It could be that in case of the constant argument to split() (the value known beforehand) the compiler took the path of calculating the function before optimizing the expressions and you got splittedNumber.y != 0.0 because of precission errors. When you used uniform, the compiler took the path of optimizing the expression which produced mathmatically strict zero.

To verify that this is the case you could try the following comparison instead:

if(abs(splittedNumber.y) > 1e-6)
{

}

On a side note, highp does not guarantee a 32-bit float in WebGL Shaders. Depending on the hardware it could be a 24-bit float or even 16-bit due to fallback to mediump (if highp is not supported in fragment shaders). To see the actual precision you can use gl.getShaderPrecisionFormat

https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/getShaderPrecisionFormat

Vlad Alex
  • 170
  • 1
  • 12
  • 1
    If you specifiy highp in the fragment shader you'll get highp in the fragment shader or it will fail to compile. It is against the spec for it to auto fallback to mediump. The opposite though, if you specifiy mediump you may get highp is true. – gman Feb 03 '20 at 08:49
  • By the way, in webgl2 highp float is guaranteed to be 32bit. – David Feb 03 '20 at 09:30
  • 1
    Well at least in GLES 2.0 the drivers of Mali 4xx GPUs (the only mediump cards that matter today) auto-fallback to mediump without errors despite the specs. I haven't tried this with WebGL but I suspect it could be the case too. – Vlad Alex Feb 03 '20 at 09:59