From 8be5dcec0ca0fa037dfeafe66732180359a4630e Mon Sep 17 00:00:00 2001 From: Robert Plummer Date: Thu, 20 Apr 2017 10:28:56 -0400 Subject: [PATCH] More refactoring towards es6 --- src/backend/base-function-node.js | 196 +++++ src/backend/base-kernel.js | 92 +++ src/backend/base-runner.js | 82 +++ src/backend/cpu/cpu-function-node.js | 26 + src/backend/cpu/cpu-kernel.js | 120 ++++ src/backend/cpu/cpu-runner.js | 35 + src/backend/function-builder.js | 28 +- src/backend/function-node.js | 218 ------ src/backend/gpu-core.js | 185 ----- .../gpu-function-node.js} | 562 ++++++++------- src/backend/gpu/gpu-kernel.js | 520 ++++++++++++++ src/backend/gpu/gpu-runner.js | 67 ++ src/backend/mode-cpu.js | 135 ---- src/backend/mode-gpu.js | 679 ------------------ src/gpu-utils.js | 107 ++- src/gpu.js | 51 +- src/jison/ecmascript.jison | 3 +- test/src/internal/functionNode_test.js | 8 +- 18 files changed, 1581 insertions(+), 1533 deletions(-) create mode 100644 src/backend/base-function-node.js create mode 100644 src/backend/base-kernel.js create mode 100644 src/backend/base-runner.js create mode 100644 src/backend/cpu/cpu-function-node.js create mode 100644 src/backend/cpu/cpu-kernel.js create mode 100644 src/backend/cpu/cpu-runner.js delete mode 100644 src/backend/function-node.js delete mode 100644 src/backend/gpu-core.js rename src/backend/{function-node-web-gl.js => gpu/gpu-function-node.js} (60%) create mode 100644 src/backend/gpu/gpu-kernel.js create mode 100644 src/backend/gpu/gpu-runner.js delete mode 100644 src/backend/mode-cpu.js delete mode 100644 src/backend/mode-gpu.js diff --git a/src/backend/base-function-node.js b/src/backend/base-function-node.js new file mode 100644 index 00000000..dc0bba9d --- /dev/null +++ b/src/backend/base-function-node.js @@ -0,0 +1,196 @@ +const GPUUtils = require('../gpu-utils'); +const parser = require('../parser').parser; + +/// +/// Class: CPUFunctionNode +/// +/// [INTERNAL] Represents a single function, inside JS, webGL, or openGL. +/// +/// This handles all the raw state, converted state, etc. Of a single function. +/// +/// Properties: +/// functionName - {String} Name of the function +/// jsFunction - {JS Function} The JS Function the node represents +/// jsFunctionString - {String} jsFunction.toString() +/// paramNames - {[String,...]} Parameter names of the function +/// paramType - {[String,...]} Shader land parameter type assumption +/// isRootKernel - {Boolean} Special indicator, for kernel function +/// webglFunctionString - {String} webgl converted function string +/// openglFunctionString - {String} opengl converted function string +/// calledFunctions - {[String,...]} List of all the functions called +/// initVariables - {[String,...]} List of variables initialized in the function +/// readVariables - {[String,...]} List of variables read operations occur +/// writeVariables - {[String,...]} List of variables write operations occur +/// +export default class CPUFunctionNode { + + // + // Constructor + //---------------------------------------------------------------------------------------------------- + + /// + /// Function: functionNode + /// + /// [Constructor] Builds the function with the given JS function, and argument type array. + /// + /// Parameters: + /// gpu - {GPU} The GPU instance + /// functionName - {String} Function name to assume, if its null, it attempts to extract from the function + /// jsFunction - {JS Function / String} JS Function to do conversion + /// paramTypeArray - {[String,...]} Parameter type array, assumes all parameters are 'float' if null + /// returnType - {String} The return type, assumes 'float' if null + /// + constructor(functionName, jsFunction, paramTypeArray, returnType) { + // + // Internal vars setup + // + this.calledFunctions = []; + this.initVariables = []; + this.readVariables = []; + this.writeVariables = []; + + // + // Missing jsFunction object exception + // + if(jsFunction == null) { + throw 'jsFunction, parameter is null'; + } + + // + // Setup jsFunction and its string property + validate them + // + this.jsFunctionString = jsFunction.toString(); + if(!GPUUtils.isFunctionString(this.jsFunctionString)) { + console.error('jsFunction, to string conversion check failed: not a function?', this.jsFunctionString); + throw 'jsFunction, to string conversion check failed: not a function?'; + } + + if(!GPUUtils.isFunction(jsFunction)) { + //throw 'jsFunction, is not a valid JS Function'; + this.jsFunction = null; + } else { + this.jsFunction = jsFunction; + } + + // + // Setup the function name property + // + this.functionName = functionName + || (jsFunction && jsFunction.name) + || GPUUtils.getFunctionNameFromString(this.jsFunctionString); + + if(!(this.functionName)) { + throw 'jsFunction, missing name argument or value'; + } + + // + // Extract parameter name, and its argument types + // + this.paramNames = GPUUtils.getParamNamesFromString(this.jsFunctionString); + if(paramTypeArray != null) { + if(paramTypeArray.length != this.paramNames.length) { + throw 'Invalid argument type array length, against function length -> ('+ + paramTypeArray.length+','+ + this.paramNames.length+ + ')'; + } + this.paramType = paramTypeArray; + } else { + this.paramType = []; + for(let a = 0; a < this.paramNames.length; ++a) { + this.paramType.push('float'); + } + } + + // + // Return type handling + // + this.returnType = returnType || 'float'; + } + + // + // Core function + //---------------------------------------------------------------------------------------------------- + + /// + /// Function: getJSFunction + /// + /// Gets and return the stored JS Function. + /// Note: that this internally eval the function, if only the string was provided on construction + /// + /// Returns: + /// {JS Function} The function object + /// + getJsFunction() { + if(this.jsFunction) { + return this.jsFunction; + } + + if(this.jsFunctionString) { + this.jsFunction = eval(this.jsFunctionString); + return this.jsFunction; + } + + throw 'Missing jsFunction, and jsFunctionString parameter'; + } + + /// + /// Function: getJS_AST + /// + /// Parses the class function JS, and returns its Abstract Syntax Tree object. + /// + /// This is used internally to convert to shader code + /// + /// Parameters: + /// inParser - {JISON Parser} Parser to use, assumes in scope 'parser' if null + /// + /// Returns: + /// {AST Object} The function AST Object, note that result is cached under this.jsFunctionAST; + /// + getJsAST(inParser) { + if(this.jsFunctionAST) { + return this.jsFunctionAST; + } + + inParser = inParser || parser; + if(inParser == null) { + throw 'Missing JS to AST parser'; + } + + const prasedObj = parser.parse('var '+this.functionName+' = '+this.jsFunctionString+';'); + if(prasedObj === null) { + throw 'Failed to parse JS code via JISON'; + } + + // take out the function object, outside the var declarations + const funcAST = prasedObj.body[0].declarations[0].init; + this.jsFunctionAST = funcAST; + + return funcAST; + } + + + /// + /// Function: getFunctionString + /// + /// Returns the converted webgl shader function equivalent of the JS function + /// + /// Returns: + /// {String} webgl function string, result is cached under this.webglFunctionString + /// + getFunctionString(opt) { + return this.functionString; + } + + /// + /// Function: setFunctionString + /// + /// Set the functionString value, overwriting it + /// + /// Parameters: + /// functionString - {String} Shader code string, representing the function + /// + setFunctionString(functionString) { + this.functionString = functionString; + } +} diff --git a/src/backend/base-kernel.js b/src/backend/base-kernel.js new file mode 100644 index 00000000..cd4a8291 --- /dev/null +++ b/src/backend/base-kernel.js @@ -0,0 +1,92 @@ +const GPUUtils = require('../gpu-utils'); +export default class BaseKernel extends Function { + constructor(fnString, runner) { + const args = GPUUtils.getParamNamesFromString(fnString); + super(args, ); + this.gpujs = runner; + this._dimensions = null; + this._debug = false; + this._graphical = false; + this._loopMaxIterations = 0; + this._constants = 0; + this._wraparound = null; + this._hardcodeConstants = null; + this._outputToTexture = null; + this._floatTextures = null; + this._floatOutput = null; + this._floatOutputForce = null; + this.texSize = null; + } + + build() { + throw new Error('"build" not defined on Base'); + } + + dimensions(dim) { + this._dimensions = dim; + return this; + } + + debug(flag) { + this._debug = flag; + return this; + } + + graphical(flag) { + this._graphical = flag; + return this; + } + + loopMaxIterations(max) { + this._loopMaxIterations = max; + return this; + } + + constants(constants) { + this._constants = constants; + return this; + } + + wraparound(flag) { + console.warn('Wraparound mode is not supported and undocumented.'); + this._wraparound = flag; + return this; + } + + hardcodeConstants(flag) { + this._hardcodeConstants = flag; + return this; + } + + outputToTexture(flag) { + this._outputToTexture = flag; + return this; + } + + floatTextures(flag) { + this._floatTextures = flag; + return this; + } + + floatOutput(flag) { + this._floatOutput = flag; + return this; + } + + floatOutputForce(flag) { + this._floatOutputForce = flag; + return this; + } + + getCanvas() { + return this.canvas; + } + + getWebgl() { + return this.webgl; + } + + validateOptions() { + throw new Error('"validateOptions not defined'); + } +} \ No newline at end of file diff --git a/src/backend/base-runner.js b/src/backend/base-runner.js new file mode 100644 index 00000000..da44fd2b --- /dev/null +++ b/src/backend/base-runner.js @@ -0,0 +1,82 @@ +const FunctionBuilder = require('../function-builder'); +const GPUUtils = require('../../gpu-utils'); + +/// +/// Class: Base +/// +/// Represents the 'private/protected' namespace of the GPU class +/// +/// *base.js* internal functions namespace +/// *gpu.js* PUBLIC function namespace +/// +/// I know @private makes more sense, but since the documentation engine state is undetirmined. +/// (See https://github.com/gpujs/gpu.js/issues/19 regarding documentation engine issue) +/// File isolation is currently the best way to go +/// +export default class BaseRunner { + constructor() { + this.kernel = null; + this.fn = null; + this.fnString = null; + + this._canvas = GPUUtils.initCanvas(); + this._webgl = GPUUtils.initWebGl(this._canvas); + this.programCache = {}; + this.endianness = GPUUtils.systemEndianness(); + + this.functionBuilder = new FunctionBuilder(this); + this.functionBuilder.polyfillStandardFunctions(); + } + + // Legacy method to get webgl : Preseved for backwards compatibility + getGl() { + return this._webgl; + } + + textureToArray(texture) { + const copy = this.createKernel(function(x) { + return x[this.thread.z][this.thread.y][this.thread.x]; + }); + + return copy(texture); + } + + deleteTexture(texture) { + this._webgl.deleteTexture(texture.texture); + } + + setFn(fn) { + this.fn = fn; + this.fnString = fn.toString(); + return this; + } + + /// + /// Get and returns the ASYNCHRONOUS executor, of a class and kernel + /// This returns a Promise object from an argument set. + /// + /// Note that there is no current implementation. + /// + buildPromiseKernel() { + throw new Error('not yet implemented'); + } + + get mode() { + throw new Error('"mode" not implemented on Base kernel'); + } + + /// + /// Get and returns the Synchronous executor, of a class and kernel + /// Which returns the result directly after passing the arguments. + /// + buildKernel() { + const fnString = this.fnString; + if( !GPUUtils.isFunctionString(fnString) ) { + throw 'Unable to get body of kernel function'; + } + + this.kernel = new this.Kernel(this.fnString); + + this.kernel.build(this.fnString, this.fn); + } +} diff --git a/src/backend/cpu/cpu-function-node.js b/src/backend/cpu/cpu-function-node.js new file mode 100644 index 00000000..129c9ed1 --- /dev/null +++ b/src/backend/cpu/cpu-function-node.js @@ -0,0 +1,26 @@ +const BaseFunctionNode = require('../base-function-node'); + +/// +/// Class: functionNode +/// +/// [INTERNAL] Represents a single function, inside JS, webGL, or openGL. +/// +/// This handles all the raw state, converted state, etc. Of a single function. +/// +/// Properties: +/// functionName - {String} Name of the function +/// jsFunction - {JS Function} The JS Function the node represents +/// jsFunctionString - {String} jsFunction.toString() +/// paramNames - {[String,...]} Parameter names of the function +/// paramType - {[String,...]} Shader land parameter type assumption +/// isRootKernel - {Boolean} Special indicator, for kernel function +/// webglFunctionString - {String} webgl converted function string +/// openglFunctionString - {String} opengl converted function string +/// calledFunctions - {[String,...]} List of all the functions called +/// initVariables - {[String,...]} List of variables initialized in the function +/// readVariables - {[String,...]} List of variables read operations occur +/// writeVariables - {[String,...]} List of variables write operations occur +/// +export default class CPUFunctionNode extends BaseFunctionNode { + +} diff --git a/src/backend/cpu/cpu-kernel.js b/src/backend/cpu/cpu-kernel.js new file mode 100644 index 00000000..7f2dea0d --- /dev/null +++ b/src/backend/cpu/cpu-kernel.js @@ -0,0 +1,120 @@ +const BaseKernel = require('../base-kernel'); +const GPUUtils = require('../../gpu-utils'); + +export default class CPUKernel extends BaseKernel { + validateOptions() { + if (!this._dimensions || this._dimensions.length === 0) { + if (arguments.length != 1) { + throw 'Auto dimensions only supported for kernels with only one input'; + } + + const argType = GPUUtils.getArgumentType(arguments[0]); + if (argType == 'Array') { + this._dimensions = GPUUtils.getDimensions(argType); + } else if (argType == 'Texture') { + this._dimensions = arguments[0].dimensions; + } else { + throw 'Auto dimensions not supported for input type: ' + argType; + } + } + } + + build() { + const kernelArgs = []; + for (let i = 0; i < arguments.length; i++) { + const argType = GPUUtils.getArgumentType(arguments[i]); + if (argType == 'Array' || argType == 'Number') { + kernelArgs[i] = arguments[i]; + } else if (argType == 'Texture') { + kernelArgs[i] = arguments[i].toArray(); + } else { + throw 'Input type not supported (CPU): ' + arguments[i]; + } + } + + const threadDim = GPUUtils.clone(this._dimensions); + + while (threadDim.length < 3) { + threadDim.push(1); + } + + let ret = new Array(threadDim[2]); + for (let i = 0; i < threadDim[2]; i++) { + ret[i] = new Array(threadDim[1]); + for (let j = 0; j < threadDim[1]; j++) { + ret[i][j] = new Array(threadDim[0]); + } + } + + const ctx = { + thread: { + x: 0, + y: 0, + z: 0 + }, + dimensions: { + x: threadDim[0], + y: threadDim[1], + z: threadDim[2] + }, + constants: this._constants + }; + + let canvasCtx; + let imageData; + let data; + if (this._graphical) { + canvas.width = threadDim[0]; + canvas.height = threadDim[1]; + + canvasCtx = canvas.getContext('2d'); + imageData = canvasCtx.createImageData(threadDim[0], threadDim[1]); + data = new Uint8ClampedArray(threadDim[0] * threadDim[1] * 4); + + ctx.color = function (r, g, b, a) { + if (a == undefined) { + a = 1.0; + } + + r = Math.floor(r * 255); + g = Math.floor(g * 255); + b = Math.floor(b * 255); + a = Math.floor(a * 255); + + const width = ctx.dimensions.x; + const height = ctx.dimensions.y; + + const x = ctx.thread.x; + const y = height - ctx.thread.y - 1; + + const index = x + y * width; + + data[index * 4 + 0] = r; + data[index * 4 + 1] = g; + data[index * 4 + 2] = b; + data[index * 4 + 3] = a; + }; + } + + for (ctx.thread.z = 0; ctx.thread.z < threadDim[2]; ctx.thread.z++) { + for (ctx.thread.y = 0; ctx.thread.y < threadDim[1]; ctx.thread.y++) { + for (ctx.thread.x = 0; ctx.thread.x < threadDim[0]; ctx.thread.x++) { + ret[ctx.thread.z][ctx.thread.y][ctx.thread.x] = kernel.apply(ctx, kernelArgs); + } + } + } + + if (this._graphical) { + imageData.data.set(data); + canvasCtx.putImageData(imageData, 0, 0); + } + + if (this._dimensions.length == 1) { + ret = ret[0][0]; + } else if (this._dimensions.length == 2) { + ret = ret[0]; + } + + return ret; + } +} \ No newline at end of file diff --git a/src/backend/cpu/cpu-runner.js b/src/backend/cpu/cpu-runner.js new file mode 100644 index 00000000..161cce96 --- /dev/null +++ b/src/backend/cpu/cpu-runner.js @@ -0,0 +1,35 @@ +const GPUUtils = require('../../gpu-utils'); +const BaseRunner = require('../base-runner'); + +export default class CPURunner extends BaseRunner { + constructor() { + super(); + this._canvas = GPUUtils.initCanvas(); + this.Kernel = CPUKernel; + this.kernel = null; + } + /// JS fallback transformation, basically pure JS + /// + /// @param inputFunction The calling to perform the conversion + /// @param opt The parameter object + /// + /// @returns callable function if converted, else returns null + buildKernel(fnString, fn) { + const kernel = this._kernelFunction; + const opt = this._kernelParamObj; + let canvas = gpu._canvasCpu; + if (!canvas) { + canvas = gpu._canvasCpu = GPUUtils.initCanvas(); + } + + + + ret.canvas = canvas; + + return this.setupExecutorExtendedFunctions(ret, opt); + } + + get mode() { + return 'cpu'; + } +} diff --git a/src/backend/function-builder.js b/src/backend/function-builder.js index 964507d6..ad4a36c2 100644 --- a/src/backend/function-builder.js +++ b/src/backend/function-builder.js @@ -1,4 +1,4 @@ -const FunctionNode = require('function-node'); +const FunctionNode = require('./function-node'); /// /// Class: functionBuilder /// @@ -30,8 +30,8 @@ export default class FunctionBuilder { /// gpu - {GPU} The GPU instance /// functionName - {String} Function name to assume, if its null, it attempts to extract from the function /// jsFunction - {JS Function} JS Function to do conversion - /// paramTypeArray - {[String,...]} Parameter type array, assumes all parameters are "float" if null - /// returnType - {String} The return type, assumes "float" if null + /// paramTypeArray - {[String,...]} Parameter type array, assumes all parameters are 'float' if null + /// returnType - {String} The return type, assumes 'float' if null /// addFunction(functionName, jsFunction, paramTypeArray, returnType) { this.addFunctionNode(new FunctionNode(this.gpu, functionName, jsFunction, paramTypeArray, returnType)); @@ -46,7 +46,7 @@ export default class FunctionBuilder { /// inNode - {functionNode} functionNode to add /// addFunctionNode(inNode) { - this.nodeMap[ inNode.functionName ] = inNode; + this.nodeMap[inNode.functionName] = inNode; } /// @@ -54,11 +54,11 @@ export default class FunctionBuilder { /// /// Trace all the depending functions being called, from a single function /// - /// This allow for "uneeded" functions to be automatically optimized out. + /// This allow for 'uneeded' functions to be automatically optimized out. /// Note that the 0-index, is the starting function trace. /// /// Parameters: - /// functionName - {String} Function name to trace from, default to "kernel" + /// functionName - {String} Function name to trace from, default to 'kernel' /// retList - {[String,...]} Returning list of function names that is traced. Including itself. /// /// Returns: @@ -75,7 +75,7 @@ export default class FunctionBuilder { } else { retList.push(functionName); - fNode.getWebGlFunctionString(opt); //ensure JS trace is done + fNode.getFunctionString(opt); //ensure JS trace is done for(let i = 0; i < fNode.calledFunctions.length; ++i) { this.traceFunctionCalls(fNode.calledFunctions[i], retList, opt); } @@ -99,10 +99,10 @@ export default class FunctionBuilder { for(let i = 0; i < functionList.length; ++i) { const node = this.nodeMap[functionList[i]]; if(node) { - ret.push(this.nodeMap[functionList[i]].getWebGlFunctionString(opt)); + ret.push(this.nodeMap[functionList[i]].getFunctionString(opt)); } } - return ret.join("\n"); + return ret.join('\n'); } webGlPrototypeStringFromFunctionNames(functionList, opt) { @@ -110,10 +110,10 @@ export default class FunctionBuilder { for(let i = 0; i < functionList.length; ++i) { const node = this.nodeMap[functionList[i]]; if(node) { - ret.push(this.nodeMap[functionList[i]].getWebGlFunctionPrototypeString(opt)); + ret.push(this.nodeMap[functionList[i]].getFunctionPrototypeString(opt)); } } - return ret.join("\n"); + return ret.join('\n'); } /// @@ -163,14 +163,14 @@ export default class FunctionBuilder { //--------------------------------------------------------- // Round function used in polyfill - round(a) { return Math.floor(a + 0.5); } + static round(a) { return Math.floor(a + 0.5); } /// /// Function: polyfillStandardFunctions /// - /// Polyfill in the missing Math funcitons (round) + /// Polyfill in the missing Math functions (round) /// polyfillStandardFunctions() { - this.addFunction(null, round); + this.addFunction(null, FunctionBuilder.round); } } diff --git a/src/backend/function-node.js b/src/backend/function-node.js deleted file mode 100644 index 59b48068..00000000 --- a/src/backend/function-node.js +++ /dev/null @@ -1,218 +0,0 @@ -/// -/// Class: functionNode -/// -/// [INTERNAL] Represents a single function, inside JS, webGL, or openGL. -/// -/// This handles all the raw state, converted state, etc. Of a single function. -/// -/// Properties: -/// functionName - {String} Name of the function -/// jsFunction - {JS Function} The JS Function the node represents -/// jsFunctionString - {String} jsFunction.toString() -/// paramNames - {[String,...]} Parameter names of the function -/// paramType - {[String,...]} Shader land parameter type assumption -/// isRootKernel - {Boolean} Special indicator, for kernel function -/// webglFunctionString - {String} webgl converted function string -/// openglFunctionString - {String} opengl converted function string -/// calledFunctions - {[String,...]} List of all the functions called -/// initVariables - {[String,...]} List of variables initialized in the function -/// readVariables - {[String,...]} List of variables read operations occur -/// writeVariables - {[String,...]} List of variables write operations occur -/// -export default class FunctionNode { - - // - // Constructor - //---------------------------------------------------------------------------------------------------- - - /// - /// Function: functionNode - /// - /// [Constructor] Builds the function with the given JS function, and argument type array. - /// - /// Parameters: - /// gpu - {GPU} The GPU instance - /// functionName - {String} Function name to assume, if its null, it attempts to extract from the function - /// jsFunction - {JS Function / String} JS Function to do conversion - /// paramTypeArray - {[String,...]} Parameter type array, assumes all parameters are 'float' if null - /// returnType - {String} The return type, assumes 'float' if null - /// - constructor(gpu, functionName, jsFunction, paramTypeArray, returnType) { - - this.gpu = gpu; - - // - // Internal vars setup - // - this.calledFunctions = []; - this.initVariables = []; - this.readVariables = []; - this.writeVariables = []; - - // - // Missing jsFunction object exception - // - if(jsFunction == null) { - throw 'jsFunction, parameter is null'; - } - - // - // Setup jsFunction and its string property + validate them - // - this.jsFunctionString = jsFunction.toString(); - if(!GPUUtils.isFunctionString(this.jsFunctionString)) { - console.error('jsFunction, to string conversion check falied: not a function?', this.jsFunctionString); - throw 'jsFunction, to string conversion check falied: not a function?'; - } - - if(!GPUUtils.isFunction(jsFunction)) { - //throw 'jsFunction, is not a valid JS Function'; - this.jsFunction = null; - } else { - this.jsFunction = jsFunction; - } - - // - // Setup the function name property - // - this.functionName = functionName - || (jsFunction && jsFunction.name) - || GPUUtils.getFunctionNameFromString(this.jsFunctionString); - - if(!(this.functionName)) { - throw 'jsFunction, missing name argument or value'; - } - - // - // Extract parameter name, and its argument types - // - this.paramNames = GPUUtils.getParamNamesFromString(this.jsFunctionString); - if(paramTypeArray != null) { - if(paramTypeArray.length != this.paramNames.length) { - throw 'Invalid argument type array length, against function length -> ('+ - paramTypeArray.length+','+ - this.paramNames.length+ - ')'; - } - this.paramType = paramTypeArray; - } else { - this.paramType = []; - for(var a=0; a 0 ) { - retArr.push(", "); + for (let i = 0; i < funcParam.paramNames.length; ++i) { + if (i > 0) { + retArr.push(', '); } - retArr.push( funcParam.paramType[i] ); - retArr.push(" "); - retArr.push("user_"); - retArr.push( funcParam.paramNames[i] ); + retArr.push(funcParam.paramType[i]); + retArr.push(' '); + retArr.push('user_'); + retArr.push(funcParam.paramNames[i]); } - retArr.push(");\n"); + retArr.push(');\n'); return retArr; } @@ -238,45 +239,45 @@ export default class FunctionNodeWebGl { /// @param retArr return array string /// @param funcParam FunctionNode, that tracks compilation state /// - /// @returns the appened retArr + /// @returns the append retArr astFunctionExpression(ast, retArr, funcParam) { // Setup function return type and name - if(funcParam.isRootKernel) { - retArr.push("void"); + if (funcParam.isRootKernel) { + retArr.push('void'); funcParam.kernalAst = ast; } else { retArr.push(funcParam.returnType); } - retArr.push(" "); + retArr.push(' '); retArr.push(funcParam.functionName); - retArr.push("("); + retArr.push('('); - if(!funcParam.isRootKernel) { + if (!funcParam.isRootKernel) { // Arguments handling - for( var i = 0; i < funcParam.paramNames.length; ++i ) { - if( i > 0 ) { - retArr.push(", "); + for (let i = 0; i < funcParam.paramNames.length; ++i) { + if (i > 0) { + retArr.push(', '); } - retArr.push( funcParam.paramType[i] ); - retArr.push(" "); - retArr.push("user_"); - retArr.push( funcParam.paramNames[i] ); + retArr.push(funcParam.paramType[i]); + retArr.push(' '); + retArr.push('user_'); + retArr.push(funcParam.paramNames[i]); } } // Function opening - retArr.push(") {\n"); + retArr.push(') {\n'); // Body statement iteration - for(var i=0; i 0) { - retArr.push(","); + retArr.push(','); } this.astGeneric(vardecNode.declarations[i], retArr, funcParam); } - retArr.push(";"); + retArr.push(';'); return retArr; } @@ -554,32 +555,32 @@ export default class FunctionNodeWebGl { this.astGeneric(ivardecNode.id, retArr, funcParam); if (ivardecNode.init !== null) { - retArr.push("="); + retArr.push('='); this.astGeneric(ivardecNode.init, retArr, funcParam); } return retArr; } astIfStatement(ifNode, retArr, funcParam) { - retArr.push("if("); + retArr.push('if ('); this.astGeneric(ifNode.test, retArr, funcParam); - retArr.push(")"); - if (ifNode.consequent.type == "BlockStatement") { + retArr.push(')'); + if (ifNode.consequent.type == 'BlockStatement') { this.astGeneric(ifNode.consequent, retArr, funcParam); } else { - retArr.push(" {\n"); + retArr.push(' {\n'); this.astGeneric(ifNode.consequent, retArr, funcParam); - retArr.push("\n}\n"); + retArr.push('\n}\n'); } if (ifNode.alternate) { - retArr.push("else "); - if (ifNode.alternate.type == "BlockStatement") { + retArr.push('else '); + if (ifNode.alternate.type == 'BlockStatement') { this.astGeneric(ifNode.alternate, retArr, funcParam); } else { - retArr.push(" {\n"); + retArr.push(' {\n'); this.astGeneric(ifNode.alternate, retArr, funcParam); - retArr.push("\n}\n"); + retArr.push('\n}\n'); } } return retArr; @@ -587,26 +588,26 @@ export default class FunctionNodeWebGl { } astBreakStatement(brNode, retArr, funcParam) { - retArr.push("break;\n"); + retArr.push('break;\n'); return retArr; } astContinueStatement(crNode, retArr, funcParam) { - retArr.push("continue;\n"); + retArr.push('continue;\n'); return retArr; } astLogicalExpression(logNode, retArr, funcParam) { - retArr.push("("); + retArr.push('('); this.astGeneric(logNode.left, retArr, funcParam); retArr.push(logNode.operator); this.astGeneric(logNode.right, retArr, funcParam); - retArr.push(")"); + retArr.push(')'); return retArr; } astUpdateExpression(uNode, retArr, funcParam) { - if(uNode.prefix) { + if (uNode.prefix) { retArr.push(uNode.operator); this.astGeneric(uNode.argument, retArr, funcParam); } else { @@ -618,7 +619,7 @@ export default class FunctionNodeWebGl { } astUnaryExpression(uNode, retArr, funcParam) { - if(uNode.prefix) { + if (uNode.prefix) { retArr.push(uNode.operator); this.astGeneric(uNode.argument, retArr, funcParam); } else { @@ -630,86 +631,86 @@ export default class FunctionNodeWebGl { } astThisExpression(tNode, retArr, funcParam) { - retArr.push("this"); + retArr.push('this'); return retArr; } astMemberExpression(mNode, retArr, funcParam) { - if(mNode.computed) { - if (mNode.object.type == "Identifier") { + if (mNode.computed) { + if (mNode.object.type == 'Identifier') { // Working logger - var reqName = mNode.object.name; - var funcName = funcParam.funcName || "kernel"; - var assumeNotTexture = false; + const reqName = mNode.object.name; + const funcName = funcParam.funcName || 'kernel'; + let assumeNotTexture = false; // Possibly an array request - handle it as such - if(funcParam != "kernel" && funcParam.paramNames ) { + if (funcParam != 'kernel' && funcParam.paramNames) { var idx = funcParam.paramNames.indexOf(reqName); - if( idx >= 0 && funcParam.paramType[idx] == "float") { + if (idx >= 0 && funcParam.paramType[idx] == 'float') { assumeNotTexture = true; } } - if(assumeNotTexture) { + if (assumeNotTexture) { // Get from array this.astGeneric(mNode.object, retArr, funcParam); - retArr.push("[int("); + retArr.push('[int('); this.astGeneric(mNode.property, retArr, funcParam); - retArr.push(")]"); + retArr.push(')]'); //console.log(mNode.property.operator); } else { // Get from texture // This normally refers to the global read only input vars - retArr.push("get("); + retArr.push('get('); this.astGeneric(mNode.object, retArr, funcParam); - retArr.push(", vec2("); + retArr.push(', vec2('); this.astGeneric(mNode.object, retArr, funcParam); - retArr.push("Size[0],"); + retArr.push('Size[0],'); this.astGeneric(mNode.object, retArr, funcParam); - retArr.push("Size[1]), vec3("); + retArr.push('Size[1]), vec3('); this.astGeneric(mNode.object, retArr, funcParam); - retArr.push("Dim[0],"); + retArr.push('Dim[0],'); this.astGeneric(mNode.object, retArr, funcParam); - retArr.push("Dim[1],"); + retArr.push('Dim[1],'); this.astGeneric(mNode.object, retArr, funcParam); - retArr.push("Dim[2]"); - retArr.push("), "); + retArr.push('Dim[2]'); + retArr.push('), '); this.astGeneric(mNode.property, retArr, funcParam); - retArr.push(")"); + retArr.push(')'); } } else { this.astGeneric(mNode.object, retArr, funcParam); - var last = retArr.pop(); - retArr.push(","); + const last = retArr.pop(); + retArr.push(','); this.astGeneric(mNode.property, retArr, funcParam); - retArr.push(")"); + retArr.push(')'); //console.log(mNode.property.operator); } } else { // Unroll the member expression - var unrolled = astMemberExpression_unroll(mNode); + var unrolled = this.astMemberExpressionUnroll(mNode); var unrolled_lc = unrolled.toLowerCase(); // Its a constant, remove this.constants. - if( unrolled.indexOf(constantsPrefix) === 0 ) { - unrolled = 'constants_'+unrolled.slice( constantsPrefix.length ); + if (unrolled.indexOf(constantsPrefix) === 0) { + unrolled = 'constants_'+unrolled.slice(constantsPrefix.length); } - if (unrolled_lc == "this.thread.x") { + if (unrolled_lc == 'this.thread.x') { retArr.push('threadId.x'); - } else if (unrolled_lc == "this.thread.y") { + } else if (unrolled_lc == 'this.thread.y') { retArr.push('threadId.y'); - } else if (unrolled_lc == "this.thread.z") { + } else if (unrolled_lc == 'this.thread.z') { retArr.push('threadId.z'); - } else if (unrolled_lc == "this.dimensions.x") { + } else if (unrolled_lc == 'this.dimensions.x') { retArr.push('uOutputDim.x'); - } else if (unrolled_lc == "this.dimensions.y") { + } else if (unrolled_lc == 'this.dimensions.y') { retArr.push('uOutputDim.y'); - } else if (unrolled_lc == "this.dimensions.z") { + } else if (unrolled_lc == 'this.dimensions.z') { retArr.push('uOutputDim.z'); } else { retArr.push(unrolled); @@ -719,9 +720,9 @@ export default class FunctionNodeWebGl { } astSequenceExpression(sNode, retArr, funcParam) { - for (var i = 0; i < sNode.expressions.length; i++) { + for (let i = 0; i < sNode.expressions.length; i++) { if (i > 0) { - retArr.push(","); + retArr.push(','); } this.astGeneric(sNode.expressions, retArr, funcParam); } @@ -736,27 +737,27 @@ export default class FunctionNodeWebGl { /// /// @returns {String} the function namespace call, unrolled astMemberExpressionUnroll(ast, funcParam) { - if( ast.type == "Identifier" ) { + if (ast.type == 'Identifier') { return ast.name; - } else if( ast.type == "ThisExpression" ) { - return "this"; + } else if (ast.type == 'ThisExpression') { + return 'this'; } - if( ast.type == "MemberExpression" ) { - if( ast.object && ast.property ) { + if (ast.type == 'MemberExpression') { + if (ast.object && ast.property) { return ( - astMemberExpression_unroll( ast.object, funcParam ) + - "." + - astMemberExpression_unroll( ast.property, funcParam ) - ); + this.astMemberExpressionUnroll(ast.object, funcParam) + + '.' + + this.astMemberExpressionUnroll(ast.property, funcParam) + ); } } // Failure, unknown expression - throw asterrorOutput( - "Unknown CallExpression_unroll", + throw astErrorOutput( + 'Unknown CallExpression_unroll', ast, funcParam - ); + ); } /// Prases the abstract syntax tree, binary expression @@ -765,52 +766,52 @@ export default class FunctionNodeWebGl { /// @param retArr return array string /// @param funcParam FunctionNode, that tracks compilation state /// - /// @returns the appened retArr + /// @returns the appended retArr astCallExpression(ast, retArr, funcParam) { - if( ast.callee ) { + if (ast.callee) { // Get the full function call, unrolled - var funcName = astMemberExpression_unroll(ast.callee); + let funcName = this.astMemberExpressionUnroll(ast.callee); // Its a math operator, remove the prefix - if( funcName.indexOf(jsMathPrefix) === 0 ) { - funcName = funcName.slice( jsMathPrefix.length ); + if (funcName.indexOf(jsMathPrefix) === 0) { + funcName = funcName.slice(jsMathPrefix.length); } // Its a local function, remove this - if( funcName.indexOf(localPrefix) === 0 ) { - funcName = funcName.slice( localPrefix.length ); + if (funcName.indexOf(localPrefix) === 0) { + funcName = funcName.slice(localPrefix.length); } // Register the function into the called registry - if( funcParam.calledFunctions.indexOf(funcName) < 0 ) { + if (funcParam.calledFunctions.indexOf(funcName) < 0) { funcParam.calledFunctions.push(funcName); } // Call the function - retArr.push( funcName ); + retArr.push(funcName); // Open arguments space - retArr.push( "(" ); + retArr.push('('); // Add the vars - for(var i=0; i 0) { - retArr.push(", "); + for (let i = 0; i < ast.arguments.length; ++i) { + if (i > 0) { + retArr.push(', '); } this.astGeneric(ast.arguments[i],retArr,funcParam); } // Close arguments space - retArr.push( ")" ); + retArr.push(')'); return retArr; } // Failure, unknown expression - throw asterrorOutput( - "Unknown CallExpression", + throw astErrorOutput( + 'Unknown CallExpression', ast, funcParam - ); + ); return retArr; } @@ -821,27 +822,46 @@ export default class FunctionNodeWebGl { /// @param retArr return array string /// @param funcParam FunctionNode, that tracks compilation state /// - /// @returns the appened retArr + /// @returns the append retArr astArrayExpression(arrNode, retArr, funcParam) { // console.log(arrNode); - var arrLen = arrNode.elements.length; + const arrLen = arrNode.elements.length; - retArr.push("float["+arrLen+"]("); - for(var i=0; i 0) { - retArr.push(", "); + retArr.push('float['+arrLen+']('); + for (let i = 0; i < arrLen; ++i) { + if (i > 0) { + retArr.push(', '); } - var subNode = arrNode.elements[i]; + const subNode = arrNode.elements[i]; this.astGeneric(subNode, retArr, funcParam) } - retArr.push(")"); + retArr.push(')'); return retArr; // // Failure, unknown expression - // throw asterrorOutput( - // "Unknown ArrayExpression", + // throw astErrorOutput( + // 'Unknown ArrayExpression', // arrNode, funcParam - // ); + //); + } + + /// + /// Function: getFunctionPrototypeString + /// + /// Returns the converted webgl shader function equivalent of the JS function + /// + /// Returns: + /// {String} webgl function string, result is cached under this.getFunctionPrototypeString + /// + getFunctionPrototypeString(opt) { + opt = opt || {}; + if(this.webGlFunctionPrototypeString) { + return this.webGlFunctionPrototypeString; + } + return this.functionPrototypeString = new FunctionNodeWebGl(this, { + prototypeOnly: true, + isRootKernel: opt.isRootKernel + }); } } \ No newline at end of file diff --git a/src/backend/gpu/gpu-kernel.js b/src/backend/gpu/gpu-kernel.js new file mode 100644 index 00000000..d9c225bb --- /dev/null +++ b/src/backend/gpu/gpu-kernel.js @@ -0,0 +1,520 @@ +const BaseKernel = require('../base-kernel'); +const GPUUtils = require('../../gpu-utils'); +const GPUTexture = require('../../gpu-texture'); +const GPUFunctionNode = require('./gpu-function-node'); + +export default class GPUKernel extends BaseKernel { + validateOptions() { + const isReadPixel = GPUUtils.isFloatReadPixelsSupported(this.gpujs); + if (this._floatTextures === true && !GPUUtils.OES_texture_float) { + throw 'Float textures are not supported on this browser'; + } else if (this._floatOutput === true && this._floatOutputForce !== true && !isReadPixel) { + throw 'Float texture outputs are not supported on this browser'; + } else if (this._floatTextures === undefined && GPUUtils.OES_texture_float) { + this._floatTextures = true; + this._floatOutput = isReadPixel && !this._graphical; + } + + if (!this._dimensions || this._dimensions.length === 0) { + if (arguments.length != 1) { + throw 'Auto dimensions only supported for kernels with only one input'; + } + + const argType = GPUUtils.getArgumentType(arguments[0]); + if (argType == "Array") { + this._dimensions = GPUUtils.getDimensions(argType); + } else if (argType == "Texture") { + this._dimensions = arguments[0]._dimensions; + } else { + throw 'Auto dimensions not supported for input type: ' + argType; + } + } + + this.texSize = GPUUtils.dimToTexSize(this, this._dimensions, true); + + if (this._graphical) { + if (this._dimensions.length != 2) { + throw 'Output must have 2 dimensions on graphical mode'; + } + + if (this._floatOutput) { + throw 'Cannot use graphical mode and float output at the same time'; + } + + this.texSize = GPUUtils.clone(this._dimensions); + } else if (this._floatOutput === undefined && GPUUtils.OES_texture_float) { + this._floatOutput = true; + } + } + + build(fnString, fn) { + this.validateOptions(); + const paramNames = GPUUtils.getParamNamesFromString(fnString); + + const vertices = new Float32Array([ + -1, -1, + 1, -1, + -1, 1, + 1, 1]); + const texCoords = new Float32Array([ + 0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + const builder = this.functionBuilder; + const endianness = this.endianness; + const texSize = this.texSize; + this.canvas.width = texSize[0]; + this.canvas.height = texSize[1]; + gl.viewport(0, 0, texSize[0], texSize[1]); + + const threadDim = GPUUtils.clone(this._dimensions); + while (threadDim.length < 3) { + threadDim.push(1); + } + + if (program === undefined) { + let constantsStr = ''; + if (this._constants) { + for (let name in this._constants) { + let value = parseFloat(this._constants[name]); + + if (Number.isInteger(value)) { + constantsStr += 'const float constants_' + name + '=' + parseInt(value) + '.0;\n'; + } else { + constantsStr += 'const float constants_' + name + '=' + parseFloat(value) + ';\n'; + } + } + } + + let paramStr = ''; + + const paramType = []; + for (let i = 0; i < paramNames.length; i++) { + const argType = GPUUtils.getArgumentType(arguments[i]); + paramType.push(argType); + if (this._hardcodeConstants) { + if (argType == "Array" || argType == "Texture") { + const paramDim = GPUUtils.getDimensions(arguments[i], true); + const paramSize = GPUUtils.dimToTexSize(gpu, paramDim); + + paramStr += 'uniform highp sampler2D user_' + paramNames[i] + ';\n'; + paramStr += 'highp vec2 user_' + paramNames[i] + 'Size = vec2(' + paramSize[0] + '.0, ' + paramSize[1] + '.0);\n'; + paramStr += 'highp vec3 user_' + paramNames[i] + 'Dim = vec3(' + paramDim[0] + '.0, ' + paramDim[1] + '.0, ' + paramDim[2] + '.0);\n'; + } else if (argType == "Number" && Number.isInteger(arguments[i])) { + paramStr += 'highp float user_' + paramNames[i] + ' = ' + arguments[i] + '.0;\n'; + } else if (argType == "Number") { + paramStr += 'highp float user_' + paramNames[i] + ' = ' + arguments[i] + ';\n'; + } + } else { + if (argType == "Array" || argType == "Texture") { + paramStr += 'uniform highp sampler2D user_' + paramNames[i] + ';\n'; + paramStr += 'uniform highp vec2 user_' + paramNames[i] + 'Size;\n'; + paramStr += 'uniform highp vec3 user_' + paramNames[i] + 'Dim;\n'; + } else if (argType == "Number") { + paramStr += 'uniform highp float user_' + paramNames[i] + ';\n'; + } + } + } + + const kernelNode = new GPUFunctionNode(gpu, "kernel", kernel); + kernelNode.paramNames = paramNames; + kernelNode.paramType = paramType; + kernelNode.isRootKernel = true; + builder.addFunctionNode(kernelNode); + + const vertShaderSrc = ` +precision highp float; +precision highp int; +precision highp sampler2D; + +attribute highp vec2 aPos; +attribute highp vec2 aTexCoord; + +varying highp vec2 vTexCoord; + +void main(void) { + gl_Position = vec4(aPos, 0, 1); + vTexCoord = aTexCoord; +} +`; + + const fragShaderSrc = ` +precision highp float; +precision highp int; +precision highp sampler2D; + +#define LOOP_MAX ${ (this._loopMaxIterations ? parseInt(this._loopMaxIterations)+'.0' : '100.0') }; +#define EPSILON 0.0000001; + +${ this._hardcodeConstants + ? `highp vec3 uOutputDim = vec3(${ threadDim[0] },${ threadDim[1] }, ${ threadDim[2] });` + : 'uniform highp vec3 uOutputDim};' +} +${ this._hardcodeConstants + ? `highp vec2 uTexSize = vec2(${ texSize[0] }, ${ texSize[1] });` + : 'uniform highp vec2 uTexSize;' +} + +varying highp vec2 vTexCoord; + +vec4 round(vec4 x) { + return floor(x + 0.5); +} + +highp float round(highp float x) { + return floor(x + 0.5); +} + +vec2 integerMod(vec2 x, float y) { + vec2 res = floor(mod(x, y)); + return res * step(1.0 - floor(y), -res); +} + +vec3 integerMod(vec3 x, float y) { + vec3 res = floor(mod(x, y)); + return res * step(1.0 - floor(y), -res); +} + +vec4 integerMod(vec4 x, vec4 y) { + vec4 res = floor(mod(x, y)); + return res * step(1.0 - floor(y), -res); +} + +highp float integerMod(highp float x, highp float y) { + highp float res = floor(mod(x, y)); + return res * (res > floor(y) - 1.0 ? 0.0 : 1.0); +} + +highp int integerMod(highp int x, highp int y) { + return int(integerMod(float(x), float(y))); +} + +// Here be dragons! +// DO NOT OPTIMIZE THIS CODE +// YOU WILL BREAK SOMETHING ON SOMEBODY\'S MACHINE +// LEAVE IT AS IT IS, LEST YOU WASTE YOUR OWN TIME +const vec2 MAGIC_VEC = vec2(1.0, -256.0); +const vec4 SCALE_FACTOR = vec4(1.0, 256.0, 65536.0, 0.0); +const vec4 SCALE_FACTOR_INV = vec4(1.0, 0.00390625, 0.0000152587890625, 0.0); // 1, 1/256, 1/65536 +highp float decode32(highp vec4 rgba) { + ${ endianness == 'LE' + ? '' + : 'rgba.rgba = rgba.abgr;' + } + rgba *= 255.0; + vec2 gte128; + gte128.x = rgba.b >= 128.0 ? 1.0 : 0.0; + gte128.y = rgba.a >= 128.0 ? 1.0 : 0.0; + float exponent = 2.0 * rgba.a - 127.0 + dot(gte128, MAGIC_VEC); + float res = exp2(round(exponent)); + rgba.b = rgba.b - 128.0 * gte128.x; + res = dot(rgba, SCALE_FACTOR) * exp2(round(exponent-23.0)) + res; + res *= gte128.y * -2.0 + 1.0; + return res; +} + +highp vec4 encode32(highp float f) { +highp float F = abs(f); +highp float sign = f < 0.0 ? 1.0 : 0.0; +highp float exponent = floor(log2(F)); +highp float mantissa = (exp2(-exponent) * F); +// exponent += floor(log2(mantissa)); +vec4 rgba = vec4(F * exp2(23.0-exponent)) * SCALE_FACTOR_INV; +rgba.rg = integerMod(rgba.rg, 256.0); +rgba.b = integerMod(rgba.b, 128.0); +rgba.a = exponent*0.5 + 63.5; +rgba.ba += vec2(integerMod(exponent+127.0, 2.0), sign) * 128.0; +rgba = floor(rgba); +rgba *= 0.003921569; // 1/255 +${ endianness == 'LE' + ? '' + : 'rgba.rgba = rgba.abgr;' +} +return rgba;', +} +// Dragons end here + +highp float index; +highp vec3 threadId; + +highp vec3 indexTo3D(highp float idx, highp vec3 texDim) { + highp float z = floor(idx / (texDim.x * texDim.y)); + idx -= z * texDim.x * texDim.y; + highp float y = floor(idx / texDim.x); + highp float x = integerMod(idx, texDim.x); + return vec3(x, y, z); +} + +highp float get(highp sampler2D tex, highp vec2 texSize, highp vec3 texDim, highp float z, highp float y, highp float x) { + highp vec3 xyz = vec3(x, y, z); + xyz = floor(xyz + 0.5); + ${ this._wraparound + ? ' xyz = mod(xyz, texDim);' + : '' + } + highp float index = round(xyz.x + texDim.x * (xyz.y + texDim.y * xyz.z)); + ${ this._floatTextures ? ' int channel = int(integerMod(index, 4.0));' : '' } + ${ this._floatTextures ? ' index = float(int(index)/4);' : ''} + highp float w = round(texSize.x); + vec2 st = vec2(integerMod(index, w), float(int(index) / int(w))) + 0.5; + ${ this._floatTextures ? ' index = float(int(index)/4);' : ''} + highp vec4 texel = texture2D(tex, st / texSize); + ${ this._floatTextures ? ' if (channel == 0) return texel.r;' : '' } + ${ this._floatTextures ? ' if (channel == 1) return texel.g;' : '' } + ${ this._floatTextures ? ' if (channel == 2) return texel.b;' : '' } + ${ this._floatTextures ? ' if (channel == 3) return texel.a;' : '' } + ${ this._floatTextures ? '' : ' return decode32(texel);' } +} + +highp float get(highp sampler2D tex, highp vec2 texSize, highp vec3 texDim, highp float y, highp float x) { + return get(tex, texSize, texDim, 0.0, y, x); +} + +highp float get(highp sampler2D tex, highp vec2 texSize, highp vec3 texDim, highp float x) { + return get(tex, texSize, texDim, 0.0, 0.0, x); +} + +const bool outputToColor = ${ this._graphical ? 'true' : 'false' }; +highp vec4 actualColor; +void color(float r, float g, float b, float a) { + actualColor = vec4(r,g,b,a); +} + +void color(float r, float g, float b) { + color(r,g,b,1.0); +} + +highp float kernelResult = 0.0; +${ paramStr } +${ constantsStr } +builder.webGlPrototypeString("kernel", opt) +builder.webGlString("kernel", opt) + +void main(void) { + index = floor(vTexCoord.s * float(uTexSize.x)) + floor(vTexCoord.t * float(uTexSize.y)) * uTexSize.x; + ${ this._floatOutput ? 'index *= 4.0;' : '' } + threadId = indexTo3D(index, uOutputDim); + kernel(); + if (outputToColor == true) { + gl_FragColor = actualColor; + } else { + ${ this._floatOutput ? '' : 'gl_FragColor = encode32(kernelResult);' } + ${ this._floatOutput ? 'gl_FragColor.r = kernelResult;' : '' } + ${ this._floatOutput ? 'index += 1.0; threadId = indexTo3D(index, uOutputDim); kernel();' : '' } + ${ this._floatOutput ? 'gl_FragColor.g = kernelResult;' : '' } + ${ this._floatOutput ? 'index += 1.0; threadId = indexTo3D(index, uOutputDim); kernel();' : '' } + ${ this._floatOutput ? 'gl_FragColor.b = kernelResult;' : '' } + ${ this._floatOutput ? 'index += 1.0; threadId = indexTo3D(index, uOutputDim); kernel();' : '' } + ${ this._floatOutput ? 'gl_FragColor.a = kernelResult;' : '' } + } +}`; + + const vertShader = gl.createShader(gl.VERTEX_SHADER); + const fragShader = gl.createShader(gl.FRAGMENT_SHADER); + + gl.shaderSource(vertShader, vertShaderSrc); + gl.shaderSource(fragShader, fragShaderSrc); + + gl.compileShader(vertShader); + gl.compileShader(fragShader); + + if (!gl.getShaderParameter(vertShader, gl.COMPILE_STATUS)) { + console.log(vertShaderSrc); + console.error("An error occurred compiling the shaders: " + gl.getShaderInfoLog(vertShader)); + throw "Error compiling vertex shader"; + } + if (!gl.getShaderParameter(fragShader, gl.COMPILE_STATUS)) { + console.log(fragShaderSrc); + console.error("An error occurred compiling the shaders: " + gl.getShaderInfoLog(fragShader)); + throw "Error compiling fragment shader"; + } + + if (this._debug) { + console.log('Options:'); + console.dir(opt); + console.log('GLSL Shader Output:'); + console.log(fragShaderSrc); + } + + const program = this.program = gl.createProgram(); + gl.attachShader(program, vertShader); + gl.attachShader(program, fragShader); + gl.linkProgram(program); + + programCache[programCacheKey] = program; + programUniformLocationCache[programCacheKey] = []; + } + + gl.useProgram(program); + + const texCoordOffset = vertices.byteLength; + let buffer = bufferCache[programCacheKey]; + if (!buffer) { + buffer = gl.createBuffer(); + bufferCache[programCacheKey] = buffer; + + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + gl.bufferData(gl.ARRAY_BUFFER, vertices.byteLength + texCoords.byteLength, gl.STATIC_DRAW); + } else { + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + } + gl.bufferSubData(gl.ARRAY_BUFFER, 0, vertices); + gl.bufferSubData(gl.ARRAY_BUFFER, texCoordOffset, texCoords); + + var aPosLoc = gl.getAttribLocation(program, "aPos"); + gl.enableVertexAttribArray(aPosLoc); + gl.vertexAttribPointer(aPosLoc, 2, gl.FLOAT, gl.FALSE, 0, 0); + var aTexCoordLoc = gl.getAttribLocation(program, "aTexCoord"); + gl.enableVertexAttribArray(aTexCoordLoc); + gl.vertexAttribPointer(aTexCoordLoc, 2, gl.FLOAT, gl.FALSE, 0, texCoordOffset); + + if (!this._hardcodeConstants) { + var uOutputDimLoc = this.getUniformLocation("uOutputDim"); + gl.uniform3fv(uOutputDimLoc, threadDim); + var uTexSizeLoc = this.getUniformLocation("uTexSize"); + gl.uniform2fv(uTexSizeLoc, texSize); + } + + if (!textureCache[programCacheKey]) { + textureCache[programCacheKey] = []; + } + let textureCount = 0; + for (textureCount=0; textureCount floor(y) - 1.0 ? 0.0 : 1.0); -} - -highp int integerMod(highp int x, highp int y) { - return int(integerMod(float(x), float(y))); -} - -// Here be dragons! -// DO NOT OPTIMIZE THIS CODE -// YOU WILL BREAK SOMETHING ON SOMEBODY\'S MACHINE -// LEAVE IT AS IT IS, LEST YOU WASTE YOUR OWN TIME -const vec2 MAGIC_VEC = vec2(1.0, -256.0); -const vec4 SCALE_FACTOR = vec4(1.0, 256.0, 65536.0, 0.0); -const vec4 SCALE_FACTOR_INV = vec4(1.0, 0.00390625, 0.0000152587890625, 0.0); // 1, 1/256, 1/65536 -highp float decode32(highp vec4 rgba) { - ${ endianness == 'LE' - ? '' - : 'rgba.rgba = rgba.abgr;' - } - rgba *= 255.0; - vec2 gte128; - gte128.x = rgba.b >= 128.0 ? 1.0 : 0.0; - gte128.y = rgba.a >= 128.0 ? 1.0 : 0.0; - float exponent = 2.0 * rgba.a - 127.0 + dot(gte128, MAGIC_VEC); - float res = exp2(round(exponent)); - rgba.b = rgba.b - 128.0 * gte128.x; - res = dot(rgba, SCALE_FACTOR) * exp2(round(exponent-23.0)) + res; - res *= gte128.y * -2.0 + 1.0; - return res; -} - -highp vec4 encode32(highp float f) { - highp float F = abs(f); - highp float sign = f < 0.0 ? 1.0 : 0.0; - highp float exponent = floor(log2(F)); - highp float mantissa = (exp2(-exponent) * F); - // exponent += floor(log2(mantissa)); - vec4 rgba = vec4(F * exp2(23.0-exponent)) * SCALE_FACTOR_INV; - rgba.rg = integerMod(rgba.rg, 256.0); - rgba.b = integerMod(rgba.b, 128.0); - rgba.a = exponent*0.5 + 63.5; - rgba.ba += vec2(integerMod(exponent+127.0, 2.0), sign) * 128.0; - rgba = floor(rgba); - rgba *= 0.003921569; // 1/255 - ${ endianness == 'LE' - ? '' - : 'rgba.rgba = rgba.abgr;' - } - return rgba;', -} -// Dragons end here - -highp float index; -highp vec3 threadId; - -highp vec3 indexTo3D(highp float idx, highp vec3 texDim) { - highp float z = floor(idx / (texDim.x * texDim.y)); - idx -= z * texDim.x * texDim.y; - highp float y = floor(idx / texDim.x); - highp float x = integerMod(idx, texDim.x); - return vec3(x, y, z); -} - -highp float get(highp sampler2D tex, highp vec2 texSize, highp vec3 texDim, highp float z, highp float y, highp float x) { - highp vec3 xyz = vec3(x, y, z); - xyz = floor(xyz + 0.5); - ${ opt.wraparound - ? ' xyz = mod(xyz, texDim);' - : '' - } - highp float index = round(xyz.x + texDim.x * (xyz.y + texDim.y * xyz.z)); - ${ opt.floatTextures ? ' int channel = int(integerMod(index, 4.0));' : '' } - ${ opt.floatTextures ? ' index = float(int(index)/4);' : ''} - highp float w = round(texSize.x); - vec2 st = vec2(integerMod(index, w), float(int(index) / int(w))) + 0.5; - ${ opt.floatTextures ? ' index = float(int(index)/4);' : ''} - highp vec4 texel = texture2D(tex, st / texSize); - ${ opt.floatTextures ? ' if (channel == 0) return texel.r;' : '' } - ${ opt.floatTextures ? ' if (channel == 1) return texel.g;' : '' } - ${ opt.floatTextures ? ' if (channel == 2) return texel.b;' : '' } - ${ opt.floatTextures ? ' if (channel == 3) return texel.a;' : '' } - ${ opt.floatTextures ? '' : ' return decode32(texel);' } -} - -highp float get(highp sampler2D tex, highp vec2 texSize, highp vec3 texDim, highp float y, highp float x) { - return get(tex, texSize, texDim, 0.0, y, x); -} - -highp float get(highp sampler2D tex, highp vec2 texSize, highp vec3 texDim, highp float x) { - return get(tex, texSize, texDim, 0.0, 0.0, x); -} - -const bool outputToColor = ${ opt.graphical? 'true' : 'false' }; -highp vec4 actualColor; -void color(float r, float g, float b, float a) { - actualColor = vec4(r,g,b,a); -} - -void color(float r, float g, float b) { - color(r,g,b,1.0); -} - -highp float kernelResult = 0.0; -paramStr -constantsStr -builder.webGlPrototypeString("kernel", opt) -builder.webGlString("kernel", opt) - -void main(void) { - index = floor(vTexCoord.s * float(uTexSize.x)) + floor(vTexCoord.t * float(uTexSize.y)) * uTexSize.x; - ${ opt.floatOutput ? 'index *= 4.0;' : '' } - threadId = indexTo3D(index, uOutputDim); - kernel(); - if (outputToColor == true) { - gl_FragColor = actualColor; - } else { - ${ opt.floatOutput ? '' : 'gl_FragColor = encode32(kernelResult);' } - ${ opt.floatOutput ? 'gl_FragColor.r = kernelResult;' : '' } - ${ opt.floatOutput ? 'index += 1.0; threadId = indexTo3D(index, uOutputDim); kernel();' : '' } - ${ opt.floatOutput ? 'gl_FragColor.g = kernelResult;' : '' } - ${ opt.floatOutput ? 'index += 1.0; threadId = indexTo3D(index, uOutputDim); kernel();' : '' } - ${ opt.floatOutput ? 'gl_FragColor.b = kernelResult;' : '' } - ${ opt.floatOutput ? 'index += 1.0; threadId = indexTo3D(index, uOutputDim); kernel();' : '' } - ${ opt.floatOutput ? 'gl_FragColor.a = kernelResult;' : '' } - } -}`; - - const vertShader = gl.createShader(gl.VERTEX_SHADER); - const fragShader = gl.createShader(gl.FRAGMENT_SHADER); - - gl.shaderSource(vertShader, vertShaderSrc); - gl.shaderSource(fragShader, fragShaderSrc); - - gl.compileShader(vertShader); - gl.compileShader(fragShader); - - if (!gl.getShaderParameter(vertShader, gl.COMPILE_STATUS)) { - console.log(vertShaderSrc); - console.error("An error occurred compiling the shaders: " + gl.getShaderInfoLog(vertShader)); - throw "Error compiling vertex shader"; - } - if (!gl.getShaderParameter(fragShader, gl.COMPILE_STATUS)) { - console.log(fragShaderSrc); - console.error("An error occurred compiling the shaders: " + gl.getShaderInfoLog(fragShader)); - throw "Error compiling fragment shader"; - } - - if (opt.debug) { - console.log('Options:'); - console.dir(opt); - console.log('GLSL Shader Output:'); - console.log(fragShaderSrc); - } - - const program = this.program = gl.createProgram(); - gl.attachShader(program, vertShader); - gl.attachShader(program, fragShader); - gl.linkProgram(program); - - programCache[programCacheKey] = program; - programUniformLocationCache[programCacheKey] = []; - } - - gl.useProgram(program); - - const texCoordOffset = vertices.byteLength; - let buffer = bufferCache[programCacheKey]; - if (!buffer) { - buffer = gl.createBuffer(); - bufferCache[programCacheKey] = buffer; - - gl.bindBuffer(gl.ARRAY_BUFFER, buffer); - gl.bufferData(gl.ARRAY_BUFFER, vertices.byteLength + texCoords.byteLength, gl.STATIC_DRAW); - } else { - gl.bindBuffer(gl.ARRAY_BUFFER, buffer); - } - gl.bufferSubData(gl.ARRAY_BUFFER, 0, vertices); - gl.bufferSubData(gl.ARRAY_BUFFER, texCoordOffset, texCoords); - - var aPosLoc = gl.getAttribLocation(program, "aPos"); - gl.enableVertexAttribArray(aPosLoc); - gl.vertexAttribPointer(aPosLoc, 2, gl.FLOAT, gl.FALSE, 0, 0); - var aTexCoordLoc = gl.getAttribLocation(program, "aTexCoord"); - gl.enableVertexAttribArray(aTexCoordLoc); - gl.vertexAttribPointer(aTexCoordLoc, 2, gl.FLOAT, gl.FALSE, 0, texCoordOffset); - - if (!opt.hardcodeConstants) { - var uOutputDimLoc = this.getUniformLocation("uOutputDim"); - gl.uniform3fv(uOutputDimLoc, threadDim); - var uTexSizeLoc = this.getUniformLocation("uTexSize"); - gl.uniform2fv(uTexSizeLoc, texSize); - } - - if (!textureCache[programCacheKey]) { - textureCache[programCacheKey] = []; - } - let textureCount = 0; - for (textureCount=0; textureCount { try { - accept( this._kernelSynchronousExecutor.apply(this, args) ); + accept(this._runner.apply(this, args)); } catch (e) { // // Error : throw rejection @@ -143,8 +156,8 @@ export default class GPU { /// Retuns: /// {GPU} returns itself /// - addFunction( jsFunction, paramTypeArray, returnType ) { - this.functionBuilder.addFunction( null, jsFunction, paramTypeArray, returnType ); + addFunction(jsFunction, paramTypeArray, returnType) { + this.functionBuilder.addFunction(null, jsFunction, paramTypeArray, returnType); return this; } diff --git a/src/jison/ecmascript.jison b/src/jison/ecmascript.jison index 4eec9c30..e448bc29 100644 --- a/src/jison/ecmascript.jison +++ b/src/jison/ecmascript.jison @@ -251,7 +251,8 @@ VariableDeclarationListNoIn } | VariableDeclarationListNoIn "," VariableDeclarationNoIn { - $$ = $1.concat($3); + $1.push.apply($1, $3); + $$ = $1; } ; diff --git a/test/src/internal/functionNode_test.js b/test/src/internal/functionNode_test.js index 69e0c901..e078f992 100644 --- a/test/src/internal/functionNode_test.js +++ b/test/src/internal/functionNode_test.js @@ -19,7 +19,7 @@ QUnit.test( "hello_world: just return magic 42", function( assert ) { assert.notEqual( node.getJS_AST(), null, "AST fetch check" ); assert.equal( - node.getWebglFunctionString().replace(/\s+/g,' '), + node.getFunctionString().replace(/\s+/g,' '), "float hello_world() { return 42.0; }", "webgl function conversion check" ); @@ -46,7 +46,7 @@ QUnit.test( "hello_inner: call a funciton inside a function", function( assert ) assert.notEqual( node.getJS_AST(), null, "AST fetch check" ); assert.equal( - node.getWebglFunctionString().replace(/\s+/g,' '), + node.getFunctionString().replace(/\s+/g,' '), "float hello_inner() { return inner(); }", "webgl function conversion check" ); @@ -69,7 +69,7 @@ QUnit.test( "Math.round implementation: A function with arguments", function( as assert.notEqual( node.getJS_AST(), null, "AST fetch check" ); assert.equal( - node.getWebglFunctionString().replace(/\s+/g,' ').replace(/user_/g,''), + node.getFunctionString().replace(/\s+/g,' ').replace(/user_/g,''), "float round(float a) { return floor((a+0.5)); }", "webgl function conversion check" ); @@ -92,7 +92,7 @@ QUnit.test( "Two arguments test", function( assert ) { assert.notEqual( node.getJS_AST(), null, "AST fetch check" ); assert.equal( - node.getWebglFunctionString().replace(/\s+/g,' ').replace(/user_/g,''), + node.getFunctionString().replace(/\s+/g,' ').replace(/user_/g,''), "float add_together(float a, float b) { return (a+b); }", "webgl function conversion check" );