UNPKG

gpu.js

Version:

GPU Accelerated JavaScript

1,016 lines (969 loc) 33.3 kB
const acorn = require('acorn'); const { Input } = require('./input'); const { Texture } = require('./texture'); const FUNCTION_NAME = /function ([^(]*)/; const STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; const ARGUMENT_NAMES = /([^\s,]+)/g; /** * * @desc Various utility functions / snippets of code that GPU.JS uses internally. * @type {utils} * This covers various snippets of code that is not entirely gpu.js specific (ie. may find uses elsewhere) */ const utils = { /** * * @desc Gets the system endianness, and cache it * @returns {String} 'LE' or 'BE' depending on system architecture * Credit: https://gist.github.com/TooTallNate/4750953 */ systemEndianness() { return _systemEndianness; }, getSystemEndianness() { const b = new ArrayBuffer(4); const a = new Uint32Array(b); const c = new Uint8Array(b); a[0] = 0xdeadbeef; if (c[0] === 0xef) return 'LE'; if (c[0] === 0xde) return 'BE'; throw new Error('unknown endianness'); }, /** * @descReturn 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 */ isFunction(funcObj) { return typeof(funcObj) === 'function'; }, /** * @desc Return TRUE, on a valid JS function string * Note: This does just a VERY simply sanity check. And may give false positives. * * @param {String} fn - String of JS function to validate * @returns {Boolean} TRUE if the string passes basic validation */ isFunctionString(fn) { if (typeof fn === 'string') { return (fn .slice(0, 'function'.length) .toLowerCase() === 'function'); } return false; }, /** * @desc 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) */ getFunctionNameFromString(funcStr) { const result = FUNCTION_NAME.exec(funcStr); if (!result || result.length === 0) return null; return result[1].trim(); }, getFunctionBodyFromString(funcStr) { return funcStr.substring(funcStr.indexOf('{') + 1, funcStr.lastIndexOf('}')); }, /** * @desc Return list of argument names extracted from a javascript function * @param {String} fn - String of JS function to validate * @returns {String[]} Array representing all the parameter names */ getArgumentNamesFromString(fn) { const fnStr = fn.replace(STRIP_COMMENTS, ''); let result = fnStr.slice(fnStr.indexOf('(') + 1, fnStr.indexOf(')')).match(ARGUMENT_NAMES); if (result === null) { result = []; } return result; }, /** * @desc Returns a clone * @param {Object} obj - Object to clone * @returns {Object|Array} Cloned object */ clone(obj) { if (obj === null || typeof obj !== 'object' || obj.hasOwnProperty('isActiveClone')) return obj; const temp = obj.constructor(); // changed for (let key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { obj.isActiveClone = null; temp[key] = utils.clone(obj[key]); delete obj.isActiveClone; } } return temp; }, /** * @desc Checks if is an array or Array-like object * @param {Object} array - The argument object to check if is array * @returns {Boolean} true if is array or Array-like object */ isArray(array) { return !isNaN(array.length); }, /** * @desc Evaluate the argument type, to apply respective logic for it * @param {*} value - The argument object to evaluate type * @param {boolean} [strictIntegers] * @returns {String} Argument type Array/Number/Float/Texture/Unknown */ getVariableType(value, strictIntegers) { if (utils.isArray(value)) { if (value.length > 0 && value[0].nodeName === 'IMG') { return 'HTMLImageArray'; } return 'Array'; } switch (value.constructor) { case Boolean: return 'Boolean'; case Number: if (strictIntegers && Number.isInteger(value)) { return 'Integer'; } return 'Float'; case Texture: return value.type; case Input: return 'Input'; } if ('nodeName' in value) { switch (value.nodeName) { case 'IMG': return 'HTMLImage'; case 'CANVAS': return 'HTMLImage'; case 'VIDEO': return 'HTMLVideo'; } } else if (value.hasOwnProperty('type')) { return value.type; } else if (typeof OffscreenCanvas !== 'undefined' && value instanceof OffscreenCanvas) { return 'OffscreenCanvas'; } else if (typeof ImageBitmap !== 'undefined' && value instanceof ImageBitmap) { return 'ImageBitmap'; } else if (typeof ImageData !== 'undefined' && value instanceof ImageData) { return 'ImageData'; } return 'Unknown'; }, getKernelTextureSize(settings, dimensions) { let [w, h, d] = dimensions; let texelCount = (w || 1) * (h || 1) * (d || 1); if (settings.optimizeFloatMemory && settings.precision === 'single') { w = texelCount = Math.ceil(texelCount / 4); } // if given dimensions == a 2d image if (h > 1 && w * h === texelCount) { return new Int32Array([w, h]); } return utils.closestSquareDimensions(texelCount); }, /** * * @param {Number} length * @returns {TextureDimensions} */ closestSquareDimensions(length) { const sqrt = Math.sqrt(length); let high = Math.ceil(sqrt); let low = Math.floor(sqrt); while (high * low < length) { high--; low = Math.ceil(length / high); } return new Int32Array([low, Math.ceil(length / low)]); }, /** * A texture takes up four * @param {OutputDimensions} dimensions * @param {Number} bitRatio * @returns {TextureDimensions} */ getMemoryOptimizedFloatTextureSize(dimensions, bitRatio) { const totalArea = utils.roundTo((dimensions[0] || 1) * (dimensions[1] || 1) * (dimensions[2] || 1) * (dimensions[3] || 1), 4); const texelCount = totalArea / bitRatio; return utils.closestSquareDimensions(texelCount); }, /** * * @param dimensions * @param bitRatio * @returns {*|TextureDimensions} */ getMemoryOptimizedPackedTextureSize(dimensions, bitRatio) { const [w, h, d] = dimensions; const totalArea = utils.roundTo((w || 1) * (h || 1) * (d || 1), 4); const texelCount = totalArea / (4 / bitRatio); return utils.closestSquareDimensions(texelCount); }, roundTo(n, d) { return Math.floor((n + d - 1) / d) * d; }, /** * @desc Return the dimension of an array. * @param {Array|String|Texture|Input} x - The array * @param {Boolean} [pad] - To include padding in the dimension calculation * @returns {OutputDimensions} */ getDimensions(x, pad) { let ret; if (utils.isArray(x)) { const dim = []; let 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 new Error(`Unknown dimensions of ${x}`); } if (pad) { ret = Array.from(ret); while (ret.length < 3) { ret.push(1); } } return new Int32Array(ret); }, /** * Puts a nested 2d array into a one-dimensional target array * @param {Array|*} array * @param {Float32Array|Float64Array} target */ flatten2dArrayTo(array, target) { let offset = 0; for (let y = 0; y < array.length; y++) { target.set(array[y], offset); offset += array[y].length; } }, /** * Puts a nested 3d array into a one-dimensional target array * @param {Array|*} array * @param {Float32Array|Float64Array} target */ flatten3dArrayTo(array, target) { let offset = 0; for (let z = 0; z < array.length; z++) { for (let y = 0; y < array[z].length; y++) { target.set(array[z][y], offset); offset += array[z][y].length; } } }, /** * Puts a nested 4d array into a one-dimensional target array * @param {Array|*} array * @param {Float32Array|Float64Array} target */ flatten4dArrayTo(array, target) { let offset = 0; for (let l = 0; l < array.length; l++) { for (let z = 0; z < array[l].length; z++) { for (let y = 0; y < array[l][z].length; y++) { target.set(array[l][z][y], offset); offset += array[l][z][y].length; } } } }, /** * Puts a nested 1d, 2d, or 3d array into a one-dimensional target array * @param {Float32Array|Uint16Array|Uint8Array} array * @param {Float32Array} target */ flattenTo(array, target) { if (utils.isArray(array[0])) { if (utils.isArray(array[0][0])) { if (utils.isArray(array[0][0][0])) { utils.flatten4dArrayTo(array, target); } else { utils.flatten3dArrayTo(array, target); } } else { utils.flatten2dArrayTo(array, target); } } else { target.set(array); } }, /** * * @desc Splits an array into smaller arrays. * Number of elements in one small chunk is given by `part` * * @param {Number[]} array - The array to split into chunks * @param {Number} part - elements in one chunk * * @returns {Number[]} An array of smaller chunks */ splitArray(array, part) { const result = []; for (let i = 0; i < array.length; i += part) { result.push(new array.constructor(array.buffer, i * 4 + array.byteOffset, part)); } return result; }, getAstString(source, ast) { const lines = Array.isArray(source) ? source : source.split(/\r?\n/g); const start = ast.loc.start; const end = ast.loc.end; const result = []; if (start.line === end.line) { result.push(lines[start.line - 1].substring(start.column, end.column)); } else { result.push(lines[start.line - 1].slice(start.column)); for (let i = start.line; i < end.line; i++) { result.push(lines[i]); } result.push(lines[end.line - 1].slice(0, end.column)); } return result.join('\n'); }, allPropertiesOf(obj) { const props = []; do { props.push.apply(props, Object.getOwnPropertyNames(obj)); } while (obj = Object.getPrototypeOf(obj)); return props; }, /** * @param {Array} lines - An Array of strings * @returns {String} Single combined String, separated by *\n* */ linesToString(lines) { if (lines.length > 0) { return lines.join(';\n') + ';\n'; } else { return '\n'; } }, warnDeprecated(type, oldName, newName) { if (newName) { console.warn(`You are using a deprecated ${ type } "${ oldName }". It has been replaced with "${ newName }". Fixing, but please upgrade as it will soon be removed.`); } else { console.warn(`You are using a deprecated ${ type } "${ oldName }". It has been removed. Fixing, but please upgrade as it will soon be removed.`); } }, flipPixels: (pixels, width, height) => { // https://stackoverflow.com/a/41973289/1324039 const halfHeight = height / 2 | 0; // the | 0 keeps the result an int const bytesPerRow = width * 4; // make a temp buffer to hold one row const temp = new Uint8ClampedArray(width * 4); const result = pixels.slice(0); for (let y = 0; y < halfHeight; ++y) { const topOffset = y * bytesPerRow; const bottomOffset = (height - y - 1) * bytesPerRow; // make copy of a row on the top half temp.set(result.subarray(topOffset, topOffset + bytesPerRow)); // copy a row from the bottom half to the top result.copyWithin(topOffset, bottomOffset, bottomOffset + bytesPerRow); // copy the copy of the top half row to the bottom half result.set(temp, bottomOffset); } return result; }, erectPackedFloat: (array, width) => { return array.subarray(0, width); }, erect2DPackedFloat: (array, width, height) => { const yResults = new Array(height); for (let y = 0; y < height; y++) { const xStart = y * width; const xEnd = xStart + width; yResults[y] = array.subarray(xStart, xEnd); } return yResults; }, erect3DPackedFloat: (array, width, height, depth) => { const zResults = new Array(depth); for (let z = 0; z < depth; z++) { const yResults = new Array(height); for (let y = 0; y < height; y++) { const xStart = (z * height * width) + y * width; const xEnd = xStart + width; yResults[y] = array.subarray(xStart, xEnd); } zResults[z] = yResults; } return zResults; }, erectMemoryOptimizedFloat: (array, width) => { return array.subarray(0, width); }, erectMemoryOptimized2DFloat: (array, width, height) => { const yResults = new Array(height); for (let y = 0; y < height; y++) { const offset = y * width; yResults[y] = array.subarray(offset, offset + width); } return yResults; }, erectMemoryOptimized3DFloat: (array, width, height, depth) => { const zResults = new Array(depth); for (let z = 0; z < depth; z++) { const yResults = new Array(height); for (let y = 0; y < height; y++) { const offset = (z * height * width) + (y * width); yResults[y] = array.subarray(offset, offset + width); } zResults[z] = yResults; } return zResults; }, erectFloat: (array, width) => { const xResults = new Float32Array(width); let i = 0; for (let x = 0; x < width; x++) { xResults[x] = array[i]; i += 4; } return xResults; }, erect2DFloat: (array, width, height) => { const yResults = new Array(height); let i = 0; for (let y = 0; y < height; y++) { const xResults = new Float32Array(width); for (let x = 0; x < width; x++) { xResults[x] = array[i]; i += 4; } yResults[y] = xResults; } return yResults; }, erect3DFloat: (array, width, height, depth) => { const zResults = new Array(depth); let i = 0; for (let z = 0; z < depth; z++) { const yResults = new Array(height); for (let y = 0; y < height; y++) { const xResults = new Float32Array(width); for (let x = 0; x < width; x++) { xResults[x] = array[i]; i += 4; } yResults[y] = xResults; } zResults[z] = yResults; } return zResults; }, erectArray2: (array, width) => { const xResults = new Array(width); const xResultsMax = width * 4; let i = 0; for (let x = 0; x < xResultsMax; x += 4) { xResults[i++] = array.subarray(x, x + 2); } return xResults; }, erect2DArray2: (array, width, height) => { const yResults = new Array(height); const XResultsMax = width * 4; for (let y = 0; y < height; y++) { const xResults = new Array(width); const offset = y * XResultsMax; let i = 0; for (let x = 0; x < XResultsMax; x += 4) { xResults[i++] = array.subarray(x + offset, x + offset + 2); } yResults[y] = xResults; } return yResults; }, erect3DArray2: (array, width, height, depth) => { const xResultsMax = width * 4; const zResults = new Array(depth); for (let z = 0; z < depth; z++) { const yResults = new Array(height); for (let y = 0; y < height; y++) { const xResults = new Array(width); const offset = (z * xResultsMax * height) + (y * xResultsMax); let i = 0; for (let x = 0; x < xResultsMax; x += 4) { xResults[i++] = array.subarray(x + offset, x + offset + 2); } yResults[y] = xResults; } zResults[z] = yResults; } return zResults; }, erectArray3: (array, width) => { const xResults = new Array(width); const xResultsMax = width * 4; let i = 0; for (let x = 0; x < xResultsMax; x += 4) { xResults[i++] = array.subarray(x, x + 3); } return xResults; }, erect2DArray3: (array, width, height) => { const xResultsMax = width * 4; const yResults = new Array(height); for (let y = 0; y < height; y++) { const xResults = new Array(width); const offset = y * xResultsMax; let i = 0; for (let x = 0; x < xResultsMax; x += 4) { xResults[i++] = array.subarray(x + offset, x + offset + 3); } yResults[y] = xResults; } return yResults; }, erect3DArray3: (array, width, height, depth) => { const xResultsMax = width * 4; const zResults = new Array(depth); for (let z = 0; z < depth; z++) { const yResults = new Array(height); for (let y = 0; y < height; y++) { const xResults = new Array(width); const offset = (z * xResultsMax * height) + (y * xResultsMax); let i = 0; for (let x = 0; x < xResultsMax; x += 4) { xResults[i++] = array.subarray(x + offset, x + offset + 3); } yResults[y] = xResults; } zResults[z] = yResults; } return zResults; }, erectArray4: (array, width) => { const xResults = new Array(array); const xResultsMax = width * 4; let i = 0; for (let x = 0; x < xResultsMax; x += 4) { xResults[i++] = array.subarray(x, x + 4); } return xResults; }, erect2DArray4: (array, width, height) => { const xResultsMax = width * 4; const yResults = new Array(height); for (let y = 0; y < height; y++) { const xResults = new Array(width); const offset = y * xResultsMax; let i = 0; for (let x = 0; x < xResultsMax; x += 4) { xResults[i++] = array.subarray(x + offset, x + offset + 4); } yResults[y] = xResults; } return yResults; }, erect3DArray4: (array, width, height, depth) => { const xResultsMax = width * 4; const zResults = new Array(depth); for (let z = 0; z < depth; z++) { const yResults = new Array(height); for (let y = 0; y < height; y++) { const xResults = new Array(width); const offset = (z * xResultsMax * height) + (y * xResultsMax); let i = 0; for (let x = 0; x < xResultsMax; x += 4) { xResults[i++] = array.subarray(x + offset, x + offset + 4); } yResults[y] = xResults; } zResults[z] = yResults; } return zResults; }, /** * * @param {String} source * @param {Object} settings * @return {String} */ flattenFunctionToString: (source, settings) => { const { findDependency, thisLookup, doNotDefine } = settings; let flattened = settings.flattened; if (!flattened) { flattened = settings.flattened = {}; } const ast = acorn.parse(source); const functionDependencies = []; let indent = 0; function flatten(ast) { if (Array.isArray(ast)) { const results = []; for (let i = 0; i < ast.length; i++) { results.push(flatten(ast[i])); } return results.join(''); } switch (ast.type) { case 'Program': return flatten(ast.body) + (ast.body[0].type === 'VariableDeclaration' ? ';' : ''); case 'FunctionDeclaration': return `function ${ast.id.name}(${ast.params.map(flatten).join(', ')}) ${ flatten(ast.body) }`; case 'BlockStatement': { const result = []; indent += 2; for (let i = 0; i < ast.body.length; i++) { const flat = flatten(ast.body[i]); if (flat) { result.push(' '.repeat(indent) + flat, ';\n'); } } indent -= 2; return `{\n${result.join('')}}`; } case 'VariableDeclaration': const declarations = utils.normalizeDeclarations(ast) .map(flatten) .filter(r => r !== null); if (declarations.length < 1) { return ''; } else { return `${ast.kind} ${declarations.join(',')}`; } case 'VariableDeclarator': if (ast.init.object && ast.init.object.type === 'ThisExpression') { const lookup = thisLookup(ast.init.property.name, true); if (lookup) { return `${ast.id.name} = ${flatten(ast.init)}`; } else { return null; } } else { return `${ast.id.name} = ${flatten(ast.init)}`; } case 'CallExpression': { if (ast.callee.property.name === 'subarray') { return `${flatten(ast.callee.object)}.${flatten(ast.callee.property)}(${ast.arguments.map(value => flatten(value)).join(', ')})`; } if (ast.callee.object.name === 'gl' || ast.callee.object.name === 'context') { return `${flatten(ast.callee.object)}.${flatten(ast.callee.property)}(${ast.arguments.map(value => flatten(value)).join(', ')})`; } if (ast.callee.object.type === 'ThisExpression') { functionDependencies.push(findDependency('this', ast.callee.property.name)); return `${ast.callee.property.name}(${ast.arguments.map(value => flatten(value)).join(', ')})`; } else if (ast.callee.object.name) { const foundSource = findDependency(ast.callee.object.name, ast.callee.property.name); if (foundSource === null) { // we're not flattening it return `${ast.callee.object.name}.${ast.callee.property.name}(${ast.arguments.map(value => flatten(value)).join(', ')})`; } else { functionDependencies.push(foundSource); // we're flattening it return `${ast.callee.property.name}(${ast.arguments.map(value => flatten(value)).join(', ')})`; } } else if (ast.callee.object.type === 'MemberExpression') { return `${flatten(ast.callee.object)}.${ast.callee.property.name}(${ast.arguments.map(value => flatten(value)).join(', ')})`; } else { throw new Error('unknown ast.callee'); } } case 'ReturnStatement': return `return ${flatten(ast.argument)}`; case 'BinaryExpression': return `(${flatten(ast.left)}${ast.operator}${flatten(ast.right)})`; case 'UnaryExpression': if (ast.prefix) { return `${ast.operator} ${flatten(ast.argument)}`; } else { return `${flatten(ast.argument)} ${ast.operator}`; } case 'ExpressionStatement': return `${flatten(ast.expression)}`; case 'SequenceExpression': return `(${flatten(ast.expressions)})`; case 'ArrowFunctionExpression': return `(${ast.params.map(flatten).join(', ')}) => ${flatten(ast.body)}`; case 'Literal': return ast.raw; case 'Identifier': return ast.name; case 'MemberExpression': if (ast.object.type === 'ThisExpression') { return thisLookup(ast.property.name); } if (ast.computed) { return `${flatten(ast.object)}[${flatten(ast.property)}]`; } return flatten(ast.object) + '.' + flatten(ast.property); case 'ThisExpression': return 'this'; case 'NewExpression': return `new ${flatten(ast.callee)}(${ast.arguments.map(value => flatten(value)).join(', ')})`; case 'ForStatement': return `for (${flatten(ast.init)};${flatten(ast.test)};${flatten(ast.update)}) ${flatten(ast.body)}`; case 'AssignmentExpression': return `${flatten(ast.left)}${ast.operator}${flatten(ast.right)}`; case 'UpdateExpression': return `${flatten(ast.argument)}${ast.operator}`; case 'IfStatement': return `if (${flatten(ast.test)}) ${flatten(ast.consequent)}`; case 'ThrowStatement': return `throw ${flatten(ast.argument)}`; case 'ObjectPattern': return ast.properties.map(flatten).join(', '); case 'ArrayPattern': return ast.elements.map(flatten).join(', '); case 'DebuggerStatement': return 'debugger;'; case 'ConditionalExpression': return `${flatten(ast.test)}?${flatten(ast.consequent)}:${flatten(ast.alternate)}`; case 'Property': if (ast.kind === 'init') { return flatten(ast.key); } } throw new Error(`unhandled ast.type of ${ ast.type }`); } const result = flatten(ast); if (functionDependencies.length > 0) { const flattenedFunctionDependencies = []; for (let i = 0; i < functionDependencies.length; i++) { const functionDependency = functionDependencies[i]; if (!flattened[functionDependency]) { flattened[functionDependency] = true; } functionDependency ? flattenedFunctionDependencies.push(utils.flattenFunctionToString(functionDependency, settings) + '\n') : ''; } return flattenedFunctionDependencies.join('') + result; } return result; }, normalizeDeclarations: (ast) => { if (ast.type !== 'VariableDeclaration') throw new Error('Ast is not of type "VariableDeclaration"'); const normalizedDeclarations = []; for (let declarationIndex = 0; declarationIndex < ast.declarations.length; declarationIndex++) { const declaration = ast.declarations[declarationIndex]; if (declaration.id && declaration.id.type === 'ObjectPattern' && declaration.id.properties) { const { properties } = declaration.id; for (let propertyIndex = 0; propertyIndex < properties.length; propertyIndex++) { const property = properties[propertyIndex]; if (property.value.type === 'ObjectPattern' && property.value.properties) { for (let subPropertyIndex = 0; subPropertyIndex < property.value.properties.length; subPropertyIndex++) { const subProperty = property.value.properties[subPropertyIndex]; if (subProperty.type === 'Property') { normalizedDeclarations.push({ type: 'VariableDeclarator', id: { type: 'Identifier', name: subProperty.key.name }, init: { type: 'MemberExpression', object: { type: 'MemberExpression', object: declaration.init, property: { type: 'Identifier', name: property.key.name }, computed: false }, property: { type: 'Identifier', name: subProperty.key.name }, computed: false } }); } else { throw new Error('unexpected state'); } } } else if (property.value.type === 'Identifier') { normalizedDeclarations.push({ type: 'VariableDeclarator', id: { type: 'Identifier', name: property.value && property.value.name ? property.value.name : property.key.name }, init: { type: 'MemberExpression', object: declaration.init, property: { type: 'Identifier', name: property.key.name }, computed: false } }); } else { throw new Error('unexpected state'); } } } else if (declaration.id && declaration.id.type === 'ArrayPattern' && declaration.id.elements) { const { elements } = declaration.id; for (let elementIndex = 0; elementIndex < elements.length; elementIndex++) { const element = elements[elementIndex]; if (element.type === 'Identifier') { normalizedDeclarations.push({ type: 'VariableDeclarator', id: { type: 'Identifier', name: element.name }, init: { type: 'MemberExpression', object: declaration.init, property: { type: 'Literal', value: elementIndex, raw: elementIndex.toString(), start: element.start, end: element.end }, computed: true } }); } else { throw new Error('unexpected state'); } } } else { normalizedDeclarations.push(declaration); } } return normalizedDeclarations; }, /** * * @param {GPU} gpu * @param image * @return {Array} */ splitHTMLImageToRGB: (gpu, image) => { const rKernel = gpu.createKernel(function(a) { const pixel = a[this.thread.y][this.thread.x]; return pixel.r * 255; }, { output: [image.width, image.height], precision: 'unsigned', argumentTypes: { a: 'HTMLImage' }, }); const gKernel = gpu.createKernel(function(a) { const pixel = a[this.thread.y][this.thread.x]; return pixel.g * 255; }, { output: [image.width, image.height], precision: 'unsigned', argumentTypes: { a: 'HTMLImage' }, }); const bKernel = gpu.createKernel(function(a) { const pixel = a[this.thread.y][this.thread.x]; return pixel.b * 255; }, { output: [image.width, image.height], precision: 'unsigned', argumentTypes: { a: 'HTMLImage' }, }); const aKernel = gpu.createKernel(function(a) { const pixel = a[this.thread.y][this.thread.x]; return pixel.a * 255; }, { output: [image.width, image.height], precision: 'unsigned', argumentTypes: { a: 'HTMLImage' }, }); const result = [ rKernel(image), gKernel(image), bKernel(image), aKernel(image), ]; result.rKernel = rKernel; result.gKernel = gKernel; result.bKernel = bKernel; result.aKernel = aKernel; result.gpu = gpu; return result; }, /** * A visual debug utility * @param {GPU} gpu * @param rgba * @param width * @param height * @return {Object[]} */ splitRGBAToCanvases: (gpu, rgba, width, height) => { const visualKernelR = gpu.createKernel(function(v) { const pixel = v[this.thread.y][this.thread.x]; this.color(pixel.r / 255, 0, 0, 255); }, { output: [width, height], graphical: true, argumentTypes: { v: 'Array2D(4)' } }); visualKernelR(rgba); const visualKernelG = gpu.createKernel(function(v) { const pixel = v[this.thread.y][this.thread.x]; this.color(0, pixel.g / 255, 0, 255); }, { output: [width, height], graphical: true, argumentTypes: { v: 'Array2D(4)' } }); visualKernelG(rgba); const visualKernelB = gpu.createKernel(function(v) { const pixel = v[this.thread.y][this.thread.x]; this.color(0, 0, pixel.b / 255, 255); }, { output: [width, height], graphical: true, argumentTypes: { v: 'Array2D(4)' } }); visualKernelB(rgba); const visualKernelA = gpu.createKernel(function(v) { const pixel = v[this.thread.y][this.thread.x]; this.color(255, 255, 255, pixel.a / 255); }, { output: [width, height], graphical: true, argumentTypes: { v: 'Array2D(4)' } }); visualKernelA(rgba); return [ visualKernelR.canvas, visualKernelG.canvas, visualKernelB.canvas, visualKernelA.canvas, ]; }, getMinifySafeName: (fn) => { try { const ast = acorn.parse(`const value = ${fn.toString()}`); const { init } = ast.body[0].declarations[0]; return init.body.name || init.body.body[0].argument.name; } catch (e) { throw new Error('Unrecognized function type. Please use `() => yourFunctionVariableHere` or function() { return yourFunctionVariableHere; }'); } }, sanitizeName: function(name) { if (dollarSign.test(name)) { name = name.replace(dollarSign, 'S_S'); } if (doubleUnderscore.test(name)) { name = name.replace(doubleUnderscore, 'U_U'); } else if (singleUnderscore.test(name)) { name = name.replace(singleUnderscore, 'u_u'); } return name; } }; const dollarSign = /\$/; const doubleUnderscore = /__/; const singleUnderscore = /_/; const _systemEndianness = utils.getSystemEndianness(); module.exports = { utils };