[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Public WebGL] The miserable state of affairs of floating point support



Besides Float16Array, a related issue is the ability to efficiently create interleaved arrays without the use of a DataView or multiple TypedArrays that share an ArrayBuffer. I believe many people avoid the use of interleaved arrays in WebGL due to this complexity. It would be great if the "array of struct type" discussions could be revisited and other element types could be considered.

On Fri, Mar 24, 2017 at 2:32 PM, Kenneth Russell <kbr@google.com> wrote:
ES6 Proxies can be used to implement the [] operator more efficiently. Here's a small example which does a little work (incrementing the value being set by 1) upon array indexing. The "buffer" property is supported for uploading the result to WebGL. I don't know how efficient it would be to support all of the Typed Array properties as well as the indexing operations -- seems they'd all have to be checked at the beginning of the handler's get function, which could be inefficient. But at least it's not necessary to define a property for each of the indices.

function createWrapperArray(size) {
  let handler = {
    get: function(target, propKey, receiver) {
      if (propKey == 'buffer')
        return target.buffer;
      let index = Number(propKey);
      return target[index] - 1.0;
    },

    set: function(target, propKey, value, receiver) {
      let index = Number(propKey);
      target[index] = value + 1.0;
    }
  }

  var array = new Float32Array(size);
  var proxy = new Proxy(array, handler);
  return proxy;
}

I think with a little work a Float16Array can be implemented using ES6 proxies that behaves almost exactly like the other typed array types, although some shimming of the WebGLRenderingContext would be needed in order to pass them seamlessly to the various data upload APIs. (If you're always willing to call "array.buffer" during upload, then they can behave exactly like the other typed arrays.)

-Ken


On Fri, Mar 24, 2017 at 5:10 AM, Florian Bösch <pyalot@gmail.com> wrote:
Here would be a somewhat comical and horrendously inefficient implementation of Float16Array. While that works for normal JS code, somewhat at least, it'd still run into trouble with texImage2D/bufferData/TypedArray functionalities etc. Is this really where we ant to be?

var twoPm14 = Math.pow(2, -14);
var smallest = twoPm14/1024;
var largest = Math.pow(2, 30-15) * (1 + 1023/1024);

var toHalf = (function() {
  var floatView = new Float32Array(1);
  var int32View = new Int32Array(floatView.buffer);

  return function toHalf( fval ) {
    floatView[0] = fval;
    var fbits = int32View[0];
    var sign  = (fbits >> 16) & 0x8000;          // sign only
    var val   = ( fbits & 0x7fffffff ) + 0x1000; // rounded value

    if( val >= 0x47800000 ) {             // might be or become NaN/Inf
      if( ( fbits & 0x7fffffff ) >= 0x47800000 ) {
                                          // is or must become NaN/Inf
        if( val < 0x7f800000 ) {          // was value but too large
          return sign | 0x7c00;           // make it +/-Inf
        }
        return sign | 0x7c00 |            // remains +/-Inf or NaN
            ( fbits & 0x007fffff ) >> 13; // keep NaN (and Inf) bits
      }
      return sign | 0x7bff;               // unrounded not quite Inf
    }
    if( val >= 0x38800000 ) {             // remains normalized value
      return sign | val - 0x38000000 >> 13; // exp - 127 + 15
    }
    if( val < 0x33000000 )  {             // too small for subnormal
      return sign;                        // becomes +/-0
    }
    val = ( fbits & 0x7fffffff ) >> 23;   // tmp exp for subnormal calc
    return sign | ( ( fbits & 0x7fffff | 0x800000 ) // add subnormal bit
         + ( 0x800000 >>> val - 102 )     // round depending on cut off
         >> 126 - val );                  // div by 2^(1-(exp-127+15)) and >> 13 | exp=0
  };
}());

var fromHalf = function(n){
    var sign = 1 - ((n & 0x8000) >> 14);
    var exponent = (n & 0x7c00) >> 10;
    var mantissa = (n & 0x03ff);

    if(exponent === 0){
        if(mantissa !== 0){
            return sign * twoPm14 * (mantissa/1024);
        }
        else{
            return sign * 0;
        }
    }
    else if(exponent < 31){
        return sign * Math.pow(2, exponent-15) * (1 + mantissa/1024);
    }
    else{
        if(mantissa === 0){
            return sign * Infinity;
        }
        else{
            return NaN;
        }
    }
};

var Float16Array = function(size){
    var array = this.array = new Uint16Array(size);
    var self = this;

    var defineProperty = function(n){
        Object.defineProperty(self, n, {
            get: function(){
                return fromHalf(array[n]);
            },
            set: function(value){
                return array[n] = toHalf(value);
            }
        });
    }

    for(var i=0; i<size; i++){
        defineProperty(i.toFixed(0));
    }
}

var foo = new Float16Array(1000);