UNPKG

gpu.js

Version:

GPU Accelerated JavaScript

1,661 lines (1,520 loc) 481 kB
/** * gpu.js * http://gpu.rocks/ * * GPU Accelerated JavaScript * * @version 2.16.0 * @date Wed Nov 16 2022 15:48:37 GMT-0500 (Eastern Standard Time) * * @license MIT * The MIT License * * Copyright (c) 2022 gpu.js Team */(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.GPU = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ },{}],2:[function(require,module,exports){ function glWiretap(gl, options = {}) { const { contextName = 'gl', throwGetError, useTrackablePrimitives, readPixelsFile, recording = [], variables = {}, onReadPixels, onUnrecognizedArgumentLookup, } = options; const proxy = new Proxy(gl, { get: listen }); const contextVariables = []; const entityNames = {}; let imageCount = 0; let indent = ''; let readPixelsVariableName; return proxy; function listen(obj, property) { switch (property) { case 'addComment': return addComment; case 'checkThrowError': return checkThrowError; case 'getReadPixelsVariableName': return readPixelsVariableName; case 'insertVariable': return insertVariable; case 'reset': return reset; case 'setIndent': return setIndent; case 'toString': return toString; case 'getContextVariableName': return getContextVariableName; } if (typeof gl[property] === 'function') { return function() { switch (property) { case 'getError': if (throwGetError) { recording.push(`${indent}if (${contextName}.getError() !== ${contextName}.NONE) throw new Error('error');`); } else { recording.push(`${indent}${contextName}.getError();`); } return gl.getError(); case 'getExtension': { const variableName = `${contextName}Variables${contextVariables.length}`; recording.push(`${indent}const ${variableName} = ${contextName}.getExtension('${arguments[0]}');`); const extension = gl.getExtension(arguments[0]); if (extension && typeof extension === 'object') { const tappedExtension = glExtensionWiretap(extension, { getEntity, useTrackablePrimitives, recording, contextName: variableName, contextVariables, variables, indent, onUnrecognizedArgumentLookup, }); contextVariables.push(tappedExtension); return tappedExtension; } else { contextVariables.push(null); } return extension; } case 'readPixels': const i = contextVariables.indexOf(arguments[6]); let targetVariableName; if (i === -1) { const variableName = getVariableName(arguments[6]); if (variableName) { targetVariableName = variableName; recording.push(`${indent}${variableName}`); } else { targetVariableName = `${contextName}Variable${contextVariables.length}`; contextVariables.push(arguments[6]); recording.push(`${indent}const ${targetVariableName} = new ${arguments[6].constructor.name}(${arguments[6].length});`); } } else { targetVariableName = `${contextName}Variable${i}`; } readPixelsVariableName = targetVariableName; const argumentAsStrings = [ arguments[0], arguments[1], arguments[2], arguments[3], getEntity(arguments[4]), getEntity(arguments[5]), targetVariableName ]; recording.push(`${indent}${contextName}.readPixels(${argumentAsStrings.join(', ')});`); if (readPixelsFile) { writePPM(arguments[2], arguments[3]); } if (onReadPixels) { onReadPixels(targetVariableName, argumentAsStrings); } return gl.readPixels.apply(gl, arguments); case 'drawBuffers': recording.push(`${indent}${contextName}.drawBuffers([${argumentsToString(arguments[0], { contextName, contextVariables, getEntity, addVariable, variables, onUnrecognizedArgumentLookup } )}]);`); return gl.drawBuffers(arguments[0]); } let result = gl[property].apply(gl, arguments); switch (typeof result) { case 'undefined': recording.push(`${indent}${methodCallToString(property, arguments)};`); return; case 'number': case 'boolean': if (useTrackablePrimitives && contextVariables.indexOf(trackablePrimitive(result)) === -1) { recording.push(`${indent}const ${contextName}Variable${contextVariables.length} = ${methodCallToString(property, arguments)};`); contextVariables.push(result = trackablePrimitive(result)); break; } default: if (result === null) { recording.push(`${methodCallToString(property, arguments)};`); } else { recording.push(`${indent}const ${contextName}Variable${contextVariables.length} = ${methodCallToString(property, arguments)};`); } contextVariables.push(result); } return result; } } entityNames[gl[property]] = property; return gl[property]; } function toString() { return recording.join('\n'); } function reset() { while (recording.length > 0) { recording.pop(); } } function insertVariable(name, value) { variables[name] = value; } function getEntity(value) { const name = entityNames[value]; if (name) { return contextName + '.' + name; } return value; } function setIndent(spaces) { indent = ' '.repeat(spaces); } function addVariable(value, source) { const variableName = `${contextName}Variable${contextVariables.length}`; recording.push(`${indent}const ${variableName} = ${source};`); contextVariables.push(value); return variableName; } function writePPM(width, height) { const sourceVariable = `${contextName}Variable${contextVariables.length}`; const imageVariable = `imageDatum${imageCount}`; recording.push(`${indent}let ${imageVariable} = ["P3\\n# ${readPixelsFile}.ppm\\n", ${width}, ' ', ${height}, "\\n255\\n"].join("");`); recording.push(`${indent}for (let i = 0; i < ${imageVariable}.length; i += 4) {`); recording.push(`${indent} ${imageVariable} += ${sourceVariable}[i] + ' ' + ${sourceVariable}[i + 1] + ' ' + ${sourceVariable}[i + 2] + ' ';`); recording.push(`${indent}}`); recording.push(`${indent}if (typeof require !== "undefined") {`); recording.push(`${indent} require('fs').writeFileSync('./${readPixelsFile}.ppm', ${imageVariable});`); recording.push(`${indent}}`); imageCount++; } function addComment(value) { recording.push(`${indent}// ${value}`); } function checkThrowError() { recording.push(`${indent}(() => { ${indent}const error = ${contextName}.getError(); ${indent}if (error !== ${contextName}.NONE) { ${indent} const names = Object.getOwnPropertyNames(gl); ${indent} for (let i = 0; i < names.length; i++) { ${indent} const name = names[i]; ${indent} if (${contextName}[name] === error) { ${indent} throw new Error('${contextName} threw ' + name); ${indent} } ${indent} } ${indent}} ${indent}})();`); } function methodCallToString(method, args) { return `${contextName}.${method}(${argumentsToString(args, { contextName, contextVariables, getEntity, addVariable, variables, onUnrecognizedArgumentLookup })})`; } function getVariableName(value) { if (variables) { for (const name in variables) { if (variables[name] === value) { return name; } } } return null; } function getContextVariableName(value) { const i = contextVariables.indexOf(value); if (i !== -1) { return `${contextName}Variable${i}`; } return null; } } function glExtensionWiretap(extension, options) { const proxy = new Proxy(extension, { get: listen }); const extensionEntityNames = {}; const { contextName, contextVariables, getEntity, useTrackablePrimitives, recording, variables, indent, onUnrecognizedArgumentLookup, } = options; return proxy; function listen(obj, property) { if (typeof obj[property] === 'function') { return function() { switch (property) { case 'drawBuffersWEBGL': recording.push(`${indent}${contextName}.drawBuffersWEBGL([${argumentsToString(arguments[0], { contextName, contextVariables, getEntity: getExtensionEntity, addVariable, variables, onUnrecognizedArgumentLookup })}]);`); return extension.drawBuffersWEBGL(arguments[0]); } let result = extension[property].apply(extension, arguments); switch (typeof result) { case 'undefined': recording.push(`${indent}${methodCallToString(property, arguments)};`); return; case 'number': case 'boolean': if (useTrackablePrimitives && contextVariables.indexOf(trackablePrimitive(result)) === -1) { recording.push(`${indent}const ${contextName}Variable${contextVariables.length} = ${methodCallToString(property, arguments)};`); contextVariables.push(result = trackablePrimitive(result)); } else { recording.push(`${indent}const ${contextName}Variable${contextVariables.length} = ${methodCallToString(property, arguments)};`); contextVariables.push(result); } break; default: if (result === null) { recording.push(`${methodCallToString(property, arguments)};`); } else { recording.push(`${indent}const ${contextName}Variable${contextVariables.length} = ${methodCallToString(property, arguments)};`); } contextVariables.push(result); } return result; }; } extensionEntityNames[extension[property]] = property; return extension[property]; } function getExtensionEntity(value) { if (extensionEntityNames.hasOwnProperty(value)) { return `${contextName}.${extensionEntityNames[value]}`; } return getEntity(value); } function methodCallToString(method, args) { return `${contextName}.${method}(${argumentsToString(args, { contextName, contextVariables, getEntity: getExtensionEntity, addVariable, variables, onUnrecognizedArgumentLookup })})`; } function addVariable(value, source) { const variableName = `${contextName}Variable${contextVariables.length}`; contextVariables.push(value); recording.push(`${indent}const ${variableName} = ${source};`); return variableName; } } function argumentsToString(args, options) { const { variables, onUnrecognizedArgumentLookup } = options; return (Array.from(args).map((arg) => { const variableName = getVariableName(arg); if (variableName) { return variableName; } return argumentToString(arg, options); }).join(', ')); function getVariableName(value) { if (variables) { for (const name in variables) { if (!variables.hasOwnProperty(name)) continue; if (variables[name] === value) { return name; } } } if (onUnrecognizedArgumentLookup) { return onUnrecognizedArgumentLookup(value); } return null; } } function argumentToString(arg, options) { const { contextName, contextVariables, getEntity, addVariable, onUnrecognizedArgumentLookup } = options; if (typeof arg === 'undefined') { return 'undefined'; } if (arg === null) { return 'null'; } const i = contextVariables.indexOf(arg); if (i > -1) { return `${contextName}Variable${i}`; } switch (arg.constructor.name) { case 'String': const hasLines = /\n/.test(arg); const hasSingleQuotes = /'/.test(arg); const hasDoubleQuotes = /"/.test(arg); if (hasLines) { return '`' + arg + '`'; } else if (hasSingleQuotes && !hasDoubleQuotes) { return '"' + arg + '"'; } else if (!hasSingleQuotes && hasDoubleQuotes) { return "'" + arg + "'"; } else { return '\'' + arg + '\''; } case 'Number': return getEntity(arg); case 'Boolean': return getEntity(arg); case 'Array': return addVariable(arg, `new ${arg.constructor.name}([${Array.from(arg).join(',')}])`); case 'Float32Array': case 'Uint8Array': case 'Uint16Array': case 'Int32Array': return addVariable(arg, `new ${arg.constructor.name}(${JSON.stringify(Array.from(arg))})`); default: if (onUnrecognizedArgumentLookup) { const instantiationString = onUnrecognizedArgumentLookup(arg); if (instantiationString) { return instantiationString; } } throw new Error(`unrecognized argument type ${arg.constructor.name}`); } } function trackablePrimitive(value) { return new value.constructor(value); } if (typeof module !== 'undefined') { module.exports = { glWiretap, glExtensionWiretap }; } if (typeof window !== 'undefined') { glWiretap.glExtensionWiretap = glExtensionWiretap; window.glWiretap = glWiretap; } },{}],3:[function(require,module,exports){ function setupArguments(args) { const newArguments = new Array(args.length); for (let i = 0; i < args.length; i++) { const arg = args[i]; if (arg.toArray) { newArguments[i] = arg.toArray(); } else { newArguments[i] = arg; } } return newArguments; } function mock1D() { const args = setupArguments(arguments); const row = new Float32Array(this.output.x); for (let x = 0; x < this.output.x; x++) { this.thread.x = x; this.thread.y = 0; this.thread.z = 0; row[x] = this._fn.apply(this, args); } return row; } function mock2D() { const args = setupArguments(arguments); const matrix = new Array(this.output.y); for (let y = 0; y < this.output.y; y++) { const row = new Float32Array(this.output.x); for (let x = 0; x < this.output.x; x++) { this.thread.x = x; this.thread.y = y; this.thread.z = 0; row[x] = this._fn.apply(this, args); } matrix[y] = row; } return matrix; } function mock2DGraphical() { const args = setupArguments(arguments); for (let y = 0; y < this.output.y; y++) { for (let x = 0; x < this.output.x; x++) { this.thread.x = x; this.thread.y = y; this.thread.z = 0; this._fn.apply(this, args); } } } function mock3D() { const args = setupArguments(arguments); const cube = new Array(this.output.z); for (let z = 0; z < this.output.z; z++) { const matrix = new Array(this.output.y); for (let y = 0; y < this.output.y; y++) { const row = new Float32Array(this.output.x); for (let x = 0; x < this.output.x; x++) { this.thread.x = x; this.thread.y = y; this.thread.z = z; row[x] = this._fn.apply(this, args); } matrix[y] = row; } cube[z] = matrix; } return cube; } function apiDecorate(kernel) { kernel.setOutput = (output) => { kernel.output = setupOutput(output); if (kernel.graphical) { setupGraphical(kernel); } }; kernel.toJSON = () => { throw new Error('Not usable with gpuMock'); }; kernel.setConstants = (flag) => { kernel.constants = flag; return kernel; }; kernel.setGraphical = (flag) => { kernel.graphical = flag; return kernel; }; kernel.setCanvas = (flag) => { kernel.canvas = flag; return kernel; }; kernel.setContext = (flag) => { kernel.context = flag; return kernel; }; kernel.destroy = () => {}; kernel.validateSettings = () => {}; if (kernel.graphical && kernel.output) { setupGraphical(kernel); } kernel.exec = function() { return new Promise((resolve, reject) => { try { resolve(kernel.apply(kernel, arguments)); } catch(e) { reject(e); } }); }; kernel.getPixels = (flip) => { const {x, y} = kernel.output; return flip ? flipPixels(kernel._imageData.data, x, y) : kernel._imageData.data.slice(0); }; kernel.color = function(r, g, b, a) { if (typeof a === 'undefined') { a = 1; } r = Math.floor(r * 255); g = Math.floor(g * 255); b = Math.floor(b * 255); a = Math.floor(a * 255); const width = kernel.output.x; const height = kernel.output.y; const x = kernel.thread.x; const y = height - kernel.thread.y - 1; const index = x + y * width; kernel._colorData[index * 4 + 0] = r; kernel._colorData[index * 4 + 1] = g; kernel._colorData[index * 4 + 2] = b; kernel._colorData[index * 4 + 3] = a; }; const mockMethod = () => kernel; const methods = [ 'setWarnVarUsage', 'setArgumentTypes', 'setTactic', 'setOptimizeFloatMemory', 'setDebug', 'setLoopMaxIterations', 'setConstantTypes', 'setFunctions', 'setNativeFunctions', 'setInjectedNative', 'setPipeline', 'setPrecision', 'setOutputToTexture', 'setImmutable', 'setStrictIntegers', 'setDynamicOutput', 'setHardcodeConstants', 'setDynamicArguments', 'setUseLegacyEncoder', 'setWarnVarUsage', 'addSubKernel', ]; for (let i = 0; i < methods.length; i++) { kernel[methods[i]] = mockMethod; } return kernel; } function setupGraphical(kernel) { const {x, y} = kernel.output; if (kernel.context && kernel.context.createImageData) { const data = new Uint8ClampedArray(x * y * 4); kernel._imageData = kernel.context.createImageData(x, y); kernel._colorData = data; } else { const data = new Uint8ClampedArray(x * y * 4); kernel._imageData = { data }; kernel._colorData = data; } } function setupOutput(output) { let result = null; if (output.length) { if (output.length === 3) { const [x,y,z] = output; result = { x, y, z }; } else if (output.length === 2) { const [x,y] = output; result = { x, y }; } else { const [x] = output; result = { x }; } } else { result = output; } return result; } function gpuMock(fn, settings = {}) { const output = settings.output ? setupOutput(settings.output) : null; function kernel() { if (kernel.output.z) { return mock3D.apply(kernel, arguments); } else if (kernel.output.y) { if (kernel.graphical) { return mock2DGraphical.apply(kernel, arguments); } return mock2D.apply(kernel, arguments); } else { return mock1D.apply(kernel, arguments); } } kernel._fn = fn; kernel.constants = settings.constants || null; kernel.context = settings.context || null; kernel.canvas = settings.canvas || null; kernel.graphical = settings.graphical || false; kernel._imageData = null; kernel._colorData = null; kernel.output = output; kernel.thread = { x: 0, y: 0, z: 0 }; return apiDecorate(kernel); } function flipPixels(pixels, width, height) { const halfHeight = height / 2 | 0; const bytesPerRow = width * 4; 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; temp.set(result.subarray(topOffset, topOffset + bytesPerRow)); result.copyWithin(topOffset, bottomOffset, bottomOffset + bytesPerRow); result.set(temp, bottomOffset); } return result; } module.exports = { gpuMock }; },{}],4:[function(require,module,exports){ const { utils } = require('./utils'); function alias(name, source) { const fnString = source.toString(); return new Function(`return function ${ name } (${ utils.getArgumentNamesFromString(fnString).join(', ') }) { ${ utils.getFunctionBodyFromString(fnString) } }`)(); } module.exports = { alias }; },{"./utils":113}],5:[function(require,module,exports){ const { FunctionNode } = require('../function-node'); class CPUFunctionNode extends FunctionNode { astFunction(ast, retArr) { if (!this.isRootKernel) { retArr.push('function'); retArr.push(' '); retArr.push(this.name); retArr.push('('); for (let i = 0; i < this.argumentNames.length; ++i) { const argumentName = this.argumentNames[i]; if (i > 0) { retArr.push(', '); } retArr.push('user_'); retArr.push(argumentName); } retArr.push(') {\n'); } for (let i = 0; i < ast.body.body.length; ++i) { this.astGeneric(ast.body.body[i], retArr); retArr.push('\n'); } if (!this.isRootKernel) { retArr.push('}\n'); } return retArr; } astReturnStatement(ast, retArr) { const type = this.returnType || this.getType(ast.argument); if (!this.returnType) { this.returnType = type; } if (this.isRootKernel) { retArr.push(this.leadingReturnStatement); this.astGeneric(ast.argument, retArr); retArr.push(';\n'); retArr.push(this.followingReturnStatement); retArr.push('continue;\n'); } else if (this.isSubKernel) { retArr.push(`subKernelResult_${ this.name } = `); this.astGeneric(ast.argument, retArr); retArr.push(';'); retArr.push(`return subKernelResult_${ this.name };`); } else { retArr.push('return '); this.astGeneric(ast.argument, retArr); retArr.push(';'); } return retArr; } astLiteral(ast, retArr) { if (isNaN(ast.value)) { throw this.astErrorOutput( 'Non-numeric literal not supported : ' + ast.value, ast ); } retArr.push(ast.value); return retArr; } astBinaryExpression(ast, retArr) { retArr.push('('); this.astGeneric(ast.left, retArr); retArr.push(ast.operator); this.astGeneric(ast.right, retArr); retArr.push(')'); return retArr; } astIdentifierExpression(idtNode, retArr) { if (idtNode.type !== 'Identifier') { throw this.astErrorOutput( 'IdentifierExpression - not an Identifier', idtNode ); } switch (idtNode.name) { case 'Infinity': retArr.push('Infinity'); break; default: if (this.constants && this.constants.hasOwnProperty(idtNode.name)) { retArr.push('constants_' + idtNode.name); } else { retArr.push('user_' + idtNode.name); } } return retArr; } astForStatement(forNode, retArr) { if (forNode.type !== 'ForStatement') { throw this.astErrorOutput('Invalid for statement', forNode); } const initArr = []; const testArr = []; const updateArr = []; const bodyArr = []; let isSafe = null; if (forNode.init) { this.pushState('in-for-loop-init'); this.astGeneric(forNode.init, initArr); for (let i = 0; i < initArr.length; i++) { if (initArr[i].includes && initArr[i].includes(',')) { isSafe = false; } } this.popState('in-for-loop-init'); } else { isSafe = false; } if (forNode.test) { this.astGeneric(forNode.test, testArr); } else { isSafe = false; } if (forNode.update) { this.astGeneric(forNode.update, updateArr); } else { isSafe = false; } if (forNode.body) { this.pushState('loop-body'); this.astGeneric(forNode.body, bodyArr); this.popState('loop-body'); } if (isSafe === null) { isSafe = this.isSafe(forNode.init) && this.isSafe(forNode.test); } if (isSafe) { retArr.push(`for (${initArr.join('')};${testArr.join('')};${updateArr.join('')}){\n`); retArr.push(bodyArr.join('')); retArr.push('}\n'); } else { const iVariableName = this.getInternalVariableName('safeI'); if (initArr.length > 0) { retArr.push(initArr.join(''), ';\n'); } retArr.push(`for (let ${iVariableName}=0;${iVariableName}<LOOP_MAX;${iVariableName}++){\n`); if (testArr.length > 0) { retArr.push(`if (!${testArr.join('')}) break;\n`); } retArr.push(bodyArr.join('')); retArr.push(`\n${updateArr.join('')};`); retArr.push('}\n'); } return retArr; } astWhileStatement(whileNode, retArr) { if (whileNode.type !== 'WhileStatement') { throw this.astErrorOutput( 'Invalid while statement', whileNode ); } retArr.push('for (let i = 0; i < LOOP_MAX; i++) {'); retArr.push('if ('); this.astGeneric(whileNode.test, retArr); retArr.push(') {\n'); this.astGeneric(whileNode.body, retArr); retArr.push('} else {\n'); retArr.push('break;\n'); retArr.push('}\n'); retArr.push('}\n'); return retArr; } astDoWhileStatement(doWhileNode, retArr) { if (doWhileNode.type !== 'DoWhileStatement') { throw this.astErrorOutput( 'Invalid while statement', doWhileNode ); } retArr.push('for (let i = 0; i < LOOP_MAX; i++) {'); this.astGeneric(doWhileNode.body, retArr); retArr.push('if (!'); this.astGeneric(doWhileNode.test, retArr); retArr.push(') {\n'); retArr.push('break;\n'); retArr.push('}\n'); retArr.push('}\n'); return retArr; } astAssignmentExpression(assNode, retArr) { const declaration = this.getDeclaration(assNode.left); if (declaration && !declaration.assignable) { throw this.astErrorOutput(`Variable ${assNode.left.name} is not assignable here`, assNode); } this.astGeneric(assNode.left, retArr); retArr.push(assNode.operator); this.astGeneric(assNode.right, retArr); return retArr; } astBlockStatement(bNode, retArr) { if (this.isState('loop-body')) { this.pushState('block-body'); for (let i = 0; i < bNode.body.length; i++) { this.astGeneric(bNode.body[i], retArr); } this.popState('block-body'); } else { retArr.push('{\n'); for (let i = 0; i < bNode.body.length; i++) { this.astGeneric(bNode.body[i], retArr); } retArr.push('}\n'); } return retArr; } astVariableDeclaration(varDecNode, retArr) { retArr.push(`${varDecNode.kind} `); const { declarations } = varDecNode; for (let i = 0; i < declarations.length; i++) { if (i > 0) { retArr.push(','); } const declaration = declarations[i]; const info = this.getDeclaration(declaration.id); if (!info.valueType) { info.valueType = this.getType(declaration.init); } this.astGeneric(declaration, retArr); } if (!this.isState('in-for-loop-init')) { retArr.push(';'); } return retArr; } astIfStatement(ifNode, retArr) { retArr.push('if ('); this.astGeneric(ifNode.test, retArr); retArr.push(')'); if (ifNode.consequent.type === 'BlockStatement') { this.astGeneric(ifNode.consequent, retArr); } else { retArr.push(' {\n'); this.astGeneric(ifNode.consequent, retArr); retArr.push('\n}\n'); } if (ifNode.alternate) { retArr.push('else '); if (ifNode.alternate.type === 'BlockStatement' || ifNode.alternate.type === 'IfStatement') { this.astGeneric(ifNode.alternate, retArr); } else { retArr.push(' {\n'); this.astGeneric(ifNode.alternate, retArr); retArr.push('\n}\n'); } } return retArr; } astSwitchStatement(ast, retArr) { const { discriminant, cases } = ast; retArr.push('switch ('); this.astGeneric(discriminant, retArr); retArr.push(') {\n'); for (let i = 0; i < cases.length; i++) { if (cases[i].test === null) { retArr.push('default:\n'); this.astGeneric(cases[i].consequent, retArr); if (cases[i].consequent && cases[i].consequent.length > 0) { retArr.push('break;\n'); } continue; } retArr.push('case '); this.astGeneric(cases[i].test, retArr); retArr.push(':\n'); if (cases[i].consequent && cases[i].consequent.length > 0) { this.astGeneric(cases[i].consequent, retArr); retArr.push('break;\n'); } } retArr.push('\n}'); } astThisExpression(tNode, retArr) { retArr.push('_this'); return retArr; } astMemberExpression(mNode, retArr) { const { signature, type, property, xProperty, yProperty, zProperty, name, origin } = this.getMemberExpressionDetails(mNode); switch (signature) { case 'this.thread.value': retArr.push(`_this.thread.${ name }`); return retArr; case 'this.output.value': switch (name) { case 'x': retArr.push('outputX'); break; case 'y': retArr.push('outputY'); break; case 'z': retArr.push('outputZ'); break; default: throw this.astErrorOutput('Unexpected expression', mNode); } return retArr; case 'value': throw this.astErrorOutput('Unexpected expression', mNode); case 'value[]': case 'value[][]': case 'value[][][]': case 'value.value': if (origin === 'Math') { retArr.push(Math[name]); return retArr; } switch (property) { case 'r': retArr.push(`user_${ name }[0]`); return retArr; case 'g': retArr.push(`user_${ name }[1]`); return retArr; case 'b': retArr.push(`user_${ name }[2]`); return retArr; case 'a': retArr.push(`user_${ name }[3]`); return retArr; } break; case 'this.constants.value': case 'this.constants.value[]': case 'this.constants.value[][]': case 'this.constants.value[][][]': break; case 'fn()[]': this.astGeneric(mNode.object, retArr); retArr.push('['); this.astGeneric(mNode.property, retArr); retArr.push(']'); return retArr; case 'fn()[][]': this.astGeneric(mNode.object.object, retArr); retArr.push('['); this.astGeneric(mNode.object.property, retArr); retArr.push(']'); retArr.push('['); this.astGeneric(mNode.property, retArr); retArr.push(']'); return retArr; default: throw this.astErrorOutput('Unexpected expression', mNode); } if (!mNode.computed) { switch (type) { case 'Number': case 'Integer': case 'Float': case 'Boolean': retArr.push(`${origin}_${name}`); return retArr; } } const markupName = `${origin}_${name}`; switch (type) { case 'Array(2)': case 'Array(3)': case 'Array(4)': case 'Matrix(2)': case 'Matrix(3)': case 'Matrix(4)': case 'HTMLImageArray': case 'ArrayTexture(1)': case 'ArrayTexture(2)': case 'ArrayTexture(3)': case 'ArrayTexture(4)': case 'HTMLImage': default: let size; let isInput; if (origin === 'constants') { const constant = this.constants[name]; isInput = this.constantTypes[name] === 'Input'; size = isInput ? constant.size : null; } else { isInput = this.isInput(name); size = isInput ? this.argumentSizes[this.argumentNames.indexOf(name)] : null; } retArr.push(`${ markupName }`); if (zProperty && yProperty) { if (isInput) { retArr.push('[('); this.astGeneric(zProperty, retArr); retArr.push(`*${ this.dynamicArguments ? '(outputY * outputX)' : size[1] * size[0] })+(`); this.astGeneric(yProperty, retArr); retArr.push(`*${ this.dynamicArguments ? 'outputX' : size[0] })+`); this.astGeneric(xProperty, retArr); retArr.push(']'); } else { retArr.push('['); this.astGeneric(zProperty, retArr); retArr.push(']'); retArr.push('['); this.astGeneric(yProperty, retArr); retArr.push(']'); retArr.push('['); this.astGeneric(xProperty, retArr); retArr.push(']'); } } else if (yProperty) { if (isInput) { retArr.push('[('); this.astGeneric(yProperty, retArr); retArr.push(`*${ this.dynamicArguments ? 'outputX' : size[0] })+`); this.astGeneric(xProperty, retArr); retArr.push(']'); } else { retArr.push('['); this.astGeneric(yProperty, retArr); retArr.push(']'); retArr.push('['); this.astGeneric(xProperty, retArr); retArr.push(']'); } } else if (typeof xProperty !== 'undefined') { retArr.push('['); this.astGeneric(xProperty, retArr); retArr.push(']'); } } return retArr; } astCallExpression(ast, retArr) { if (ast.type !== 'CallExpression') { throw this.astErrorOutput('Unknown CallExpression', ast); } let functionName = this.astMemberExpressionUnroll(ast.callee); if (this.calledFunctions.indexOf(functionName) < 0) { this.calledFunctions.push(functionName); } const isMathFunction = this.isAstMathFunction(ast); if (this.onFunctionCall) { this.onFunctionCall(this.name, functionName, ast.arguments); } retArr.push(functionName); retArr.push('('); const targetTypes = this.lookupFunctionArgumentTypes(functionName) || []; for (let i = 0; i < ast.arguments.length; ++i) { const argument = ast.arguments[i]; let argumentType = this.getType(argument); if (!targetTypes[i]) { this.triggerImplyArgumentType(functionName, i, argumentType, this); } if (i > 0) { retArr.push(', '); } this.astGeneric(argument, retArr); } retArr.push(')'); return retArr; } astArrayExpression(arrNode, retArr) { const returnType = this.getType(arrNode); const arrLen = arrNode.elements.length; const elements = []; for (let i = 0; i < arrLen; ++i) { const element = []; this.astGeneric(arrNode.elements[i], element); elements.push(element.join('')); } switch (returnType) { case 'Matrix(2)': case 'Matrix(3)': case 'Matrix(4)': retArr.push(`[${elements.join(', ')}]`); break; default: retArr.push(`new Float32Array([${elements.join(', ')}])`); } return retArr; } astDebuggerStatement(arrNode, retArr) { retArr.push('debugger;'); return retArr; } } module.exports = { CPUFunctionNode }; },{"../function-node":9}],6:[function(require,module,exports){ const { utils } = require('../../utils'); function constantsToString(constants, types) { const results = []; for (const name in types) { if (!types.hasOwnProperty(name)) continue; const type = types[name]; const constant = constants[name]; switch (type) { case 'Number': case 'Integer': case 'Float': case 'Boolean': results.push(`${name}:${constant}`); break; case 'Array(2)': case 'Array(3)': case 'Array(4)': case 'Matrix(2)': case 'Matrix(3)': case 'Matrix(4)': results.push(`${name}:new ${constant.constructor.name}(${JSON.stringify(Array.from(constant))})`); break; } } return `{ ${ results.join() } }`; } function cpuKernelString(cpuKernel, name) { const header = []; const thisProperties = []; const beforeReturn = []; const useFunctionKeyword = !/^function/.test(cpuKernel.color.toString()); header.push( ' const { context, canvas, constants: incomingConstants } = settings;', ` const output = new Int32Array(${JSON.stringify(Array.from(cpuKernel.output))});`, ` const _constantTypes = ${JSON.stringify(cpuKernel.constantTypes)};`, ` const _constants = ${constantsToString(cpuKernel.constants, cpuKernel.constantTypes)};` ); thisProperties.push( ' constants: _constants,', ' context,', ' output,', ' thread: {x: 0, y: 0, z: 0},' ); if (cpuKernel.graphical) { header.push(` const _imageData = context.createImageData(${cpuKernel.output[0]}, ${cpuKernel.output[1]});`); header.push(` const _colorData = new Uint8ClampedArray(${cpuKernel.output[0]} * ${cpuKernel.output[1]} * 4);`); const colorFn = utils.flattenFunctionToString((useFunctionKeyword ? 'function ' : '') + cpuKernel.color.toString(), { thisLookup: (propertyName) => { switch (propertyName) { case '_colorData': return '_colorData'; case '_imageData': return '_imageData'; case 'output': return 'output'; case 'thread': return 'this.thread'; } return JSON.stringify(cpuKernel[propertyName]); }, findDependency: (object, name) => { return null; } }); const getPixelsFn = utils.flattenFunctionToString((useFunctionKeyword ? 'function ' : '') + cpuKernel.getPixels.toString(), { thisLookup: (propertyName) => { switch (propertyName) { case '_colorData': return '_colorData'; case '_imageData': return '_imageData'; case 'output': return 'output'; case 'thread': return 'this.thread'; } return JSON.stringify(cpuKernel[propertyName]); }, findDependency: () => { return null; } }); thisProperties.push( ' _imageData,', ' _colorData,', ` color: ${colorFn},` ); beforeReturn.push( ` kernel.getPixels = ${getPixelsFn};` ); } const constantTypes = []; const constantKeys = Object.keys(cpuKernel.constantTypes); for (let i = 0; i < constantKeys.length; i++) { constantTypes.push(cpuKernel.constantTypes[constantKeys]); } if (cpuKernel.argumentTypes.indexOf('HTMLImageArray') !== -1 || constantTypes.indexOf('HTMLImageArray') !== -1) { const flattenedImageTo3DArray = utils.flattenFunctionToString((useFunctionKeyword ? 'function ' : '') + cpuKernel._imageTo3DArray.toString(), { doNotDefine: ['canvas'], findDependency: (object, name) => { if (object === 'this') { return (useFunctionKeyword ? 'function ' : '') + cpuKernel[name].toString(); } return null; }, thisLookup: (propertyName) => { switch (propertyName) { case 'canvas': return; case 'context': return 'context'; } } }); beforeReturn.push(flattenedImageTo3DArray); thisProperties.push(` _mediaTo2DArray,`); thisProperties.push(` _imageTo3DArray,`); } else if (cpuKernel.argumentTypes.indexOf('HTMLImage') !== -1 || constantTypes.indexOf('HTMLImage') !== -1) { const flattenedImageTo2DArray = utils.flattenFunctionToString((useFunctionKeyword ? 'function ' : '') + cpuKernel._mediaTo2DArray.toString(), { findDependency: (object, name) => { return null; }, thisLookup: (propertyName) => { switch (propertyName) { case 'canvas': return 'settings.canvas'; case 'context': return 'settings.context'; } throw new Error('unhandled thisLookup'); } }); beforeReturn.push(flattenedImageTo2DArray); thisProperties.push(` _mediaTo2DArray,`); } return `function(settings) { ${ header.join('\n') } for (const p in _constantTypes) { if (!_constantTypes.hasOwnProperty(p)) continue; const type = _constantTypes[p]; switch (type) { case 'Number': case 'Integer': case 'Float': case 'Boolean': case 'Array(2)': case 'Array(3)': case 'Array(4)': case 'Matrix(2)': case 'Matrix(3)': case 'Matrix(4)': if (incomingConstants.hasOwnProperty(p)) { console.warn('constant ' + p + ' of type ' + type + ' cannot be resigned'); } continue; } if (!incomingConstants.hasOwnProperty(p)) { throw new Error('constant ' + p + ' not found'); } _constants[p] = incomingConstants[p]; } const kernel = (function() { ${cpuKernel._kernelString} }) .apply({ ${thisProperties.join('\n')} }); ${ beforeReturn.join('\n') } return kernel; }`; } module.exports = { cpuKernelString }; },{"../../utils":113}],7:[function(require,module,exports){ const { Kernel } = require('../kernel'); const { FunctionBuilder } = require('../function-builder'); const { CPUFunctionNode } = require('./function-node'); const { utils } = require('../../utils'); const { cpuKernelString } = require('./kernel-string'); class CPUKernel extends Kernel { static getFeatures() { return this.features; } static get features() { return Object.freeze({ kernelMap: true, isIntegerDivisionAccurate: true }); } static get isSupported() { return true; } static isContextMatch(context) { return false; } static get mode() { return 'cpu'; } static nativeFunctionArguments() { return null; } static nativeFunctionReturnType() { throw new Error(`Looking up native function return type not supported on ${this.name}`); } static combineKernels(combinedKernel) { return combinedKernel; } static getSignature(kernel, argumentTypes) { return 'cpu' + (argumentTypes.length > 0 ? ':' + argumentTypes.join(',') : ''); } constructor(source, settings) { super(source, settings); this.mergeSettings(source.settings || settings); this._imageData = null; this._colorData = null; this._kernelString = null; this._prependedString = []; this.thread = { x: 0, y: 0, z: 0 }; this.translatedSources = null; } initCanvas() { if (typeof document !== 'undefined') { return document.createElement('canvas'); } else if (typeof OffscreenCanvas !== 'undefined') { return new OffscreenCanvas(0, 0); } } initContext() { if (!this.canvas) return null; return this.canvas.getContext('2d'); } initPlugins(settings) { return []; } validateSettings(args) { if (!this.output || this.output.length === 0) { if (args.length !== 1) { throw new Error('Auto output only supported for kernels with only one input'); } const argType = utils.getVariableType(args[0], this.strictIntegers); if (argType === 'Array') { this.output = utils.getDimensions(argType); } else if (argType === 'NumberTexture' || argType === 'ArrayTexture(4)') { this.output = args[0].output; } else { throw new Error('Auto output not supported for input type: ' + argType); } } if (this.graphical) { if (this.output.length !== 2) { throw new Error('Output must have 2 dimensions on graphical mode'); } } this.checkOutput(); } translateSource() { this.leadingReturnStatement = this.output.length > 1 ? 'resultX[x] = ' : 'result[x] = '; if (this.subKernels) { const followingReturnStatement = []; for (let i = 0; i < this.subKernels.length; i++) { const { name } = this.subKernels[i]; followingReturnStatement.push(this.output.length > 1 ? `resultX_${ name }[x] = subKernelResult_${ name };\n` : `result_${ name }[x] = subKernelResult_${ name };\n`); } this.followingReturnStatement = followingReturnStatement.join(''); } const functionBuilder = FunctionBuilder.fromKernel(this, CPUFunctionNode); this.translatedSources = functionBuilder.getPrototypes('kernel'); if (!this.graphical && !this.returnType) { this.returnType = functionBuilder.getKernelResultType(); } } build() { if (this.built) return; this.setupConstants(); this.setupArguments(arguments); this.validateSettings(arguments); this.translateSource(); if (this.graphical) { const { canvas, output } = this; if (!canvas) { throw new Error('no canvas available for using graphical output'); } const width = output[0]; const height = output[1] || 1; canvas.width = width; canvas.height = height; this._imageData = this.context.createImageData(width, height); this._colorData = new Uint8ClampedArray(width * height * 4); } const kernelString = this.getKernelString(); this.kernelString = kernelString; if (this.debug) { console.log('Function output:'); console.log(kernelString); } try { this.run = new Function([], kernelString).bind(this)(); } catch (e) { console.error('An error occurred compiling the javascript: ', e); } this.buildSignature(arguments); this.built = true; } color(r, g, b, a) { if (typeof a === 'undefined') { a = 1; } r = Math.floor(r * 255); g = Math.floor(g * 255); b = Math.floor(b * 255); a = Math.floor(a * 255); const width = this.output[0]; const height = this.output[1]; const x = this.thread.x; const y = height - this.thread.y - 1; const index = x + y * width; this._colorData[index * 4 + 0] = r; this._colorData[index * 4 + 1] = g; this._colorData[index * 4 + 2] = b; this._colorData[index * 4 + 3] = a; } getKernelString() { if (this._kernelString !== null) return this._kernelString; let kernelThreadString = null; let { translatedSources } = this; if (translatedSources.length > 1) { translatedSources = translatedSources.filter(fn => { if (/^function/.test(fn)) return fn; kernelThreadString = fn; return false; }); } else { kernelThreadString = translatedSources.shift(); } return this._kernelString = ` const LOOP_MAX = ${ this._getLoopMaxString() }; ${ this.injectedNative || '' } const _this = this; ${ this._resultKernelHeader() } ${ this._processConstants() } return (${ this.argumentNames.map(argumentName => 'user_' + argumentName).join(', ') }) => { ${ this._prependedString.join('') } ${ this._earlyThrows() } ${ this._processArguments() } ${ this.graphical ? this._graphicalKernelBody(kernelThreadString) : this._resultKernelBody(kernelThreadString) } ${ translatedSources.length > 0 ? translatedSources.join('\n') : '' } };`; } toString() { return cpuKernelString(this); } _getLoopMaxString() { return ( this.loopMaxIterations ? ` ${ parseInt(this.loopMaxIterations) };` : ' 1000;' ); } _processConstants() { if (!this.constants) return ''; const result = []; for (let p in this.constants) { const type = this.constantTypes[p]; switch (type) { case 'HTMLCanvas': case 'OffscreenCanvas': case 'HTMLImage': case 'ImageBitmap': case 'ImageData': case 'HTMLVideo': result.push(` const constants_${p} = this._mediaTo2DArray(this.constants.${p});\n`); break; case 'HTMLImageArray': result.push(` const constants_${p} = this._imageTo3DArray(this.constants.${p});\n`); break; case 'Input': result.push(` const constants_${p} = this.constants.${p}.value;\n`); break; default: result.push(` const constants_${p} = this.constants.${p};\n`); } } return result.join(''); } _earlyThrows() { if (this.graphical) return ''; if (this.immutable) return ''; if (!this.pipeline) return ''; const arrayArguments = []; for (let i = 0; i < this.argumentTypes.length; i++) { if (this.argumentTypes[i] === 'Array') { arrayArguments.push(this.argumentNames[i]); } } if (arrayArguments.length === 0) return ''; const checks = []; for (let i = 0; i < arrayArguments.length; i++) { const argumentName = arrayArguments[i]; const checkSubKernels = this._mapSubKernels(subKernel => `user_${argumentName} === result_${subKernel.name}`).join(' || '); checks.push(`user_${argumentName} === result${checkSubKernels ? ` || ${checkSubKernels}` : ''}`); } return `if (${checks.join(' || ')}) throw new Error('Source and destination arrays are the same. Use immutable = true');`; } _processArguments() { const result = []; for (let i = 0; i < this.argumentTypes.length; i++) { const variableName = `user_${this.argumentNames[i]}`; switch (this.argumentTypes[i])