mirror of
https://github.com/gpujs/gpu.js.git
synced 2025-12-08 20:35:56 +00:00
709 lines
18 KiB
JavaScript
709 lines
18 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
*
|
|
* @classdesc Various utility functions / snippets of code that GPU.JS uses internally.\
|
|
* This covers various snippets of code that is not entirely gpu.js specific (ie. may find uses elsewhere)
|
|
*
|
|
* Note that all methods in this class are *static* by nature `Utils.functionName()`
|
|
*
|
|
* @class Utils
|
|
* @extends UtilsCore
|
|
*
|
|
*/
|
|
|
|
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
|
|
|
|
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
|
|
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
|
|
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
|
|
|
|
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
|
|
|
|
var UtilsCore = require("./utils-core");
|
|
var Input = require('./input');
|
|
var Texture = require('./texture');
|
|
// FUNCTION_NAME regex
|
|
var FUNCTION_NAME = /function ([^(]*)/;
|
|
|
|
// STRIP COMMENTS regex
|
|
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
|
|
|
|
// ARGUMENT NAMES regex
|
|
var ARGUMENT_NAMES = /([^\s,]+)/g;
|
|
|
|
var _systemEndianness = function () {
|
|
var b = new ArrayBuffer(4);
|
|
var a = new Uint32Array(b);
|
|
var c = new Uint8Array(b);
|
|
a[0] = 0xdeadbeef;
|
|
if (c[0] === 0xef) return 'LE';
|
|
if (c[0] === 0xde) return 'BE';
|
|
throw new Error('unknown endianness');
|
|
}();
|
|
|
|
var _isFloatReadPixelsSupported = null;
|
|
var _isFloatReadPixelsSupportedWebGL2 = null;
|
|
|
|
var _isMixedIdentifiersSupported = function () {
|
|
try {
|
|
new Function('let i = 1; const j = 1;')();
|
|
return true;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}();
|
|
|
|
var _hasIntegerDivisionAccuracyBug = null;
|
|
|
|
/**
|
|
* @class
|
|
* @extends UtilsCore
|
|
*/
|
|
|
|
var Utils = function (_UtilsCore) {
|
|
_inherits(Utils, _UtilsCore);
|
|
|
|
function Utils() {
|
|
_classCallCheck(this, Utils);
|
|
|
|
return _possibleConstructorReturn(this, (Utils.__proto__ || Object.getPrototypeOf(Utils)).apply(this, arguments));
|
|
}
|
|
|
|
_createClass(Utils, null, [{
|
|
key: 'systemEndianness',
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// System values support (currently only endianness)
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/**
|
|
* @memberOf Utils
|
|
* @name systemEndianness
|
|
* @function
|
|
* @static
|
|
*
|
|
* Gets the system endianness, and cache it
|
|
*
|
|
* @returns {String} 'LE' or 'BE' depending on system architecture
|
|
*
|
|
* Credit: https://gist.github.com/TooTallNate/4750953
|
|
*/
|
|
value: function systemEndianness() {
|
|
return _systemEndianness;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// Function and function string validations
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/**
|
|
* @memberOf Utils
|
|
* @name isFunction
|
|
* @function
|
|
* @static
|
|
*
|
|
* Return TRUE, on a JS function
|
|
*
|
|
* @param {Function} funcObj - Object to validate if its a function
|
|
*
|
|
* @returns {Boolean} TRUE if the object is a JS function
|
|
*
|
|
*/
|
|
|
|
}, {
|
|
key: 'isFunction',
|
|
value: function isFunction(funcObj) {
|
|
return typeof funcObj === 'function';
|
|
}
|
|
|
|
/**
|
|
* @memberOf Utils
|
|
* @name isFunctionString
|
|
* @function
|
|
* @static
|
|
*
|
|
* Return TRUE, on a valid JS function string
|
|
*
|
|
* Note: This does just a VERY simply sanity check. And may give false positives.
|
|
*
|
|
* @param {String} funcStr - String of JS function to validate
|
|
*
|
|
* @returns {Boolean} TRUE if the string passes basic validation
|
|
*
|
|
*/
|
|
|
|
}, {
|
|
key: 'isFunctionString',
|
|
value: function isFunctionString(funcStr) {
|
|
if (funcStr !== null) {
|
|
return funcStr.toString().slice(0, 'function'.length).toLowerCase() === 'function';
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @memberOf Utils
|
|
* @name getFunctionName_fromString
|
|
* @function
|
|
* @static
|
|
*
|
|
* Return the function name from a JS function string
|
|
*
|
|
* @param {String} funcStr - String of JS function to validate
|
|
*
|
|
* @returns {String} Function name string (if found)
|
|
*
|
|
*/
|
|
|
|
}, {
|
|
key: 'getFunctionNameFromString',
|
|
value: function getFunctionNameFromString(funcStr) {
|
|
return FUNCTION_NAME.exec(funcStr)[1];
|
|
}
|
|
}, {
|
|
key: 'getFunctionBodyFromString',
|
|
value: function getFunctionBodyFromString(funcStr) {
|
|
return funcStr.substring(funcStr.indexOf('{') + 1, funcStr.lastIndexOf('}'));
|
|
}
|
|
|
|
/**
|
|
* @memberOf Utils
|
|
* @name getParamNames_fromString
|
|
* @function
|
|
* @static
|
|
*
|
|
* Return list of parameter names extracted from the JS function string
|
|
*
|
|
* @param {String} funcStr - String of JS function to validate
|
|
*
|
|
* @returns {String[]} Array representing all the parameter names
|
|
*
|
|
*/
|
|
|
|
}, {
|
|
key: 'getParamNamesFromString',
|
|
value: function getParamNamesFromString(func) {
|
|
var fnStr = func.toString().replace(STRIP_COMMENTS, '');
|
|
var result = fnStr.slice(fnStr.indexOf('(') + 1, fnStr.indexOf(')')).match(ARGUMENT_NAMES);
|
|
if (result === null) result = [];
|
|
return result;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// Object / function cloning and manipulation
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/**
|
|
* @memberOf Utils
|
|
* @name clone
|
|
* @function
|
|
* @static
|
|
*
|
|
* Returns a clone
|
|
*
|
|
* @param {Object} obj - Object to clone
|
|
*
|
|
* @returns {Object} Cloned object
|
|
*
|
|
*/
|
|
|
|
}, {
|
|
key: 'clone',
|
|
value: function clone(obj) {
|
|
if (obj === null || (typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) !== 'object' || obj.hasOwnProperty('isActiveClone')) return obj;
|
|
|
|
var temp = obj.constructor(); // changed
|
|
|
|
for (var key in obj) {
|
|
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
obj.isActiveClone = null;
|
|
temp[key] = Utils.clone(obj[key]);
|
|
delete obj.isActiveClone;
|
|
}
|
|
}
|
|
|
|
return temp;
|
|
}
|
|
|
|
/**
|
|
* @memberOf Utils
|
|
* @name newPromise
|
|
* @function
|
|
* @static
|
|
*
|
|
* Returns a `new Promise` object based on the underlying implmentation
|
|
*
|
|
* @param {Function} executor - Promise builder function
|
|
*
|
|
* @returns {Promise} Promise object
|
|
*
|
|
*/
|
|
|
|
}, {
|
|
key: 'newPromise',
|
|
value: function newPromise(executor) {
|
|
var simple = Promise || small_promise;
|
|
if (simple === null) {
|
|
throw TypeError('Browser is missing Promise implementation. Consider adding small_promise.js polyfill');
|
|
}
|
|
return new simple(executor);
|
|
}
|
|
|
|
/**
|
|
* @memberOf Utils
|
|
* @name functionBinder
|
|
* @function
|
|
* @static
|
|
*
|
|
* Limited implementation of Function.bind, with fallback
|
|
*
|
|
* @param {Function} inFunc - to setup bind on
|
|
* @param {Object} thisObj - The this parameter to assume inside the binded function
|
|
*
|
|
* @returns {Function} The binded function
|
|
*
|
|
*/
|
|
|
|
}, {
|
|
key: 'functionBinder',
|
|
value: function functionBinder(inFunc, thisObj) {
|
|
if (inFunc.bind) {
|
|
return inFunc.bind(thisObj);
|
|
}
|
|
|
|
return function () {
|
|
var args = arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments);
|
|
return inFunc.apply(thisObj, args);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @memberOf Utils
|
|
* @name isArray
|
|
* @function
|
|
* @static
|
|
*
|
|
* * Checks if is an array or Array-like object
|
|
*
|
|
* @param {Object} arg - The argument object to check if is array
|
|
*
|
|
* @returns {Boolean} true if is array or Array-like object
|
|
*
|
|
*/
|
|
|
|
}, {
|
|
key: 'isArray',
|
|
value: function isArray(array) {
|
|
if (isNaN(array.length)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @memberOf Utils
|
|
* @name getArgumentType
|
|
* @function
|
|
* @static
|
|
*
|
|
* Evaluate the argument type, to apply respective logic for it
|
|
*
|
|
* @param {Object} arg - The argument object to evaluate type
|
|
*
|
|
* @returns {String} Argument type Array/Number/Float/Texture/Unknown
|
|
*
|
|
*/
|
|
|
|
}, {
|
|
key: 'getArgumentType',
|
|
value: function getArgumentType(arg) {
|
|
if (Utils.isArray(arg)) {
|
|
if (arg[0].nodeName === 'IMG') {
|
|
return 'HTMLImageArray';
|
|
}
|
|
return 'Array';
|
|
} else if (typeof arg === 'number') {
|
|
if (Number.isInteger(arg)) {
|
|
return 'Integer';
|
|
}
|
|
return 'Float';
|
|
} else if (arg instanceof Texture) {
|
|
return 'Texture';
|
|
} else if (arg instanceof Input) {
|
|
return 'Input';
|
|
} else if (arg.nodeName === 'IMG') {
|
|
return 'HTMLImage';
|
|
} else {
|
|
return 'Unknown';
|
|
}
|
|
}
|
|
/**
|
|
* @typedef {Object} gpuJSObject
|
|
*/
|
|
|
|
/**
|
|
* @memberOf Utils
|
|
* @name isFloatReadPixelsSupported
|
|
* @function
|
|
* @static
|
|
*
|
|
* Checks if the browser supports readPixels with float type
|
|
*
|
|
* @returns {Boolean} true if browser supports
|
|
*
|
|
*/
|
|
|
|
}, {
|
|
key: 'isFloatReadPixelsSupported',
|
|
value: function isFloatReadPixelsSupported() {
|
|
if (_isFloatReadPixelsSupported !== null) {
|
|
return _isFloatReadPixelsSupported;
|
|
}
|
|
|
|
var GPU = require('../index');
|
|
var gpu = new GPU({
|
|
mode: 'webgl-validator'
|
|
});
|
|
var x = gpu.createKernel(function () {
|
|
return 1;
|
|
}, {
|
|
output: [2],
|
|
floatTextures: true,
|
|
floatOutput: true,
|
|
floatOutputForce: true
|
|
})();
|
|
|
|
_isFloatReadPixelsSupported = x[0] === 1;
|
|
gpu.destroy();
|
|
return _isFloatReadPixelsSupported;
|
|
}
|
|
|
|
/**
|
|
* @memberOf Utils
|
|
* @name isFloatReadPixelsSupportedWebGL2
|
|
* @function
|
|
* @static
|
|
*
|
|
* Checks if the browser supports readPixels with float type
|
|
*
|
|
* @returns {Boolean} true if browser supports
|
|
*
|
|
*/
|
|
|
|
}, {
|
|
key: 'isFloatReadPixelsSupportedWebGL2',
|
|
value: function isFloatReadPixelsSupportedWebGL2() {
|
|
if (_isFloatReadPixelsSupportedWebGL2 !== null) {
|
|
return _isFloatReadPixelsSupportedWebGL2;
|
|
}
|
|
|
|
var GPU = require('../index');
|
|
var gpu = new GPU({
|
|
mode: 'webgl2-validator'
|
|
});
|
|
var x = gpu.createKernel(function () {
|
|
return 1;
|
|
}, {
|
|
output: [2],
|
|
floatTextures: true,
|
|
floatOutput: true,
|
|
floatOutputForce: true
|
|
})();
|
|
|
|
_isFloatReadPixelsSupportedWebGL2 = x[0] === 1;
|
|
gpu.destroy();
|
|
return _isFloatReadPixelsSupportedWebGL2;
|
|
}
|
|
|
|
/**
|
|
* @memberOf Utils
|
|
* @name hasIntegerDivisionAccuracyBug
|
|
* @function
|
|
* @static
|
|
*
|
|
* Checks if the system has inaccuracies when dividing integers
|
|
*
|
|
* @returns {Boolean} true if bug is exhibited on this system
|
|
*
|
|
*/
|
|
|
|
}, {
|
|
key: 'hasIntegerDivisionAccuracyBug',
|
|
value: function hasIntegerDivisionAccuracyBug() {
|
|
if (_hasIntegerDivisionAccuracyBug !== null) {
|
|
return _hasIntegerDivisionAccuracyBug;
|
|
}
|
|
|
|
var GPU = require('../index');
|
|
var gpu = new GPU({
|
|
mode: 'webgl-validator'
|
|
});
|
|
var x = gpu.createKernel(function (v1, v2) {
|
|
return v1[this.thread.x] / v2[this.thread.x];
|
|
}, {
|
|
output: [1]
|
|
})([6, 6030401], [3, 3991]);
|
|
|
|
// have we not got whole numbers for 6/3 or 6030401/3991
|
|
// add more here if others see this problem
|
|
_hasIntegerDivisionAccuracyBug = x[0] !== 2 || x[1] !== 1511;
|
|
gpu.destroy();
|
|
return _hasIntegerDivisionAccuracyBug;
|
|
}
|
|
}, {
|
|
key: 'isMixedIdentifiersSupported',
|
|
value: function isMixedIdentifiersSupported() {
|
|
return _isMixedIdentifiersSupported;
|
|
}
|
|
}, {
|
|
key: 'dimToTexSize',
|
|
value: function dimToTexSize(opt, dimensions, output) {
|
|
var numTexels = dimensions[0];
|
|
var w = dimensions[0];
|
|
var h = dimensions[1];
|
|
for (var i = 1; i < dimensions.length; i++) {
|
|
numTexels *= dimensions[i];
|
|
}
|
|
|
|
if (opt.floatTextures && (!output || opt.floatOutput)) {
|
|
w = numTexels = Math.ceil(numTexels / 4);
|
|
}
|
|
// if given dimensions == a 2d image
|
|
if (h > 1 && w * h === numTexels) {
|
|
return [w, h];
|
|
}
|
|
// find as close to square width, height sizes as possible
|
|
var sqrt = Math.sqrt(numTexels);
|
|
var high = Math.ceil(sqrt);
|
|
var low = Math.floor(sqrt);
|
|
while (high * low > numTexels) {
|
|
high--;
|
|
low = Math.ceil(numTexels / high);
|
|
}
|
|
w = low;
|
|
h = Math.ceil(numTexels / w);
|
|
return [w, h];
|
|
}
|
|
|
|
/**
|
|
* @memberOf Utils
|
|
* @name getDimensions
|
|
* @function
|
|
* @static
|
|
*
|
|
* Return the dimension of an array.
|
|
*
|
|
* @param {Array|String} x - The array
|
|
* @param {number} [pad] - To include padding in the dimension calculation [Optional]
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
|
|
}, {
|
|
key: 'getDimensions',
|
|
value: function getDimensions(x, pad) {
|
|
var ret = void 0;
|
|
if (Utils.isArray(x)) {
|
|
var dim = [];
|
|
var temp = x;
|
|
while (Utils.isArray(temp)) {
|
|
dim.push(temp.length);
|
|
temp = temp[0];
|
|
}
|
|
ret = dim.reverse();
|
|
} else if (x instanceof Texture) {
|
|
ret = x.output;
|
|
} else if (x instanceof Input) {
|
|
ret = x.size;
|
|
} else {
|
|
throw 'Unknown dimensions of ' + x;
|
|
}
|
|
|
|
if (pad) {
|
|
ret = Utils.clone(ret);
|
|
while (ret.length < 3) {
|
|
ret.push(1);
|
|
}
|
|
}
|
|
// return ret;
|
|
return new Int32Array(ret);
|
|
}
|
|
|
|
/**
|
|
* @memberOf Utils
|
|
* @name pad
|
|
* @function
|
|
* @static
|
|
*
|
|
* Pad an array AND its elements with leading and ending zeros
|
|
*
|
|
* @param {Array} arr - the array to pad zeros to
|
|
* @param {number} padding - amount of padding
|
|
*
|
|
* @returns {Array} Array with leading and ending zeros, and all the elements padded by zeros.
|
|
*
|
|
*/
|
|
|
|
}, {
|
|
key: 'pad',
|
|
value: function pad(arr, padding) {
|
|
function zeros(n) {
|
|
return Array.apply(null, new Array(n)).map(Number.prototype.valueOf, 0);
|
|
}
|
|
|
|
var len = arr.length + padding * 2;
|
|
|
|
var ret = arr.map(function (x) {
|
|
return [].concat(zeros(padding), x, zeros(padding));
|
|
});
|
|
|
|
for (var i = 0; i < padding; i++) {
|
|
ret = [].concat([zeros(len)], ret, [zeros(len)]);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @memberOf Utils
|
|
* @name flatten2dArrayTo
|
|
* @function
|
|
* @static
|
|
*
|
|
* Puts a nested 2d array into a one-dimensional target array
|
|
* @param {Array|*} array
|
|
* @param {Float32Array|Float64Array} target
|
|
*/
|
|
|
|
}, {
|
|
key: 'flatten2dArrayTo',
|
|
value: function flatten2dArrayTo(array, target) {
|
|
var offset = 0;
|
|
for (var y = 0; y < array.length; y++) {
|
|
target.set(array[y], offset);
|
|
offset += array[y].length;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @memberOf Utils
|
|
* @name flatten3dArrayTo
|
|
* @function
|
|
* @static
|
|
*
|
|
* Puts a nested 3d array into a one-dimensional target array
|
|
* @param {Array|*} array
|
|
* @param {Float32Array|Float64Array} target
|
|
*/
|
|
|
|
}, {
|
|
key: 'flatten3dArrayTo',
|
|
value: function flatten3dArrayTo(array, target) {
|
|
var offset = 0;
|
|
for (var z = 0; z < array.length; z++) {
|
|
for (var y = 0; y < array[z].length; y++) {
|
|
target.set(array[z][y], offset);
|
|
offset += array[z][y].length;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @memberOf Utils
|
|
* @name flatten3dArrayTo
|
|
* @function
|
|
* @static
|
|
*
|
|
* Puts a nested 1d, 2d, or 3d array into a one-dimensional target array
|
|
* @param {Array|*} array
|
|
* @param {Float32Array|Float64Array} target
|
|
*/
|
|
|
|
}, {
|
|
key: 'flattenTo',
|
|
value: function flattenTo(array, target) {
|
|
if (Utils.isArray(array[0])) {
|
|
if (Utils.isArray(array[0][0])) {
|
|
Utils.flatten3dArrayTo(array, target);
|
|
} else {
|
|
Utils.flatten2dArrayTo(array, target);
|
|
}
|
|
} else {
|
|
target.set(array);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @memberOf Utils
|
|
* @name splitArray
|
|
* @function
|
|
* @static
|
|
*
|
|
* Splits an array into smaller arrays.
|
|
* Number of elements in one small chunk is given by `part`
|
|
*
|
|
* @param {Array} array - The array to split into chunks
|
|
* @param {Array} part - elements in one chunk
|
|
*
|
|
* @returns {Array} An array of smaller chunks
|
|
*
|
|
*/
|
|
|
|
}, {
|
|
key: 'splitArray',
|
|
value: function splitArray(array, part) {
|
|
var result = [];
|
|
for (var i = 0; i < array.length; i += part) {
|
|
result.push(new array.constructor(array.buffer, i * 4 + array.byteOffset, part));
|
|
}
|
|
return result;
|
|
}
|
|
}, {
|
|
key: 'getAstString',
|
|
value: function getAstString(source, ast) {
|
|
var lines = Array.isArray(source) ? source : source.split(/\r?\n/g);
|
|
var start = ast.loc.start;
|
|
var end = ast.loc.end;
|
|
var result = [];
|
|
result.push(lines[start.line - 1].slice(start.column));
|
|
for (var i = start.line; i < end.line - 1; i++) {
|
|
result.push(lines[i]);
|
|
}
|
|
result.push(lines[end.line - 1].slice(0, end.column));
|
|
return result.join('\n');
|
|
}
|
|
}, {
|
|
key: 'allPropertiesOf',
|
|
value: function allPropertiesOf(obj) {
|
|
var props = [];
|
|
|
|
do {
|
|
props.push.apply(props, Object.getOwnPropertyNames(obj));
|
|
} while (obj = Object.getPrototypeOf(obj));
|
|
|
|
return props;
|
|
}
|
|
}]);
|
|
|
|
return Utils;
|
|
}(UtilsCore);
|
|
|
|
// This ensure static methods are "inherited"
|
|
// See: https://stackoverflow.com/questions/5441508/how-to-inherit-static-methods-from-base-class-in-javascript
|
|
|
|
|
|
Object.assign(Utils, UtilsCore);
|
|
|
|
module.exports = Utils; |