Search code examples
javascriptwebglieee-754typed-arrays

Conversion from Float32Array to Uint16Array in WebGL


I have Float32Array textures which can be displayed through WebGL correctly. However, when I tried to convert them into Uint16Array, the problem occurs.

enter image description here

Here is my conversion part.

var _floatToHalfFloat = function(input, offset) {
    var largestHalf = Math.pow(2, 30-15) * (1 + 1023/1024);
    var m = new ArrayBuffer(4);
    var n = new Float32Array(m);
    var o = new Uint32Array(m);
    var f = 0.0;
    for (var i = input.length - 1 - offset; i >= 0;i--) {
        n[0] = input[i];
        f = o[0];
        // fast conversion of half
        // ref : ftp://www.fox-toolkit.org/pub/fasthalffloatconversion.pdf

        if (isNaN(input[i])) {
            input[i] = 0x7fff;
        } else if(n === Infinity || n > largestHalf) {
            input[i] = 0x7c00;
        } else if(n === -Infinity || n < -largestHalf) {
            input[i] = 0xfc00;
        } else if(n === 0) {
            input[i] = 0;
        } else {
            input[i] = ((f>>16)&0x8000)|((((f&0x7f800000)-0x38000000)>>13)&0x7c00)|((f>>13)&0x03ff);
        }
    }
    return new Uint16Array(input);
};

Solution

  • We can see saturated colors (full red, green and/or blue) in the converted image when reaching black color in the original image. I think the function doesn't work very well near 0.

    I have done a quick implementation of wikipedia explanation of norm of the float 16 bits.

    <html>
    <head>
    <script>
    var _floatToHalfFloat = #### YOUR FUNCTION HERE CUT ####
    
    var _halfFloatToFloat = function(hf) {
      var m = new ArrayBuffer(2);
      var n = new Uint16Array(m);
      n[0] = hf;
      var sign = n[0] & 0x8000;
      var exp = (n[0] >> 10) & 0x1F;
      var mant = n[0]& 0x03FF;
      document.getElementById('sign').innerHTML += sign+" - ";
      document.getElementById('exp').innerHTML += exp+" - ";
      document.getElementById('mant').innerHTML += mant+" - ";
      if (exp == 0x1F) {
        return 1.0 * Math.pow(-1, sign) * Infinity;
      } else if (exp == 0) {
        return Math.pow(-1, sign) *
               Math.pow(2, -14) *
               (mant / Math.pow(2, 10));
      } else {
        return Math.pow(-1, sign) *
               Math.pow(2, exp-15) *
               (1+(mant / Math.pow(2, 10)));
      }
    };
    
    document.addEventListener("DOMContentLoaded", function(event) {
      var input = new Float32Array(8);
      input[0] = 2.5;
      input[1] = 0.25;
      input[2] = 0.025;
      input[3] = 0.025;
      input[4] = 0.0025;
      input[5] = 0.00025;
      input[6] = 0.000025;
      input[7] = 0.0;
    
      var i, s = "Value before = ";
      for (i = 0; i < input.length; i++) 
        s += input[i] + " - ";
      document.getElementById('res1').innerHTML = s;
      var output = _floatToHalfFloat(input, 0);
      s = "Value after = ";
      for (i = 0; i < output.length; i++) 
        s += _halfFloatToFloat(output[i]) + " - ";
      document.getElementById('res2').innerHTML = s;
    });
    </script>
    </head>
    <body>
      <span id="res1">result</span></br>
      <span id="res2">result</span></br>
      </br></br></br>
      <span id="sign">signs =</span></br>
      <span id="exp">exponents =</span></br>
      <span id="mant">mantissas =</span></br>
    </body>
    </html>
    

    The test results are shown below :

    Value before = 2.5 - 0.25 - 0.02500000037252903 - 0.02500000037252903 - 0.0024999999441206455 - 0.0002500000118743628 - 0.00002499999936844688 - 0 -
    Value after = 2.5 - 0.25 - 0.024993896484375 - 0.024993896484375 - 0.002498626708984375 - 0.0002498626708984375 - Infinity - 2 -
    
    
    
    signs =0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 -
    exponents =16 - 13 - 9 - 9 - 6 - 3 - 31 - 16 -
    mantissas =256 - 0 - 614 - 614 - 286 - 24 - 653 - 0 - 
    

    This shows that the 2 last information are not coherent. 0.000025 is transformed into Infinity (rather than 0?) and 0 itself is transformed to 2. This doesn't appear to be correct. When you want to code a zero "wikipedia says" your mantissa AND your exponent should be zero. In the code you provided the mantissa is zero but the exponent is 16 which leads to 2 (2^(16-15)).

    After tweaking a bit your function it appears that all cases are treated as normal one. This is due to a bug in your if statements. So instead of having :

    } else if(n === 0) {
        input[i] = 0;
    }
    

    You want probably do something like that :

    } else if(n[0] === 0) {
        input[i] = 0;
    }
    

    And the same for all uses of n variable. But you still have the underflow problem.So may be you can find acceptable to do :

    } else if(Math.abs(n[0]) < 0.0001) {
        input[i] = 0;
    }