gpu.js
Version:
GPU Accelerated JavaScript
1,661 lines (1,520 loc) • 481 kB
JavaScript
/**
* 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])