This method calls a helper method *renderOutput* to return the result.
\r\n * @returns {Float32Array|Float32Array[]|Float32Array[][]|void} Result The final output of the program, as float, and as Textures for reuse.\r\n * @abstract\r\n */\r\n run() {\r\n throw new Error(`\"run\" not defined on ${ this.constructor.name }`)\r\n }\r\n\r\n /**\r\n * @abstract\r\n * @return {Object}\r\n */\r\n initCanvas() {\r\n throw new Error(`\"initCanvas\" not defined on ${ this.constructor.name }`);\r\n }\r\n\r\n /**\r\n * @abstract\r\n * @return {Object}\r\n */\r\n initContext() {\r\n throw new Error(`\"initContext\" not defined on ${ this.constructor.name }`);\r\n }\r\n\r\n /**\r\n * @param {IFunctionSettings} settings\r\n * @return {Object};\r\n * @abstract\r\n */\r\n initPlugins(settings) {\r\n throw new Error(`\"initPlugins\" not defined on ${ this.constructor.name }`);\r\n }\r\n\r\n /**\r\n * @desc Setup the parameter types for the parameters\r\n * supplied to the Kernel function\r\n *\r\n * @param {IArguments} args - The actual parameters sent to the Kernel\r\n */\r\n setupArguments(args) {\r\n this.kernelArguments = [];\r\n if (!this.argumentTypes) {\r\n if (!this.argumentTypes) {\r\n this.argumentTypes = [];\r\n for (let i = 0; i < args.length; i++) {\r\n const argType = getVariableType(args[i], this.strictIntegers);\r\n const type = argType === 'Integer' ? 'Number' : argType;\r\n this.argumentTypes.push(type);\r\n this.kernelArguments.push({\r\n type\r\n });\r\n }\r\n }\r\n } else {\r\n for (let i = 0; i < this.argumentTypes.length; i++) {\r\n this.kernelArguments.push({\r\n type: this.argumentTypes[i]\r\n });\r\n }\r\n }\r\n\r\n // setup sizes\r\n this.argumentSizes = new Array(args.length);\r\n this.argumentBitRatios = new Int32Array(args.length);\r\n\r\n for (let i = 0; i < args.length; i++) {\r\n const arg = args[i];\r\n this.argumentSizes[i] = arg.constructor === Input ? arg.size : null;\r\n this.argumentBitRatios[i] = this.getBitRatio(arg);\r\n }\r\n\r\n if (this.argumentNames.length !== args.length) {\r\n throw new Error(`arguments are miss-aligned`);\r\n }\r\n }\r\n\r\n /**\r\n * Setup constants\r\n */\r\n setupConstants() {\r\n this.kernelConstants = [];\r\n let needsConstantTypes = this.constantTypes === null;\r\n if (needsConstantTypes) {\r\n this.constantTypes = {};\r\n }\r\n this.constantBitRatios = {};\r\n if (this.constants) {\r\n for (let name in this.constants) {\r\n if (needsConstantTypes) {\r\n const type = getVariableType(this.constants[name], this.strictIntegers);\r\n this.constantTypes[name] = type;\r\n this.kernelConstants.push({\r\n name,\r\n type\r\n });\r\n } else {\r\n this.kernelConstants.push({\r\n name,\r\n type: this.constantTypes[name]\r\n });\r\n }\r\n this.constantBitRatios[name] = this.getBitRatio(this.constants[name]);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n *\r\n * @param flag\r\n * @return {Kernel}\r\n */\r\n setOptimizeFloatMemory(flag) {\r\n this.optimizeFloatMemory = flag;\r\n return this;\r\n }\r\n\r\n /**\r\n * @desc Set output dimensions of the kernel function\r\n * @param {Array|Object} output - The output array to set the kernel output size to\r\n */\r\n setOutput(output) {\r\n if (output.hasOwnProperty('x')) {\r\n if (output.hasOwnProperty('y')) {\r\n if (output.hasOwnProperty('z')) {\r\n this.output = [output.x, output.y, output.z];\r\n } else {\r\n this.output = [output.x, output.y];\r\n }\r\n } else {\r\n this.output = [output.x];\r\n }\r\n } else {\r\n this.output = output;\r\n }\r\n return this;\r\n }\r\n\r\n /**\r\n * @desc Toggle debug mode\r\n * @param {Boolean} flag - true to enable debug\r\n */\r\n setDebug(flag) {\r\n this.debug = flag;\r\n return this;\r\n }\r\n\r\n /**\r\n * @desc Toggle graphical output mode\r\n * @param {Boolean} flag - true to enable graphical output\r\n */\r\n setGraphical(flag) {\r\n this.graphical = flag;\r\n this.precision = 'unsigned';\r\n return this;\r\n }\r\n\r\n /**\r\n * @desc Set the maximum number of loop iterations\r\n * @param {number} max - iterations count\r\n *\r\n */\r\n setLoopMaxIterations(max) {\r\n this.loopMaxIterations = max;\r\n return this;\r\n }\r\n\r\n /**\r\n * @desc Set Constants\r\n */\r\n setConstants(constants) {\r\n this.constants = constants;\r\n return this;\r\n }\r\n\r\n /**\r\n *\r\n * @param [IKernelValueTypes] constantTypes\r\n * @return {Kernel}\r\n */\r\n setConstantTypes(constantTypes) {\r\n this.constantTypes = constantTypes;\r\n return this;\r\n }\r\n\r\n /**\r\n *\r\n * @param {IFunction[]|KernelFunction[]} functions\r\n * @return {Kernel}\r\n */\r\n setFunctions(functions) {\r\n if (typeof functions[0] === 'function') {\r\n this.functions = functions.map(source => functionToIFunction(source));\r\n } else {\r\n this.functions = functions;\r\n }\r\n return this;\r\n }\r\n\r\n /**\r\n *\r\n * @param {IGPUNativeFunction} nativeFunctions\r\n * @return {Kernel}\r\n */\r\n setNativeFunctions(nativeFunctions) {\r\n this.nativeFunctions = nativeFunctions;\r\n return this;\r\n }\r\n\r\n /**\r\n *\r\n * @param {String} injectedNative\r\n * @return {Kernel}\r\n */\r\n setInjectedNative(injectedNative) {\r\n this.injectedNative = injectedNative;\r\n return this;\r\n }\r\n\r\n /**\r\n * Set writing to texture on/off\r\n * @param flag\r\n * @return {Kernel}\r\n */\r\n setPipeline(flag) {\r\n this.pipeline = flag;\r\n return this;\r\n }\r\n\r\n /**\r\n * Set precision to 'unsigned' or 'single'\r\n * @param {String} flag 'unsigned' or 'single'\r\n * @return {Kernel}\r\n */\r\n setPrecision(flag) {\r\n this.precision = flag;\r\n return this;\r\n }\r\n\r\n /**\r\n * @param flag\r\n * @return {Kernel}\r\n * @deprecated\r\n */\r\n setOutputToTexture(flag) {\r\n warnDeprecated('method', 'setOutputToTexture', 'setPipeline');\r\n this.pipeline = flag;\r\n return this;\r\n }\r\n\r\n /**\r\n * Set to immutable\r\n * @param flag\r\n * @return {Kernel}\r\n */\r\n setImmutable(flag) {\r\n this.immutable = flag;\r\n return this;\r\n }\r\n\r\n /**\r\n * @desc Bind the canvas to kernel\r\n * @param {Object} canvas\r\n */\r\n setCanvas(canvas) {\r\n this.canvas = canvas;\r\n return this;\r\n }\r\n\r\n /**\r\n * @param {Boolean} flag\r\n * @return {Kernel}\r\n */\r\n setStrictIntegers(flag) {\r\n this.strictIntegers = flag;\r\n return this;\r\n }\r\n\r\n /**\r\n *\r\n * @param flag\r\n * @return {Kernel}\r\n */\r\n setDynamicOutput(flag) {\r\n this.dynamicOutput = flag;\r\n return this;\r\n }\r\n\r\n /**\r\n * @deprecated\r\n * @param flag\r\n * @return {Kernel}\r\n */\r\n setHardcodeConstants(flag) {\r\n warnDeprecated('method', 'setHardcodeConstants');\r\n this.setDynamicOutput(flag);\r\n this.setDynamicArguments(flag);\r\n return this;\r\n }\r\n\r\n /**\r\n *\r\n * @param flag\r\n * @return {Kernel}\r\n */\r\n setDynamicArguments(flag) {\r\n this.dynamicArguments = flag;\r\n return this;\r\n }\r\n\r\n /**\r\n * @param {Boolean} flag\r\n * @return {Kernel}\r\n */\r\n setUseLegacyEncoder(flag) {\r\n this.useLegacyEncoder = flag;\r\n return this;\r\n }\r\n\r\n /**\r\n *\r\n * @param {Boolean} flag\r\n * @return {Kernel}\r\n */\r\n setWarnVarUsage(flag) {\r\n this.warnVarUsage = flag;\r\n return this;\r\n }\r\n\r\n /**\r\n * @deprecated\r\n * @returns {Object}\r\n */\r\n getCanvas() {\r\n warnDeprecated('method', 'getCanvas');\r\n return this.canvas;\r\n }\r\n\r\n /**\r\n * @deprecated\r\n * @returns {Object}\r\n */\r\n getWebGl() {\r\n warnDeprecated('method', 'getWebGl');\r\n return this.context;\r\n }\r\n\r\n /**\r\n * @desc Bind the webGL instance to kernel\r\n * @param {WebGLRenderingContext} context - webGl instance to bind\r\n */\r\n setContext(context) {\r\n this.context = context;\r\n return this;\r\n }\r\n\r\n /**\r\n *\r\n * @param [IKernelValueTypes|GPUVariableType[]] argumentTypes\r\n * @return {Kernel}\r\n */\r\n setArgumentTypes(argumentTypes) {\r\n if (Array.isArray(argumentTypes)) {\r\n this.argumentTypes = argumentTypes;\r\n } else {\r\n this.argumentTypes = [];\r\n for (const p in argumentTypes) {\r\n const argumentIndex = this.argumentNames.indexOf(p);\r\n if (argumentIndex === -1) throw new Error(`unable to find argument ${ p }`);\r\n this.argumentTypes[argumentIndex] = argumentTypes[p];\r\n }\r\n }\r\n return this;\r\n }\r\n\r\n /**\r\n *\r\n * @param [Tactic] tactic\r\n * @return {Kernel}\r\n */\r\n setTactic(tactic) {\r\n this.tactic = tactic;\r\n return this;\r\n }\r\n\r\n requestFallback(args) {\r\n if (!this.onRequestFallback) {\r\n throw new Error(`\"onRequestFallback\" not defined on ${ this.constructor.name }`);\r\n }\r\n this.fallbackRequested = true;\r\n return this.onRequestFallback(args);\r\n }\r\n\r\n /**\r\n * @desc Validate settings\r\n * @abstract\r\n */\r\n validateSettings() {\r\n throw new Error(`\"validateSettings\" not defined on ${ this.constructor.name }`);\r\n }\r\n\r\n /**\r\n * @desc Add a sub kernel to the root kernel instance.\r\n * This is what `createKernelMap` uses.\r\n *\r\n * @param {ISubKernel} subKernel - function (as a String) of the subKernel to add\r\n */\r\n addSubKernel(subKernel) {\r\n if (this.subKernels === null) {\r\n this.subKernels = [];\r\n }\r\n if (!subKernel.source) throw new Error('subKernel missing \"source\" property');\r\n if (!subKernel.property && isNaN(subKernel.property)) throw new Error('subKernel missing \"property\" property');\r\n if (!subKernel.name) throw new Error('subKernel missing \"name\" property');\r\n this.subKernels.push(subKernel);\r\n return this;\r\n }\r\n\r\n /**\r\n * @desc Destroys all memory associated with this kernel\r\n * @param {Boolean} [removeCanvasReferences] remove any associated canvas references\r\n */\r\n destroy(removeCanvasReferences) {\r\n throw new Error(`\"destroy\" called on ${ this.constructor.name }`);\r\n }\r\n\r\n /**\r\n * bit storage ratio of source to target 'buffer', i.e. if 8bit array -> 32bit tex = 4\r\n * @param value\r\n * @returns {number}\r\n */\r\n getBitRatio(value) {\r\n if (this.precision === 'single') {\r\n // 8 and 16 are upconverted to float32\r\n return 4;\r\n } else if (Array.isArray(value[0])) {\r\n return this.getBitRatio(value[0]);\r\n } else if (value.constructor === Input) {\r\n return this.getBitRatio(value.value);\r\n }\r\n switch (value.constructor) {\r\n case Uint8ClampedArray:\r\n case Uint8Array:\r\n case Int8Array:\r\n return 1;\r\n case Uint16Array:\r\n case Int16Array:\r\n return 2;\r\n case Float32Array:\r\n case Int32Array:\r\n default:\r\n return 4;\r\n }\r\n }\r\n\r\n /**\r\n * @returns {number[]}\r\n */\r\n getPixels() {\r\n throw new Error(`\"getPixels\" called on ${ this.constructor.name }`);\r\n }\r\n\r\n checkOutput() {\r\n if (!this.output || !isArray(this.output)) throw new Error('kernel.output not an array');\r\n if (this.output.length < 1) throw new Error('kernel.output is empty, needs at least 1 value');\r\n for (let i = 0; i < this.output.length; i++) {\r\n if (isNaN(this.output[i]) || this.output[i] < 1) {\r\n throw new Error(`${ this.constructor.name }.output[${ i }] incorrectly defined as \\`${ this.output[i] }\\`, needs to be numeric, and greater than 0`);\r\n }\r\n }\r\n }\r\n\r\n toJSON() {\r\n const settings = {\r\n output: this.output,\r\n threadDim: this.threadDim,\r\n pipeline: this.pipeline,\r\n argumentNames: this.argumentNames,\r\n argumentsTypes: this.argumentTypes,\r\n constants: this.constants,\r\n pluginNames: this.plugins ? this.plugins.map(plugin => plugin.name) : null,\r\n returnType: this.returnType,\r\n };\r\n return {\r\n settings\r\n };\r\n }\r\n}\r\n","/**\r\n * @desc This handles all the raw state, converted state, etc. of a single function.\r\n * [INTERNAL] A collection of functionNodes.\r\n * @class\r\n */\r\nexport class FunctionBuilder {\r\n /**\r\n *\r\n * @param {Kernel} kernel\r\n * @param {FunctionNode} FunctionNode\r\n * @param {object} [extraNodeOptions]\r\n * @returns {FunctionBuilder}\r\n * @static\r\n */\r\n static fromKernel(kernel, FunctionNode, extraNodeOptions) {\r\n const {\r\n kernelArguments,\r\n kernelConstants,\r\n argumentNames,\r\n argumentSizes,\r\n argumentBitRatios,\r\n constants,\r\n constantBitRatios,\r\n debug,\r\n loopMaxIterations,\r\n nativeFunctions,\r\n output,\r\n optimizeFloatMemory,\r\n precision,\r\n plugins,\r\n source,\r\n subKernels,\r\n functions,\r\n leadingReturnStatement,\r\n followingReturnStatement,\r\n dynamicArguments,\r\n dynamicOutput,\r\n warnVarUsage,\r\n } = kernel;\r\n\r\n const argumentTypes = new Array(kernelArguments.length);\r\n const constantTypes = {};\r\n\r\n for (let i = 0; i < kernelArguments.length; i++) {\r\n argumentTypes[i] = kernelArguments[i].type;\r\n }\r\n\r\n for (let i = 0; i < kernelConstants.length; i++) {\r\n const kernelConstant = kernelConstants[i]\r\n constantTypes[kernelConstant.name] = kernelConstant.type;\r\n }\r\n\r\n const needsArgumentType = (functionName, index) => {\r\n return functionBuilder.needsArgumentType(functionName, index);\r\n };\r\n\r\n const assignArgumentType = (functionName, index, type) => {\r\n functionBuilder.assignArgumentType(functionName, index, type);\r\n };\r\n\r\n const lookupReturnType = (functionName, ast, requestingNode) => {\r\n return functionBuilder.lookupReturnType(functionName, ast, requestingNode);\r\n };\r\n\r\n const lookupFunctionArgumentTypes = (functionName) => {\r\n return functionBuilder.lookupFunctionArgumentTypes(functionName);\r\n };\r\n\r\n const lookupFunctionArgumentName = (functionName, argumentIndex) => {\r\n return functionBuilder.lookupFunctionArgumentName(functionName, argumentIndex);\r\n };\r\n\r\n const lookupFunctionArgumentBitRatio = (functionName, argumentName) => {\r\n return functionBuilder.lookupFunctionArgumentBitRatio(functionName, argumentName);\r\n };\r\n\r\n const triggerImplyArgumentType = (functionName, i, argumentType, requestingNode) => {\r\n functionBuilder.assignArgumentType(functionName, i, argumentType, requestingNode);\r\n };\r\n\r\n const triggerTrackArgumentSynonym = (functionName, argumentName, calleeFunctionName, argumentIndex) => {\r\n functionBuilder.trackArgumentSynonym(functionName, argumentName, calleeFunctionName, argumentIndex);\r\n };\r\n\r\n const lookupArgumentSynonym = (originFunctionName, functionName, argumentName) => {\r\n return functionBuilder.lookupArgumentSynonym(originFunctionName, functionName, argumentName);\r\n };\r\n\r\n const onFunctionCall = (functionName, calleeFunctionName, args) => {\r\n functionBuilder.trackFunctionCall(functionName, calleeFunctionName, args);\r\n };\r\n\r\n const onNestedFunction = (ast, returnType) => {\r\n const argumentNames = [];\r\n for (let i = 0; i < ast.params.length; i++) {\r\n argumentNames.push(ast.params[i].name);\r\n }\r\n const nestedFunction = new FunctionNode(null, Object.assign({}, nodeOptions, {\r\n returnType: null,\r\n ast,\r\n name: ast.id.name,\r\n argumentNames,\r\n lookupReturnType,\r\n lookupFunctionArgumentTypes,\r\n lookupFunctionArgumentName,\r\n lookupFunctionArgumentBitRatio,\r\n needsArgumentType,\r\n assignArgumentType,\r\n triggerImplyArgumentType,\r\n triggerTrackArgumentSynonym,\r\n lookupArgumentSynonym,\r\n onFunctionCall,\r\n warnVarUsage,\r\n }));\r\n nestedFunction.traceFunctionAST(ast);\r\n functionBuilder.addFunctionNode(nestedFunction);\r\n };\r\n\r\n const nodeOptions = Object.assign({\r\n isRootKernel: false,\r\n onNestedFunction,\r\n lookupReturnType,\r\n lookupFunctionArgumentTypes,\r\n lookupFunctionArgumentName,\r\n lookupFunctionArgumentBitRatio,\r\n needsArgumentType,\r\n assignArgumentType,\r\n triggerImplyArgumentType,\r\n triggerTrackArgumentSynonym,\r\n lookupArgumentSynonym,\r\n onFunctionCall,\r\n optimizeFloatMemory,\r\n precision,\r\n constants,\r\n constantTypes,\r\n constantBitRatios,\r\n debug,\r\n loopMaxIterations,\r\n output,\r\n plugins,\r\n dynamicArguments,\r\n dynamicOutput,\r\n }, extraNodeOptions || {});\r\n\r\n const rootNodeOptions = Object.assign({}, nodeOptions, {\r\n isRootKernel: true,\r\n name: 'kernel',\r\n argumentNames,\r\n argumentTypes,\r\n argumentSizes,\r\n argumentBitRatios,\r\n leadingReturnStatement,\r\n followingReturnStatement,\r\n });\r\n\r\n if (typeof source === 'object' && source.functionNodes) {\r\n return new FunctionBuilder().fromJSON(source.functionNodes, FunctionNode);\r\n }\r\n\r\n const rootNode = new FunctionNode(source, rootNodeOptions);\r\n\r\n let functionNodes = null;\r\n if (functions) {\r\n functionNodes = functions.map((fn) => new FunctionNode(fn.source, {\r\n returnType: fn.returnType,\r\n argumentTypes: fn.argumentTypes,\r\n output,\r\n plugins,\r\n constants,\r\n constantTypes,\r\n constantBitRatios,\r\n optimizeFloatMemory,\r\n precision,\r\n lookupReturnType,\r\n lookupFunctionArgumentTypes,\r\n lookupFunctionArgumentName,\r\n lookupFunctionArgumentBitRatio,\r\n needsArgumentType,\r\n assignArgumentType,\r\n triggerImplyArgumentType,\r\n triggerTrackArgumentSynonym,\r\n lookupArgumentSynonym,\r\n onFunctionCall,\r\n }));\r\n }\r\n\r\n let subKernelNodes = null;\r\n if (subKernels) {\r\n subKernelNodes = subKernels.map((subKernel) => {\r\n const { name, source } = subKernel;\r\n return new FunctionNode(source, Object.assign({}, nodeOptions, {\r\n name,\r\n isSubKernel: true,\r\n isRootKernel: false,\r\n }));\r\n });\r\n }\r\n\r\n const functionBuilder = new FunctionBuilder({\r\n kernel,\r\n rootNode,\r\n functionNodes,\r\n nativeFunctions,\r\n subKernelNodes\r\n });\r\n\r\n return functionBuilder;\r\n }\r\n\r\n /**\r\n *\r\n * @param {IFunctionBuilderSettings} [settings]\r\n */\r\n constructor(settings) {\r\n settings = settings || {};\r\n this.kernel = settings.kernel;\r\n this.rootNode = settings.rootNode;\r\n this.functionNodes = settings.functionNodes || [];\r\n this.subKernelNodes = settings.subKernelNodes || [];\r\n this.nativeFunctions = settings.nativeFunctions || [];\r\n this.functionMap = {};\r\n this.nativeFunctionNames = [];\r\n this.lookupChain = [];\r\n this.argumentChain = [];\r\n this.functionNodeDependencies = {};\r\n this.functionCalls = {};\r\n\r\n if (this.rootNode) {\r\n this.functionMap['kernel'] = this.rootNode;\r\n }\r\n\r\n if (this.functionNodes) {\r\n for (let i = 0; i < this.functionNodes.length; i++) {\r\n this.functionMap[this.functionNodes[i].name] = this.functionNodes[i];\r\n }\r\n }\r\n\r\n if (this.subKernelNodes) {\r\n for (let i = 0; i < this.subKernelNodes.length; i++) {\r\n this.functionMap[this.subKernelNodes[i].name] = this.subKernelNodes[i];\r\n }\r\n }\r\n\r\n if (this.nativeFunctions) {\r\n for (let i = 0; i < this.nativeFunctions.length; i++) {\r\n const nativeFunction = this.nativeFunctions[i];\r\n this.nativeFunctionNames.push(nativeFunction.name);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * @desc Add the function node directly\r\n *\r\n * @param {FunctionNode} functionNode - functionNode to add\r\n *\r\n */\r\n addFunctionNode(functionNode) {\r\n if (!functionNode.name) throw new Error('functionNode.name needs set');\r\n this.functionMap[functionNode.name] = functionNode;\r\n if (functionNode.isRootKernel) {\r\n this.rootNode = functionNode;\r\n }\r\n }\r\n\r\n /**\r\n * @desc Trace all the depending functions being called, from a single function\r\n *\r\n * This allow for 'unneeded' functions to be automatically optimized out.\r\n * Note that the 0-index, is the starting function trace.\r\n *\r\n * @param {String} functionName - Function name to trace from, default to 'kernel'\r\n * @param {String[]} [retList] - Returning list of function names that is traced. Including itself.\r\n *\r\n * @returns {String[]} Returning list of function names that is traced. Including itself.\r\n */\r\n traceFunctionCalls(functionName, retList) {\r\n functionName = functionName || 'kernel';\r\n retList = retList || [];\r\n\r\n if (this.nativeFunctionNames.indexOf(functionName) > -1) {\r\n if (retList.indexOf(functionName) === -1) {\r\n retList.push(functionName);\r\n }\r\n return retList;\r\n }\r\n\r\n const functionNode = this.functionMap[functionName];\r\n if (functionNode) {\r\n // Check if function already exists\r\n const functionIndex = retList.indexOf(functionName);\r\n if (functionIndex === -1) {\r\n retList.push(functionName);\r\n functionNode.toString(); //ensure JS trace is done\r\n for (let i = 0; i < functionNode.calledFunctions.length; ++i) {\r\n this.traceFunctionCalls(functionNode.calledFunctions[i], retList);\r\n }\r\n } else {\r\n /**\r\n * https://github.com/gpujs/gpu.js/issues/207\r\n * if dependent function is already in the list, because a function depends on it, and because it has\r\n * already been traced, we know that we must move the dependent function to the end of the the retList.\r\n * */\r\n const dependantFunctionName = retList.splice(functionIndex, 1)[0];\r\n retList.push(dependantFunctionName);\r\n }\r\n }\r\n\r\n return retList;\r\n }\r\n\r\n /**\r\n * @desc Return the string for a function\r\n * @param {String} functionName - Function name to trace from. If null, it returns the WHOLE builder stack\r\n * @returns {String} The full string, of all the various functions. Trace optimized if functionName given\r\n */\r\n getPrototypeString(functionName) {\r\n return this.getPrototypes(functionName).join('\\n');\r\n }\r\n\r\n /**\r\n * @desc Return the string for a function\r\n * @param {String} [functionName] - Function name to trace from. If null, it returns the WHOLE builder stack\r\n * @returns {Array} The full string, of all the various functions. Trace optimized if functionName given\r\n */\r\n getPrototypes(functionName) {\r\n if (this.rootNode) {\r\n this.rootNode.toString();\r\n }\r\n if (functionName) {\r\n return this.getPrototypesFromFunctionNames(this.traceFunctionCalls(functionName, []).reverse());\r\n }\r\n return this.getPrototypesFromFunctionNames(Object.keys(this.functionMap));\r\n }\r\n\r\n /**\r\n * @desc Get string from function names\r\n * @param {String[]} functionList - List of function to build string\r\n * @returns {String} The string, of all the various functions. Trace optimized if functionName given\r\n */\r\n getStringFromFunctionNames(functionList) {\r\n const ret = [];\r\n for (let i = 0; i < functionList.length; ++i) {\r\n const node = this.functionMap[functionList[i]];\r\n if (node) {\r\n ret.push(this.functionMap[functionList[i]].toString());\r\n }\r\n }\r\n return ret.join('\\n');\r\n }\r\n\r\n /**\r\n * @desc Return string of all functions converted\r\n * @param {String[]} functionList - List of function names to build the string.\r\n * @returns {Array} Prototypes of all functions converted\r\n */\r\n getPrototypesFromFunctionNames(functionList) {\r\n const ret = [];\r\n for (let i = 0; i < functionList.length; ++i) {\r\n const functionName = functionList[i];\r\n const functionIndex = this.nativeFunctionNames.indexOf(functionName);\r\n if (functionIndex > -1) {\r\n ret.push(this.nativeFunctions[functionIndex].source);\r\n continue;\r\n }\r\n const node = this.functionMap[functionName];\r\n if (node) {\r\n ret.push(node.toString());\r\n }\r\n }\r\n return ret;\r\n }\r\n\r\n toJSON() {\r\n return this.traceFunctionCalls(this.rootNode.name).reverse().map(name => {\r\n const nativeIndex = this.nativeFunctions.indexOf(name);\r\n if (nativeIndex > -1) {\r\n return {\r\n name,\r\n source: this.nativeFunctions[nativeIndex].source\r\n };\r\n } else if (this.functionMap[name]) {\r\n return this.functionMap[name].toJSON();\r\n } else {\r\n throw new Error(`function ${ name } not found`);\r\n }\r\n });\r\n }\r\n\r\n fromJSON(jsonFunctionNodes, FunctionNode) {\r\n this.functionMap = {};\r\n for (let i = 0; i < jsonFunctionNodes.length; i++) {\r\n const jsonFunctionNode = jsonFunctionNodes[i];\r\n this.functionMap[jsonFunctionNode.settings.name] = new FunctionNode(jsonFunctionNode.ast, jsonFunctionNode.settings);\r\n }\r\n return this;\r\n }\r\n\r\n /**\r\n * @desc Get string for a particular function name\r\n * @param {String} functionName - Function name to trace from. If null, it returns the WHOLE builder stack\r\n * @returns {String} settings - The string, of all the various functions. Trace optimized if functionName given\r\n */\r\n getString(functionName) {\r\n if (functionName) {\r\n return this.getStringFromFunctionNames(this.traceFunctionCalls(functionName).reverse());\r\n }\r\n return this.getStringFromFunctionNames(Object.keys(this.functionMap));\r\n }\r\n\r\n lookupReturnType(functionName, ast, requestingNode) {\r\n if (ast.type !== 'CallExpression') {\r\n throw new Error(`expected ast type of \"CallExpression\", but is ${ ast.type }`);\r\n }\r\n if (this._isNativeFunction(functionName)) {\r\n return this._lookupNativeFunctionReturnType(functionName);\r\n } else if (this._isFunction(functionName)) {\r\n const node = this._getFunction(functionName);\r\n if (node.returnType) {\r\n return node.returnType;\r\n } else {\r\n for (let i = 0; i < this.lookupChain.length; i++) {\r\n // detect circlical logic\r\n if (this.lookupChain[i].ast === ast) {\r\n // detect if arguments have not resolved, preventing a return type\r\n // if so, go ahead and resolve them, so we can resolve the return type\r\n if (node.argumentTypes.length === 0 && ast.arguments.length > 0) {\r\n const args = ast.arguments;\r\n for (let j = 0; j < args.length; j++) {\r\n this.lookupChain.push({\r\n name: requestingNode.name,\r\n ast: args[i],\r\n requestingNode\r\n });\r\n node.argumentTypes[j] = requestingNode.getType(args[j]);\r\n this.lookupChain.pop();\r\n }\r\n return node.returnType = node.getType(node.getJsAST());\r\n }\r\n\r\n throw new Error('circlical logic detected!');\r\n }\r\n }\r\n // get ready for a ride!\r\n this.lookupChain.push({\r\n name: requestingNode.name,\r\n ast,\r\n requestingNode\r\n });\r\n const type = node.getType(node.getJsAST());\r\n this.lookupChain.pop();\r\n return node.returnType = type;\r\n }\r\n }\r\n\r\n // function not found, maybe native?\r\n return null;\r\n\r\n /**\r\n * first iteration\r\n * kernel.outputs = Array\r\n * kernel.targets = Array\r\n * kernel.returns = null\r\n * kernel.calls.calcErrorOutput = [kernel.output, kernel.targets]\r\n * kernel.calls.calcDeltas = [calcErrorOutput.returns, kernel.output]\r\n * calcErrorOutput.output = null\r\n * calcErrorOutput.targets = null\r\n * calcErrorOutput.returns = null\r\n * calcDeltasSigmoid.error = null\r\n * calcDeltasSigmoid.output = Number\r\n * calcDeltasSigmoid.returns = null\r\n *\r\n * resolvable are:\r\n * calcErrorOutput.output\r\n * calcErrorOutput.targets\r\n * calcErrorOutput.returns\r\n *\r\n * second iteration\r\n * kernel.outputs = Array\r\n * kernel.targets = Array\r\n * kernel.returns = null\r\n * kernel.calls.calcErrorOutput = [kernel.output, kernel.targets]\r\n * kernel.calls.calcDeltas = [calcErrorOutput.returns, kernel.output]\r\n * calcErrorOutput.output = Number\r\n * calcErrorOutput.targets = Array\r\n * calcErrorOutput.returns = Number\r\n * calcDeltasSigmoid.error = null\r\n * calcDeltasSigmoid.output = Number\r\n * calcDeltasSigmoid.returns = null\r\n *\r\n * resolvable are:\r\n * calcDeltasSigmoid.error\r\n * calcDeltasSigmoid.returns\r\n * kernel.returns\r\n *\r\n * third iteration\r\n * kernel.outputs = Array\r\n * kernel.targets = Array\r\n * kernel.returns = Number\r\n * kernel.calls.calcErrorOutput = [kernel.output, kernel.targets]\r\n * kernel.calls.calcDeltas = [calcErrorOutput.returns, kernel.output]\r\n * calcErrorOutput.output = Number\r\n * calcErrorOutput.targets = Array\r\n * calcErrorOutput.returns = Number\r\n * calcDeltasSigmoid.error = Number\r\n * calcDeltasSigmoid.output = Number\r\n * calcDeltasSigmoid.returns = Number\r\n *\r\n *\r\n */\r\n }\r\n\r\n _getFunction(functionName) {\r\n if (!this._isFunction(functionName)) {\r\n new Error(`Function ${functionName} not found`);\r\n }\r\n return this.functionMap[functionName];\r\n }\r\n\r\n _isFunction(functionName) {\r\n return Boolean(this.functionMap[functionName]);\r\n }\r\n\r\n _getNativeFunction(functionName) {\r\n for (let i = 0; i < this.nativeFunctions.length; i++) {\r\n if (this.nativeFunctions[i].name === functionName) return this.nativeFunctions[i];\r\n }\r\n return null;\r\n }\r\n\r\n _isNativeFunction(functionName) {\r\n return Boolean(this._getNativeFunction(functionName));\r\n }\r\n\r\n _lookupNativeFunctionReturnType(functionName) {\r\n let nativeFunction = this._getNativeFunction(functionName);\r\n if (nativeFunction) {\r\n return nativeFunction.returnType;\r\n }\r\n throw new Error(`Native function ${ functionName } not found`);\r\n }\r\n\r\n lookupFunctionArgumentTypes(functionName) {\r\n if (this._isNativeFunction(functionName)) {\r\n return this._getNativeFunction(functionName).argumentTypes;\r\n } else if (this._isFunction(functionName)) {\r\n return this._getFunction(functionName).argumentTypes;\r\n }\r\n return null;\r\n }\r\n\r\n lookupFunctionArgumentName(functionName, argumentIndex) {\r\n return this._getFunction(functionName).argumentNames[argumentIndex];\r\n }\r\n\r\n lookupFunctionArgumentBitRatio(functionName, argumentName) {\r\n if (!this._isFunction(functionName)) {\r\n throw new Error('function not found');\r\n }\r\n if (this.rootNode.name === functionName) {\r\n const i = this.rootNode.argumentNames.indexOf(argumentName);\r\n if (i !== -1) {\r\n return this.rootNode.argumentBitRatios[i];\r\n } else {\r\n throw new Error('argument bit ratio not found');\r\n }\r\n } else {\r\n const node = this._getFunction(functionName);\r\n const argumentSynonym = node.argumentSynonym[node.synonymIndex];\r\n if (!argumentSynonym) {\r\n throw new Error('argument synonym not found');\r\n }\r\n return this.lookupFunctionArgumentBitRatio(argumentSynonym.functionName, argumentSynonym.argumentName);\r\n }\r\n }\r\n\r\n needsArgumentType(functionName, i) {\r\n if (!this._isFunction(functionName)) return false;\r\n const fnNode = this._getFunction(functionName);\r\n return !fnNode.argumentTypes[i];\r\n }\r\n\r\n assignArgumentType(functionName, i, argumentType, requestingNode) {\r\n if (!this._isFunction(functionName)) return;\r\n const fnNode = this._getFunction(functionName);\r\n if (!fnNode.argumentTypes[i]) {\r\n fnNode.argumentTypes[i] = argumentType;\r\n }\r\n }\r\n\r\n trackArgumentSynonym(functionName, argumentName, calleeFunctionName, argumentIndex) {\r\n if (!this._isFunction(calleeFunctionName)) return;\r\n const node = this._getFunction(calleeFunctionName);\r\n if (!node.argumentSynonym) {\r\n node.argumentSynonym = {};\r\n }\r\n const calleeArgumentName = node.argumentNames[argumentIndex];\r\n if (!node.argumentSynonym[calleeArgumentName]) {\r\n node.argumentSynonym[calleeArgumentName] = {};\r\n }\r\n node.synonymIndex++;\r\n node.argumentSynonym[node.synonymIndex] = {\r\n functionName,\r\n argumentName,\r\n calleeArgumentName,\r\n calleeFunctionName,\r\n };\r\n }\r\n\r\n lookupArgumentSynonym(originFunctionName, functionName, argumentName) {\r\n if (originFunctionName === functionName) return argumentName;\r\n if (!this._isFunction(functionName)) return null;\r\n const node = this._getFunction(functionName);\r\n const argumentSynonym = node.argumentSynonym[node.synonymUseIndex];\r\n if (!argumentSynonym) return null;\r\n if (argumentSynonym.calleeArgumentName !== argumentName) return null;\r\n node.synonymUseIndex++;\r\n if (originFunctionName !== functionName) {\r\n return this.lookupArgumentSynonym(originFunctionName, argumentSynonym.functionName, argumentSynonym.argumentName);\r\n }\r\n return argumentSynonym.argumentName;\r\n }\r\n\r\n trackFunctionCall(functionName, calleeFunctionName, args) {\r\n if (!this.functionNodeDependencies[functionName]) {\r\n this.functionNodeDependencies[functionName] = new Set();\r\n this.functionCalls[functionName] = [];\r\n }\r\n this.functionNodeDependencies[functionName].add(calleeFunctionName);\r\n this.functionCalls[functionName].push(args);\r\n }\r\n\r\n getKernelResultType() {\r\n return this.rootNode.returnType || this.rootNode.getType(this.rootNode.ast);\r\n }\r\n\r\n getSubKernelResultType(index) {\r\n const subKernelNode = this.subKernelNodes[index];\r\n let called = false;\r\n for (let functionCallIndex = 0; functionCallIndex < this.rootNode.functionCalls.length; functionCallIndex++) {\r\n const functionCall = this.rootNode.functionCalls[functionCallIndex];\r\n if (functionCall.ast.callee.name === subKernelNode.name) {\r\n called = true;\r\n }\r\n }\r\n if (!called) {\r\n throw new Error(`SubKernel ${ subKernelNode.name } never called by kernel`);\r\n }\r\n return subKernelNode.returnType || subKernelNode.getType(subKernelNode.getJsAST());\r\n }\r\n\r\n getReturnTypes() {\r\n const result = {\r\n [this.rootNode.name]: this.rootNode.getType(this.rootNode.ast),\r\n };\r\n const list = this.traceFunctionCalls(this.rootNode.name);\r\n for (let i = 0; i < list.length; i++) {\r\n const functionName = list[i];\r\n const functionNode = this.functionMap[functionName];\r\n result[functionName] = functionNode.getType(functionNode.ast);\r\n }\r\n return result;\r\n }\r\n}\r\n","export class FunctionTracer {\r\n constructor(ast) {\r\n this.runningContexts = [];\r\n this.contexts = [];\r\n this.functionCalls = [];\r\n this.declarations = [];\r\n this.identifiers = [];\r\n this.functions = [];\r\n this.returnStatements = [];\r\n this.inLoopInit = false;\r\n this.scan(ast);\r\n }\r\n\r\n get currentContext() {\r\n return this.runningContexts.length > 0 ? this.runningContexts[this.runningContexts.length - 1] : null;\r\n }\r\n\r\n newContext(run) {\r\n const newContext = Object.assign({}, this.currentContext);\r\n this.contexts.push(newContext);\r\n this.runningContexts.push(newContext);\r\n run();\r\n this.runningContexts.pop();\r\n }\r\n\r\n /**\r\n * Recursively scans AST for declarations and functions, and add them to their respective context\r\n * @param ast\r\n */\r\n scan(ast) {\r\n if (Array.isArray(ast)) {\r\n for (let i = 0; i < ast.length; i++) {\r\n this.scan(ast[i]);\r\n }\r\n return;\r\n }\r\n switch (ast.type) {\r\n case 'Program':\r\n this.scan(ast.body);\r\n break;\r\n case 'BlockStatement':\r\n this.newContext(() => {\r\n this.scan(ast.body);\r\n });\r\n break;\r\n case 'AssignmentExpression':\r\n case 'LogicalExpression':\r\n this.scan(ast.left);\r\n this.scan(ast.right);\r\n break;\r\n case 'BinaryExpression':\r\n this.scan(ast.left);\r\n this.scan(ast.right);\r\n break;\r\n case 'UpdateExpression':\r\n case 'UnaryExpression':\r\n this.scan(ast.argument);\r\n break;\r\n case 'VariableDeclaration':\r\n this.scan(ast.declarations);\r\n break;\r\n case 'VariableDeclarator':\r\n const { currentContext } = this;\r\n const declaration = {\r\n ast: ast,\r\n context: currentContext,\r\n name: ast.id.name,\r\n origin: 'declaration',\r\n forceInteger: this.inLoopInit,\r\n assignable: !this.inLoopInit && !currentContext.hasOwnProperty(ast.id.name),\r\n };\r\n currentContext[ast.id.name] = declaration;\r\n this.declarations.push(declaration);\r\n this.scan(ast.id);\r\n this.scan(ast.init);\r\n break;\r\n case 'FunctionExpression':\r\n case 'FunctionDeclaration':\r\n if (this.runningContexts.length === 0) {\r\n this.scan(ast.body);\r\n } else {\r\n this.functions.push(ast);\r\n }\r\n break;\r\n case 'IfStatement':\r\n this.scan(ast.test);\r\n this.scan(ast.consequent);\r\n if (ast.alternate) this.scan(ast.alternate);\r\n break;\r\n case 'ForStatement':\r\n this.newContext(() => {\r\n this.inLoopInit = true;\r\n this.scan(ast.init);\r\n this.inLoopInit = false;\r\n this.scan(ast.test);\r\n this.scan(ast.update);\r\n this.newContext(() => {\r\n this.scan(ast.body);\r\n });\r\n });\r\n break;\r\n case 'DoWhileStatement':\r\n case 'WhileStatement':\r\n this.newContext(() => {\r\n this.scan(ast.body);\r\n this.scan(ast.test);\r\n });\r\n break;\r\n case 'Identifier':\r\n this.identifiers.push({\r\n context: this.currentContext,\r\n ast,\r\n });\r\n break;\r\n case 'ReturnStatement':\r\n this.returnStatements.push(ast);\r\n this.scan(ast.argument);\r\n break;\r\n case 'MemberExpression':\r\n this.scan(ast.object);\r\n this.scan(ast.property);\r\n break;\r\n case 'ExpressionStatement':\r\n this.scan(ast.expression);\r\n break;\r\n case 'CallExpression':\r\n this.functionCalls.push({\r\n context: this.currentContext,\r\n ast,\r\n });\r\n this.scan(ast.arguments);\r\n break;\r\n case 'ArrayExpression':\r\n this.scan(ast.elements);\r\n break;\r\n case 'ConditionalExpression':\r\n this.scan(ast.test);\r\n this.scan(ast.alternate);\r\n this.scan(ast.consequent);\r\n break;\r\n case 'SwitchStatement':\r\n this.scan(ast.discriminant);\r\n this.scan(ast.cases);\r\n break;\r\n case 'SwitchCase':\r\n this.scan(ast.test);\r\n this.scan(ast.consequent);\r\n break;\r\n case 'ThisExpression':\r\n this.scan(ast.left);\r\n this.scan(ast.right);\r\n break;\r\n case 'Literal':\r\n case 'DebuggerStatement':\r\n case 'EmptyStatement':\r\n case 'BreakStatement':\r\n case 'ContinueStatement':\r\n break;\r\n default:\r\n throw new Error(`unhandled type \"${ast.type}\"`);\r\n }\r\n }\r\n}\r\n","import { parse } from 'acorn';\r\nimport { FunctionTracer } from './function-tracer';\r\nimport {\r\n getArgumentNamesFromString,\r\n getAstString,\r\n getFunctionNameFromString,\r\n isFunctionString,\r\n} from '../common';\r\n\r\n/**\r\n *\r\n * @desc Represents a single function, inside JS, webGL, or openGL.\r\n *This handles all the raw state, converted state, etc. Of a single function.
\r\n */\r\nexport class FunctionNode {\r\n /**\r\n *\r\n * @param {string|object} source\r\n * @param {IFunctionSettings} [settings]\r\n */\r\n constructor(source, settings) {\r\n if (!source && !settings.ast) {\r\n throw new Error('source parameter is missing');\r\n }\r\n settings = settings || {};\r\n this.source = source;\r\n this.ast = null;\r\n this.name = typeof source === 'string' ? settings.isRootKernel ?\r\n 'kernel' :\r\n (settings.name || getFunctionNameFromString(source)) : null;\r\n this.calledFunctions = [];\r\n this.constants = {};\r\n this.constantTypes = {};\r\n this.constantBitRatios = {};\r\n this.isRootKernel = false;\r\n this.isSubKernel = false;\r\n this.debug = null;\r\n this.declarations = null;\r\n this.functions = null;\r\n this.identifiers = null;\r\n this.contexts = null;\r\n this.functionCalls = null;\r\n this.states = [];\r\n this.needsArgumentType = null;\r\n this.assignArgumentType = null;\r\n this.lookupReturnType = null;\r\n this.lookupFunctionArgumentTypes = null;\r\n this.lookupFunctionArgumentBitRatio = null;\r\n this.triggerImplyArgumentType = null;\r\n this.triggerImplyArgumentBitRatio = null;\r\n this.onNestedFunction = null;\r\n this.onFunctionCall = null;\r\n this.optimizeFloatMemory = null;\r\n this.precision = null;\r\n this.loopMaxIterations = null;\r\n this.argumentNames = (typeof this.source === 'string' ? getArgumentNamesFromString(this.source) : null);\r\n this.argumentTypes = [];\r\n this.argumentSizes = [];\r\n this.argumentBitRatios = null;\r\n this.returnType = null;\r\n this.output = [];\r\n this.plugins = null;\r\n this.leadingReturnStatement = null;\r\n this.followingReturnStatement = null;\r\n this.dynamicOutput = null;\r\n this.dynamicArguments = null;\r\n this.strictTypingChecking = false;\r\n this.fixIntegerDivisionAccuracy = null;\r\n this.warnVarUsage = true;\r\n\r\n if (settings) {\r\n for (const p in settings) {\r\n if (!settings.hasOwnProperty(p)) continue;\r\n if (!this.hasOwnProperty(p)) continue;\r\n this[p] = settings[p];\r\n }\r\n }\r\n\r\n this.literalTypes = {};\r\n\r\n this.validate();\r\n this._string = null;\r\n this._internalVariableNames = {};\r\n }\r\n\r\n validate() {\r\n if (typeof this.source !== 'string' && !this.ast) {\r\n throw new Error('this.source not a string');\r\n }\r\n\r\n if (!this.ast && !isFunctionString(this.source)) {\r\n throw new Error('this.source not a function string');\r\n }\r\n\r\n if (!this.name) {\r\n throw new Error('this.name could not be set');\r\n }\r\n\r\n if (this.argumentTypes.length > 0 && this.argumentTypes.length !== this.argumentNames.length) {\r\n throw new Error(`argumentTypes count of ${ this.argumentTypes.length } exceeds ${ this.argumentNames.length }`);\r\n }\r\n\r\n if (this.output.length < 1) {\r\n throw new Error('this.output is not big enough');\r\n }\r\n }\r\n\r\n /**\r\n * @param {String} name\r\n * @returns {boolean}\r\n */\r\n isIdentifierConstant(name) {\r\n if (!this.constants) return false;\r\n return this.constants.hasOwnProperty(name);\r\n }\r\n\r\n isInput(argumentName) {\r\n return this.argumentTypes[this.argumentNames.indexOf(argumentName)] === 'Input';\r\n }\r\n\r\n pushState(state) {\r\n this.states.push(state);\r\n }\r\n\r\n popState(state) {\r\n if (this.state !== state) {\r\n throw new Error(`Cannot popState ${ state } when in ${ this.state }`);\r\n }\r\n this.states.pop();\r\n }\r\n\r\n isState(state) {\r\n return this.state === state;\r\n }\r\n\r\n get state() {\r\n return this.states[this.states.length - 1];\r\n }\r\n\r\n /**\r\n * @function\r\n * @name astMemberExpressionUnroll\r\n * @desc Parses the abstract syntax tree for binary expression.\r\n *\r\n *Utility function for astCallExpression.
\r\n *\r\n * @param {Object} ast - the AST object to parse\r\n *\r\n * @returns {String} the function namespace call, unrolled\r\n */\r\n astMemberExpressionUnroll(ast) {\r\n if (ast.type === 'Identifier') {\r\n return ast.name;\r\n } else if (ast.type === 'ThisExpression') {\r\n return 'this';\r\n }\r\n\r\n if (ast.type === 'MemberExpression') {\r\n if (ast.object && ast.property) {\r\n //babel sniffing\r\n if (ast.object.hasOwnProperty('name') && ast.object.name[0] === '_') {\r\n return this.astMemberExpressionUnroll(ast.property);\r\n }\r\n\r\n return (\r\n this.astMemberExpressionUnroll(ast.object) +\r\n '.' +\r\n this.astMemberExpressionUnroll(ast.property)\r\n );\r\n }\r\n }\r\n\r\n //babel sniffing\r\n if (ast.hasOwnProperty('expressions')) {\r\n const firstExpression = ast.expressions[0];\r\n if (firstExpression.type === 'Literal' && firstExpression.value === 0 && ast.expressions.length === 2) {\r\n return this.astMemberExpressionUnroll(ast.expressions[1]);\r\n }\r\n }\r\n\r\n // Failure, unknown expression\r\n throw this.astErrorOutput('Unknown astMemberExpressionUnroll', ast);\r\n }\r\n\r\n /**\r\n * @desc Parses the class function JS, and returns its Abstract Syntax Tree object.\r\n * This is used internally to convert to shader code\r\n *\r\n * @param {Object} [inParser] - Parser to use, assumes in scope 'parser' if null or undefined\r\n *\r\n * @returns {Object} The function AST Object, note that result is cached under this.ast;\r\n */\r\n getJsAST(inParser) {\r\n if (this.ast) {\r\n return this.ast;\r\n }\r\n if (typeof this.source === 'object') {\r\n this.traceFunctionAST(this.source);\r\n return this.ast = this.source;\r\n }\r\n\r\n const parser = inParser && inParser.hasOwnProperty('parse') ? inParser.parse : parse\r\n if (inParser === null) {\r\n throw new Error('Missing JS to AST parser');\r\n }\r\n\r\n const ast = Object.freeze(parser(`const parser_${ this.name } = ${ this.source };`, {\r\n locations: true\r\n }));\r\n // take out the function object, outside the var declarations\r\n const functionAST = ast.body[0].declarations[0].init;\r\n this.traceFunctionAST(functionAST);\r\n\r\n if (!ast) {\r\n throw new Error('Failed to parse JS code');\r\n }\r\n\r\n return this.ast = functionAST;\r\n }\r\n\r\n traceFunctionAST(ast) {\r\n const { contexts, declarations, functions, identifiers, functionCalls } = new FunctionTracer(ast);\r\n this.contexts = contexts;\r\n this.identifiers = identifiers;\r\n this.functionCalls = functionCalls;\r\n this.declarations = [];\r\n this.functions = functions;\r\n for (let i = 0; i < declarations.length; i++) {\r\n const declaration = declarations[i];\r\n const { ast, context, name, origin, forceInteger, assignable } = declaration;\r\n const { init } = ast;\r\n const dependencies = this.getDependencies(init);\r\n let valueType = null;\r\n\r\n if (forceInteger) {\r\n valueType = 'Integer';\r\n } else {\r\n if (init) {\r\n const realType = this.getType(init);\r\n switch (realType) {\r\n case 'Integer':\r\n case 'Float':\r\n case 'Number':\r\n if (init.type === 'MemberExpression') {\r\n valueType = realType;\r\n } else {\r\n valueType = 'Number';\r\n }\r\n break;\r\n case 'LiteralInteger':\r\n valueType = 'Number';\r\n break;\r\n default:\r\n valueType = realType;\r\n }\r\n }\r\n }\r\n this.declarations.push({\r\n valueType,\r\n dependencies,\r\n isSafe: this.isSafeDependencies(dependencies),\r\n ast,\r\n name,\r\n context,\r\n origin,\r\n assignable,\r\n });\r\n }\r\n\r\n for (let i = 0; i < functions.length; i++) {\r\n this.onNestedFunction(functions[i]);\r\n }\r\n }\r\n\r\n getDeclaration(ast) {\r\n for (let i = 0; i < this.identifiers.length; i++) {\r\n const identifier = this.identifiers[i];\r\n if (ast === identifier.ast && identifier.context.hasOwnProperty(ast.name)) {\r\n for (let j = 0; j < this.declarations.length; j++) {\r\n const declaration = this.declarations[j];\r\n if (declaration.name === ast.name && declaration.context[ast.name] === identifier.context[ast.name]) {\r\n return declaration;\r\n }\r\n }\r\n }\r\n }\r\n return null;\r\n }\r\n\r\n /**\r\n * @desc Return the type of parameter sent to subKernel/Kernel.\r\n * @param {Object} ast - Identifier\r\n * @returns {String} Type of the parameter\r\n */\r\n getVariableType(ast) {\r\n if (ast.type !== 'Identifier') {\r\n throw new Error(`ast of ${ast.type} not \"Identifier\"`);\r\n }\r\n let type = null;\r\n const argumentIndex = this.argumentNames.indexOf(ast.name);\r\n if (argumentIndex === -1) {\r\n const declaration = this.getDeclaration(ast);\r\n if (declaration) {\r\n return declaration.valueType;\r\n }\r\n } else {\r\n const argumentType = this.argumentTypes[argumentIndex];\r\n if (argumentType) {\r\n type = argumentType;\r\n }\r\n }\r\n if (!type && this.strictTypingChecking) {\r\n throw new Error(`Declaration of ${name} not found`);\r\n }\r\n return type;\r\n }\r\n\r\n /**\r\n * Generally used to lookup the value type returned from a member expressions\r\n * @param {String} type\r\n * @return {String}\r\n */\r\n getLookupType(type) {\r\n if (!typeLookupMap.hasOwnProperty(type)) {\r\n throw new Error(`unknown typeLookupMap ${ type }`);\r\n }\r\n return typeLookupMap[type];\r\n }\r\n\r\n getConstantType(constantName) {\r\n if (this.constantTypes[constantName]) {\r\n const type = this.constantTypes[constantName];\r\n if (type === 'Float') {\r\n return 'Number';\r\n } else {\r\n return type;\r\n }\r\n }\r\n throw new Error(`Type for constant \"${ constantName }\" not declared`);\r\n }\r\n\r\n toString() {\r\n if (this._string) return this._string;\r\n return this._string = this.astGeneric(this.getJsAST(), []).join('').trim();\r\n }\r\n\r\n toJSON() {\r\n const settings = {\r\n source: this.source,\r\n name: this.name,\r\n constants: this.constants,\r\n constantTypes: this.constantTypes,\r\n isRootKernel: this.isRootKernel,\r\n isSubKernel: this.isSubKernel,\r\n debug: this.debug,\r\n output: this.output,\r\n loopMaxIterations: this.loopMaxIterations,\r\n argumentNames: this.argumentNames,\r\n argumentTypes: this.argumentTypes,\r\n argumentSizes: this.argumentSizes,\r\n returnType: this.returnType,\r\n leadingReturnStatement: this.leadingReturnStatement,\r\n followingReturnStatement: this.followingReturnStatement,\r\n };\r\n\r\n return {\r\n ast: this.ast,\r\n settings\r\n };\r\n }\r\n\r\n /**\r\n * Recursively looks up type for ast expression until it's found\r\n * @param ast\r\n * @returns {String|null}\r\n */\r\n getType(ast) {\r\n if (Array.isArray(ast)) {\r\n return this.getType(ast[ast.length - 1]);\r\n }\r\n switch (ast.type) {\r\n case 'BlockStatement':\r\n return this.getType(ast.body);\r\n case 'ArrayExpression':\r\n return `Array(${ ast.elements.length })`;\r\n case 'Literal':\r\n const literalKey = `${ast.start},${ast.end}`;\r\n if (this.literalTypes[literalKey]) {\r\n return this.literalTypes[literalKey];\r\n }\r\n if (Number.isInteger(ast.value)) {\r\n return 'LiteralInteger';\r\n } else if (ast.value === true || ast.value === false) {\r\n return 'Boolean';\r\n } else {\r\n return 'Number';\r\n }\r\n case 'AssignmentExpression':\r\n return this.getType(ast.left);\r\n case 'CallExpression':\r\n if (this.isAstMathFunction(ast)) {\r\n return 'Number';\r\n }\r\n if (!ast.callee || !ast.callee.name) {\r\n if (ast.callee.type === 'SequenceExpression' && ast.callee.expressions[ast.callee.expressions.length - 1].property.name) {\r\n const functionName = ast.callee.expressions[ast.callee.expressions.length - 1].property.name;\r\n this.inferArgumentTypesIfNeeded(functionName, ast.arguments);\r\n return this.lookupReturnType(functionName, ast, this);\r\n }\r\n throw this.astErrorOutput('Unknown call expression', ast);\r\n }\r\n if (ast.callee && ast.callee.name) {\r\n const functionName = ast.callee.name;\r\n this.inferArgumentTypesIfNeeded(functionName, ast.arguments);\r\n return this.lookupReturnType(functionName, ast, this);\r\n }\r\n throw this.astErrorOutput(`Unhandled getType Type \"${ ast.type }\"`, ast);\r\n case 'BinaryExpression':\r\n // modulos is Number\r\n switch (ast.operator) {\r\n case '%':\r\n case '/':\r\n if (this.fixIntegerDivisionAccuracy) {\r\n return 'Number';\r\n } else {\r\n break;\r\n }\r\n case '>':\r\n case '<':\r\n return 'Boolean';\r\n case '&':\r\n case '|':\r\n case '^':\r\n case '<<':\r\n case '>>':\r\n case '>>>':\r\n return 'Integer';\r\n }\r\n const type = this.getType(ast.left);\r\n if (this.isState('skip-literal-correction')) return type;\r\n if (type === 'LiteralInteger') {\r\n const rightType = this.getType(ast.right);\r\n if (rightType === 'LiteralInteger') {\r\n if (ast.left.value % 1 === 0) {\r\n return 'Integer';\r\n } else {\r\n return 'Float';\r\n }\r\n }\r\n return rightType;\r\n }\r\n return typeLookupMap[type] || type;\r\n case 'UpdateExpression':\r\n return this.getType(ast.argument);\r\n case 'UnaryExpression':\r\n if (ast.operator === '~') {\r\n return 'Integer';\r\n }\r\n return this.getType(ast.argument);\r\n case 'VariableDeclaration': {\r\n const declarations = ast.declarations;\r\n let lastType;\r\n for (let i = 0; i < declarations.length; i++) {\r\n const declaration = declarations[i];\r\n lastType = this.getType(declaration);\r\n }\r\n if (!lastType) {\r\n throw this.astErrorOutput(`Unable to find type for declaration`, ast);\r\n }\r\n return lastType;\r\n }\r\n case 'VariableDeclarator':\r\n const declaration = this.getDeclaration(ast.id);\r\n if (!declaration) {\r\n throw this.astErrorOutput(`Unable to find declarator`, ast);\r\n }\r\n\r\n if (!declaration.valueType) {\r\n throw this.astErrorOutput(`Unable to find declarator valueType`, ast);\r\n }\r\n\r\n return declaration.valueType;\r\n case 'Identifier':\r\n if (ast.name === 'Infinity') {\r\n return 'Number';\r\n }\r\n if (this.isAstVariable(ast)) {\r\n const signature = this.getVariableSignature(ast);\r\n if (signature === 'value') {\r\n const type = this.getVariableType(ast);\r\n if (!type) {\r\n throw this.astErrorOutput(`Unable to find identifier valueType`, ast);\r\n }\r\n return type;\r\n }\r\n }\r\n const origin = this.findIdentifierOrigin(ast);\r\n if (origin && origin.init) {\r\n return this.getType(origin.init);\r\n }\r\n return null;\r\n case 'ReturnStatement':\r\n return this.getType(ast.argument);\r\n case 'MemberExpression':\r\n if (this.isAstMathFunction(ast)) {\r\n switch (ast.property.name) {\r\n case 'ceil':\r\n return 'Integer';\r\n case 'floor':\r\n return 'Integer';\r\n case 'round':\r\n return 'Integer';\r\n }\r\n return 'Number';\r\n }\r\n if (this.isAstVariable(ast)) {\r\n const variableSignature = this.getVariableSignature(ast);\r\n switch (variableSignature) {\r\n case 'value[]':\r\n return this.getLookupType(this.getVariableType(ast.object));\r\n case 'value[][]':\r\n return this.getLookupType(this.getVariableType(ast.object.object));\r\n case 'value[][][]':\r\n return this.getLookupType(this.getVariableType(ast.object.object.object));\r\n case 'value[][][][]':\r\n return this.getLookupType(this.getVariableType(ast.object.object.object.object));\r\n case 'value.thread.value':\r\n case 'this.thread.value':\r\n return 'Integer';\r\n case 'this.output.value':\r\n return this.dynamicOutput ? 'Integer' : 'LiteralInteger';\r\n case 'this.constants.value':\r\n return this.getConstantType(ast.property.name);\r\n case 'this.constants.value[]':\r\n return this.getLookupType(this.getConstantType(ast.object.property.name));\r\n case 'this.constants.value[][]':\r\n return this.getLookupType(this.getConstantType(ast.object.object.property.name));\r\n case 'this.constants.value[][][]':\r\n return this.getLookupType(this.getConstantType(ast.object.object.object.property.name));\r\n case 'this.constants.value[][][][]':\r\n return this.getLookupType(this.getConstantType(ast.object.object.object.object.property.name));\r\n case 'fn()[]':\r\n return this.getLookupType(this.getType(ast.object));\r\n case 'fn()[][]':\r\n return this.getLookupType(this.getType(ast.object));\r\n case 'fn()[][][]':\r\n return this.getLookupType(this.getType(ast.object));\r\n case 'value.value':\r\n if (this.isAstMathVariable(ast)) {\r\n return 'Number';\r\n }\r\n switch (ast.property.name) {\r\n case 'r':\r\n return this.getLookupType(this.getVariableType(ast.object));\r\n case 'g':\r\n return this.getLookupType(this.getVariableType(ast.object));\r\n case 'b':\r\n return this.getLookupType(this.getVariableType(ast.object));\r\n case 'a':\r\n return this.getLookupType(this.getVariableType(ast.object));\r\n }\r\n case '[][]':\r\n return 'Number';\r\n }\r\n throw this.astErrorOutput('Unhandled getType MemberExpression', ast);\r\n }\r\n throw this.astErrorOutput('Unhandled getType MemberExpression', ast);\r\n case 'ConditionalExpression':\r\n return this.getType(ast.consequent);\r\n case 'FunctionDeclaration':\r\n case 'FunctionExpression':\r\n const lastReturn = this.findLastReturn(ast.body);\r\n if (lastReturn) {\r\n return this.getType(lastReturn);\r\n }\r\n return null;\r\n case 'IfStatement':\r\n return this.getType(ast.consequent);\r\n default:\r\n throw this.astErrorOutput(`Unhandled getType Type \"${ ast.type }\"`, ast);\r\n }\r\n }\r\n\r\n inferArgumentTypesIfNeeded(functionName, args) {\r\n // ensure arguments are filled in, so when we lookup return type, we already can infer it\r\n for (let i = 0; i < args.length; i++) {\r\n if (!this.needsArgumentType(functionName, i)) continue;\r\n const type = this.getType(args[i]);\r\n if (!type) {\r\n throw this.astErrorOutput(`Unable to infer argument ${i}`, args[i]);\r\n }\r\n this.assignArgumentType(functionName, i, type);\r\n }\r\n }\r\n\r\n isAstMathVariable(ast) {\r\n const mathProperties = [\r\n 'E',\r\n 'PI',\r\n 'SQRT2',\r\n 'SQRT1_2',\r\n 'LN2',\r\n 'LN10',\r\n 'LOG2E',\r\n 'LOG10E',\r\n ];\r\n return ast.type === 'MemberExpression' &&\r\n ast.object && ast.object.type === 'Identifier' &&\r\n ast.object.name === 'Math' &&\r\n ast.property &&\r\n ast.property.type === 'Identifier' &&\r\n mathProperties.indexOf(ast.property.name) > -1;\r\n }\r\n\r\n isAstMathFunction(ast) {\r\n const mathFunctions = [\r\n 'abs',\r\n 'acos',\r\n 'asin',\r\n 'atan',\r\n 'atan2',\r\n 'ceil',\r\n 'cos',\r\n 'exp',\r\n 'floor',\r\n 'log',\r\n 'log2',\r\n 'max',\r\n 'min',\r\n 'pow',\r\n 'random',\r\n 'round',\r\n 'sign',\r\n 'sin',\r\n 'sqrt',\r\n 'tan',\r\n ];\r\n return ast.type === 'CallExpression' &&\r\n ast.callee &&\r\n ast.callee.type === 'MemberExpression' &&\r\n ast.callee.object &&\r\n ast.callee.object.type === 'Identifier' &&\r\n ast.callee.object.name === 'Math' &&\r\n ast.callee.property &&\r\n ast.callee.property.type === 'Identifier' &&\r\n mathFunctions.indexOf(ast.callee.property.name) > -1;\r\n }\r\n\r\n isAstVariable(ast) {\r\n return ast.type === 'Identifier' || ast.type === 'MemberExpression';\r\n }\r\n\r\n isSafe(ast) {\r\n return this.isSafeDependencies(this.getDependencies(ast));\r\n }\r\n\r\n isSafeDependencies(dependencies) {\r\n return dependencies && dependencies.every ? dependencies.every(dependency => dependency.isSafe) : true;\r\n }\r\n\r\n /**\r\n *\r\n * @param ast\r\n * @param dependencies\r\n * @param isNotSafe\r\n * @return {Array}\r\n */\r\n getDependencies(ast, dependencies, isNotSafe) {\r\n if (!dependencies) {\r\n dependencies = [];\r\n }\r\n if (!ast) return null;\r\n if (Array.isArray(ast)) {\r\n for (let i = 0; i < ast.length; i++) {\r\n this.getDependencies(ast[i], dependencies, isNotSafe);\r\n }\r\n return dependencies;\r\n }\r\n switch (ast.type) {\r\n case 'AssignmentExpression':\r\n this.getDependencies(ast.left, dependencies, isNotSafe);\r\n this.getDependencies(ast.right, dependencies, isNotSafe);\r\n return dependencies;\r\n case 'ConditionalExpression':\r\n this.getDependencies(ast.test, dependencies, isNotSafe);\r\n this.getDependencies(ast.alternate, dependencies, isNotSafe);\r\n this.getDependencies(ast.consequent, dependencies, isNotSafe);\r\n return dependencies;\r\n case 'Literal':\r\n dependencies.push({\r\n origin: 'literal',\r\n value: ast.value,\r\n isSafe: isNotSafe === true ? false : ast.value > -Infinity && ast.value < Infinity && !isNaN(ast.value)\r\n });\r\n break;\r\n case 'VariableDeclarator':\r\n return this.getDependencies(ast.init, dependencies, isNotSafe);\r\n case 'Identifier':\r\n const declaration = this.getDeclaration(ast);\r\n if (declaration) {\r\n dependencies.push({\r\n name: ast.name,\r\n origin: 'declaration',\r\n isSafe: isNotSafe ? false : this.isSafeDependencies(declaration.dependencies),\r\n });\r\n } else if (this.argumentNames.indexOf(ast.name) > -1) {\r\n dependencies.push({\r\n name: ast.name,\r\n origin: 'argument',\r\n isSafe: false,\r\n });\r\n } else if (this.strictTypingChecking) {\r\n throw new Error(`Cannot find identifier origin \"${ast.name}\"`);\r\n }\r\n break;\r\n case 'FunctionDeclaration':\r\n return this.getDependencies(ast.body.body[ast.body.body.length - 1], dependencies, isNotSafe);\r\n case 'ReturnStatement':\r\n return this.getDependencies(ast.argument, dependencies);\r\n case 'BinaryExpression':\r\n isNotSafe = (ast.operator === '/' || ast.operator === '*');\r\n this.getDependencies(ast.left, dependencies, isNotSafe);\r\n this.getDependencies(ast.right, dependencies, isNotSafe);\r\n return dependencies;\r\n case 'UnaryExpression':\r\n case 'UpdateExpression':\r\n return this.getDependencies(ast.argument, dependencies, isNotSafe);\r\n case 'VariableDeclaration':\r\n return this.getDependencies(ast.declarations, dependencies, isNotSafe);\r\n case 'ArrayExpression':\r\n dependencies.push({\r\n origin: 'declaration',\r\n isSafe: true,\r\n });\r\n return dependencies;\r\n case 'CallExpression':\r\n dependencies.push({\r\n origin: 'function',\r\n isSafe: true,\r\n });\r\n return dependencies;\r\n case 'MemberExpression':\r\n const details = this.getMemberExpressionDetails(ast);\r\n switch (details.signature) {\r\n case 'value[]':\r\n this.getDependencies(ast.object, dependencies, isNotSafe);\r\n break;\r\n case 'value[][]':\r\n this.getDependencies(ast.object.object, dependencies, isNotSafe);\r\n break;\r\n case 'value[][][]':\r\n this.getDependencies(ast.object.object.object, dependencies, isNotSafe);\r\n break;\r\n case 'this.output.value':\r\n if (this.dynamicOutput) {\r\n dependencies.push({\r\n name: details.name,\r\n origin: 'output',\r\n isSafe: false,\r\n });\r\n }\r\n break;\r\n }\r\n if (details) {\r\n if (details.property) {\r\n this.getDependencies(details.property, dependencies, isNotSafe);\r\n }\r\n if (details.xProperty) {\r\n this.getDependencies(details.xProperty, dependencies, isNotSafe);\r\n }\r\n if (details.yProperty) {\r\n this.getDependencies(details.yProperty, dependencies, isNotSafe);\r\n }\r\n if (details.zProperty) {\r\n this.getDependencies(details.zProperty, dependencies, isNotSafe);\r\n }\r\n return dependencies;\r\n }\r\n default:\r\n throw this.astErrorOutput(`Unhandled type ${ ast.type } in getDependencies`, ast);\r\n }\r\n return dependencies;\r\n }\r\n\r\n getVariableSignature(ast) {\r\n if (!this.isAstVariable(ast)) {\r\n throw new Error(`ast of type \"${ ast.type }\" is not a variable signature`);\r\n }\r\n if (ast.type === 'Identifier') {\r\n return 'value';\r\n }\r\n const signature = [];\r\n while (true) {\r\n if (!ast) break;\r\n if (ast.computed) {\r\n signature.push('[]');\r\n } else if (ast.type === 'ThisExpression') {\r\n signature.unshift('this');\r\n } else if (ast.property && ast.property.name) {\r\n if (\r\n ast.property.name === 'x' ||\r\n ast.property.name === 'y' ||\r\n ast.property.name === 'z'\r\n ) {\r\n signature.unshift('.value');\r\n } else if (\r\n ast.property.name === 'constants' ||\r\n ast.property.name === 'thread' ||\r\n ast.property.name === 'output'\r\n ) {\r\n signature.unshift('.' + ast.property.name);\r\n } else {\r\n signature.unshift('.value');\r\n }\r\n } else if (ast.name) {\r\n signature.unshift('value');\r\n } else if (ast.callee && ast.callee.name) {\r\n signature.unshift('fn()');\r\n } else if (ast.elements) {\r\n signature.unshift('[]');\r\n } else {\r\n signature.unshift('unknown');\r\n }\r\n ast = ast.object;\r\n }\r\n\r\n const signatureString = signature.join('');\r\n const allowedExpressions = [\r\n 'value',\r\n 'value[]',\r\n 'value[][]',\r\n 'value[][][]',\r\n 'value[][][][]',\r\n 'value.value',\r\n 'value.thread.value',\r\n 'this.thread.value',\r\n 'this.output.value',\r\n 'this.constants.value',\r\n 'this.constants.value[]',\r\n 'this.constants.value[][]',\r\n 'this.constants.value[][][]',\r\n 'this.constants.value[][][][]',\r\n 'fn()[]',\r\n 'fn()[][]',\r\n 'fn()[][][]',\r\n '[][]',\r\n ];\r\n if (allowedExpressions.indexOf(signatureString) > -1) {\r\n return signatureString;\r\n }\r\n return null;\r\n }\r\n\r\n build() {\r\n return this.toString().length > 0;\r\n }\r\n\r\n /**\r\n * @desc Parses the abstract syntax tree for generically to its respective function\r\n * @param {Object} ast - the AST object to parse\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the parsed string array\r\n */\r\n astGeneric(ast, retArr) {\r\n if (ast === null) {\r\n throw this.astErrorOutput('NULL ast', ast);\r\n } else {\r\n if (Array.isArray(ast)) {\r\n for (let i = 0; i < ast.length; i++) {\r\n this.astGeneric(ast[i], retArr);\r\n }\r\n return retArr;\r\n }\r\n\r\n switch (ast.type) {\r\n case 'FunctionDeclaration':\r\n return this.astFunctionDeclaration(ast, retArr);\r\n case 'FunctionExpression':\r\n return this.astFunctionExpression(ast, retArr);\r\n case 'ReturnStatement':\r\n return this.astReturnStatement(ast, retArr);\r\n case 'Literal':\r\n return this.astLiteral(ast, retArr);\r\n case 'BinaryExpression':\r\n return this.astBinaryExpression(ast, retArr);\r\n case 'Identifier':\r\n return this.astIdentifierExpression(ast, retArr);\r\n case 'AssignmentExpression':\r\n return this.astAssignmentExpression(ast, retArr);\r\n case 'ExpressionStatement':\r\n return this.astExpressionStatement(ast, retArr);\r\n case 'EmptyStatement':\r\n return this.astEmptyStatement(ast, retArr);\r\n case 'BlockStatement':\r\n return this.astBlockStatement(ast, retArr);\r\n case 'IfStatement':\r\n return this.astIfStatement(ast, retArr);\r\n case 'SwitchStatement':\r\n return this.astSwitchStatement(ast, retArr);\r\n case 'BreakStatement':\r\n return this.astBreakStatement(ast, retArr);\r\n case 'ContinueStatement':\r\n return this.astContinueStatement(ast, retArr);\r\n case 'ForStatement':\r\n return this.astForStatement(ast, retArr);\r\n case 'WhileStatement':\r\n return this.astWhileStatement(ast, retArr);\r\n case 'DoWhileStatement':\r\n return this.astDoWhileStatement(ast, retArr);\r\n case 'VariableDeclaration':\r\n return this.astVariableDeclaration(ast, retArr);\r\n case 'VariableDeclarator':\r\n return this.astVariableDeclarator(ast, retArr);\r\n case 'ThisExpression':\r\n return this.astThisExpression(ast, retArr);\r\n case 'SequenceExpression':\r\n return this.astSequenceExpression(ast, retArr);\r\n case 'UnaryExpression':\r\n return this.astUnaryExpression(ast, retArr);\r\n case 'UpdateExpression':\r\n return this.astUpdateExpression(ast, retArr);\r\n case 'LogicalExpression':\r\n return this.astLogicalExpression(ast, retArr);\r\n case 'MemberExpression':\r\n return this.astMemberExpression(ast, retArr);\r\n case 'CallExpression':\r\n return this.astCallExpression(ast, retArr);\r\n case 'ArrayExpression':\r\n return this.astArrayExpression(ast, retArr);\r\n case 'DebuggerStatement':\r\n return this.astDebuggerStatement(ast, retArr);\r\n case 'ConditionalExpression':\r\n return this.astConditionalExpression(ast, retArr);\r\n }\r\n\r\n throw this.astErrorOutput('Unknown ast type : ' + ast.type, ast);\r\n }\r\n }\r\n /**\r\n * @desc To throw the AST error, with its location.\r\n * @param {string} error - the error message output\r\n * @param {Object} ast - the AST object where the error is\r\n */\r\n astErrorOutput(error, ast) {\r\n if (typeof this.source !== 'string') {\r\n return new Error(error);\r\n }\r\n\r\n const debugString = getAstString(this.source, ast);\r\n const leadingSource = this.source.substr(ast.start);\r\n const splitLines = leadingSource.split(/\\n/);\r\n const lineBefore = splitLines.length > 0 ? splitLines[splitLines.length - 1] : 0;\r\n return new Error(`${error} on line ${ splitLines.length }, position ${ lineBefore.length }:\\n ${ debugString }`);\r\n }\r\n\r\n astDebuggerStatement(arrNode, retArr) {\r\n return retArr;\r\n }\r\n\r\n astConditionalExpression(ast, retArr) {\r\n if (ast.type !== 'ConditionalExpression') {\r\n throw this.astErrorOutput('Not a conditional expression', ast);\r\n }\r\n retArr.push('(');\r\n this.astGeneric(ast.test, retArr);\r\n retArr.push('?');\r\n this.astGeneric(ast.consequent, retArr);\r\n retArr.push(':');\r\n this.astGeneric(ast.alternate, retArr);\r\n retArr.push(')');\r\n return retArr;\r\n }\r\n\r\n /**\r\n * @abstract\r\n * @param {Object} ast\r\n * @param {String[]} retArr\r\n * @returns {String[]}\r\n */\r\n astFunction(ast, retArr) {\r\n throw new Error(`\"astFunction\" not defined on ${ this.constructor.name }`);\r\n }\r\n\r\n /**\r\n * @desc Parses the abstract syntax tree for to its *named function declaration*\r\n * @param {Object} ast - the AST object to parse\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astFunctionDeclaration(ast, retArr) {\r\n if (this.isChildFunction(ast)) {\r\n return retArr;\r\n }\r\n return this.astFunction(ast, retArr);\r\n }\r\n astFunctionExpression(ast, retArr) {\r\n if (this.isChildFunction(ast)) {\r\n return retArr;\r\n }\r\n return this.astFunction(ast, retArr);\r\n }\r\n isChildFunction(ast) {\r\n for (let i = 0; i < this.functions.length; i++) {\r\n if (this.functions[i] === ast) {\r\n return true;\r\n }\r\n }\r\n return false;\r\n }\r\n astReturnStatement(ast, retArr) {\r\n return retArr;\r\n }\r\n astLiteral(ast, retArr) {\r\n this.literalTypes[`${ast.start},${ast.end}`] = 'Number';\r\n return retArr;\r\n }\r\n astBinaryExpression(ast, retArr) {\r\n return retArr;\r\n }\r\n astIdentifierExpression(ast, retArr) {\r\n return retArr;\r\n }\r\n astAssignmentExpression(ast, retArr) {\r\n return retArr;\r\n }\r\n /**\r\n * @desc Parses the abstract syntax tree for *generic expression* statement\r\n * @param {Object} esNode - An ast Node\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astExpressionStatement(esNode, retArr) {\r\n this.astGeneric(esNode.expression, retArr);\r\n retArr.push(';');\r\n return retArr;\r\n }\r\n /**\r\n * @desc Parses the abstract syntax tree for an *Empty* Statement\r\n * @param {Object} eNode - An ast Node\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astEmptyStatement(eNode, retArr) {\r\n return retArr;\r\n }\r\n astBlockStatement(ast, retArr) {\r\n return retArr;\r\n }\r\n astIfStatement(ast, retArr) {\r\n return retArr;\r\n }\r\n astSwitchStatement(ast, retArr) {\r\n return retArr;\r\n }\r\n /**\r\n * @desc Parses the abstract syntax tree for *Break* Statement\r\n * @param {Object} brNode - An ast Node\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astBreakStatement(brNode, retArr) {\r\n retArr.push('break;');\r\n return retArr;\r\n }\r\n /**\r\n * @desc Parses the abstract syntax tree for *Continue* Statement\r\n * @param {Object} crNode - An ast Node\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astContinueStatement(crNode, retArr) {\r\n retArr.push('continue;\\n');\r\n return retArr;\r\n }\r\n astForStatement(ast, retArr) {\r\n return retArr;\r\n }\r\n astWhileStatement(ast, retArr) {\r\n return retArr;\r\n }\r\n astDoWhileStatement(ast, retArr) {\r\n return retArr;\r\n }\r\n /**\r\n * @desc Parses the abstract syntax tree for *Variable Declaration*\r\n * @param {Object} varDecNode - An ast Node\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astVariableDeclaration(varDecNode, retArr) {\r\n const declarations = varDecNode.declarations;\r\n if (!declarations || !declarations[0] || !declarations[0].init) {\r\n throw this.astErrorOutput('Unexpected expression', varDecNode);\r\n }\r\n const result = [];\r\n const firstDeclaration = declarations[0];\r\n const init = firstDeclaration.init;\r\n let type = this.isState('in-for-loop-init') ? 'Integer' : this.getType(init);\r\n if (type === 'LiteralInteger') {\r\n // We had the choice to go either float or int, choosing float\r\n type = 'Number';\r\n }\r\n const markupType = typeMap[type];\r\n if (!markupType) {\r\n throw this.astErrorOutput(`Markup type ${ markupType } not handled`, varDecNode);\r\n }\r\n let dependencies = this.getDependencies(firstDeclaration.init);\r\n throw new Error('remove me');\r\n this.declarations[firstDeclaration.id.name] = Object.freeze({\r\n type,\r\n dependencies,\r\n isSafe: dependencies.every(dependency => dependency.isSafe)\r\n });\r\n const initResult = [`${type} user_${firstDeclaration.id.name}=`];\r\n this.astGeneric(init, initResult);\r\n result.push(initResult.join(''));\r\n\r\n // first declaration is done, now any added ones setup\r\n for (let i = 1; i < declarations.length; i++) {\r\n const declaration = declarations[i];\r\n dependencies = this.getDependencies(declaration);\r\n throw new Error('Remove me');\r\n this.declarations[declaration.id.name] = Object.freeze({\r\n type,\r\n dependencies,\r\n isSafe: false\r\n });\r\n this.astGeneric(declaration, result);\r\n }\r\n\r\n retArr.push(retArr, result.join(','));\r\n retArr.push(';');\r\n return retArr;\r\n }\r\n /**\r\n * @desc Parses the abstract syntax tree for *Variable Declarator*\r\n * @param {Object} iVarDecNode - An ast Node\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astVariableDeclarator(iVarDecNode, retArr) {\r\n this.astGeneric(iVarDecNode.id, retArr);\r\n if (iVarDecNode.init !== null) {\r\n retArr.push('=');\r\n this.astGeneric(iVarDecNode.init, retArr);\r\n }\r\n return retArr;\r\n }\r\n astThisExpression(ast, retArr) {\r\n return retArr;\r\n }\r\n astSequenceExpression(sNode, retArr) {\r\n for (let i = 0; i < sNode.expressions.length; i++) {\r\n if (i > 0) {\r\n retArr.push(',');\r\n }\r\n this.astGeneric(sNode.expressions, retArr);\r\n }\r\n return retArr;\r\n }\r\n /**\r\n * @desc Parses the abstract syntax tree for *Unary* Expression\r\n * @param {Object} uNode - An ast Node\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astUnaryExpression(uNode, retArr) {\r\n const unaryResult = this.checkAndUpconvertBitwiseUnary(uNode, retArr);\r\n if (unaryResult) {\r\n return retArr;\r\n }\r\n\r\n if (uNode.prefix) {\r\n retArr.push(uNode.operator);\r\n this.astGeneric(uNode.argument, retArr);\r\n } else {\r\n this.astGeneric(uNode.argument, retArr);\r\n retArr.push(uNode.operator);\r\n }\r\n\r\n return retArr;\r\n }\r\n\r\n checkAndUpconvertBitwiseUnary(uNode, retArr) {}\r\n\r\n /**\r\n * @desc Parses the abstract syntax tree for *Update* Expression\r\n * @param {Object} uNode - An ast Node\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astUpdateExpression(uNode, retArr) {\r\n if (uNode.prefix) {\r\n retArr.push(uNode.operator);\r\n this.astGeneric(uNode.argument, retArr);\r\n } else {\r\n this.astGeneric(uNode.argument, retArr);\r\n retArr.push(uNode.operator);\r\n }\r\n\r\n return retArr;\r\n }\r\n /**\r\n * @desc Parses the abstract syntax tree for *Logical* Expression\r\n * @param {Object} logNode - An ast Node\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astLogicalExpression(logNode, retArr) {\r\n retArr.push('(');\r\n this.astGeneric(logNode.left, retArr);\r\n retArr.push(logNode.operator);\r\n this.astGeneric(logNode.right, retArr);\r\n retArr.push(')');\r\n return retArr;\r\n }\r\n astMemberExpression(ast, retArr) {\r\n return retArr;\r\n }\r\n astCallExpression(ast, retArr) {\r\n return retArr;\r\n }\r\n astArrayExpression(ast, retArr) {\r\n return retArr;\r\n }\r\n\r\n /**\r\n *\r\n * @param ast\r\n * @return {IFunctionNodeMemberExpressionDetails}\r\n */\r\n getMemberExpressionDetails(ast) {\r\n if (ast.type !== 'MemberExpression') {\r\n throw this.astErrorOutput(`Expression ${ ast.type } not a MemberExpression`, ast);\r\n }\r\n let name = null;\r\n let type = null;\r\n const variableSignature = this.getVariableSignature(ast);\r\n switch (variableSignature) {\r\n case 'value':\r\n return null;\r\n case 'value.thread.value':\r\n case 'this.thread.value':\r\n case 'this.output.value':\r\n return {\r\n signature: variableSignature,\r\n type: 'Integer',\r\n name: ast.property.name\r\n };\r\n case 'value[]':\r\n if (typeof ast.object.name !== 'string') {\r\n throw this.astErrorOutput('Unexpected expression', ast);\r\n }\r\n name = ast.object.name;\r\n return {\r\n name,\r\n origin: 'user',\r\n signature: variableSignature,\r\n type: this.getVariableType(ast.object),\r\n xProperty: ast.property\r\n };\r\n case 'value[][]':\r\n if (typeof ast.object.object.name !== 'string') {\r\n throw this.astErrorOutput('Unexpected expression', ast);\r\n }\r\n name = ast.object.object.name;\r\n return {\r\n name,\r\n origin: 'user',\r\n signature: variableSignature,\r\n type: this.getVariableType(ast.object.object),\r\n yProperty: ast.object.property,\r\n xProperty: ast.property,\r\n };\r\n case 'value[][][]':\r\n if (typeof ast.object.object.object.name !== 'string') {\r\n throw this.astErrorOutput('Unexpected expression', ast);\r\n }\r\n name = ast.object.object.object.name;\r\n return {\r\n name,\r\n origin: 'user',\r\n signature: variableSignature,\r\n type: this.getVariableType(ast.object.object.object),\r\n zProperty: ast.object.object.property,\r\n yProperty: ast.object.property,\r\n xProperty: ast.property,\r\n };\r\n case 'value[][][][]':\r\n if (typeof ast.object.object.object.object.name !== 'string') {\r\n throw this.astErrorOutput('Unexpected expression', ast);\r\n }\r\n name = ast.object.object.object.object.name;\r\n return {\r\n name,\r\n origin: 'user',\r\n signature: variableSignature,\r\n type: this.getVariableType(ast.object.object.object.object),\r\n zProperty: ast.object.object.property,\r\n yProperty: ast.object.property,\r\n xProperty: ast.property,\r\n };\r\n case 'value.value':\r\n if (typeof ast.property.name !== 'string') {\r\n throw this.astErrorOutput('Unexpected expression', ast);\r\n }\r\n if (this.isAstMathVariable(ast)) {\r\n name = ast.property.name;\r\n return {\r\n name,\r\n origin: 'Math',\r\n type: 'Number',\r\n signature: variableSignature,\r\n };\r\n }\r\n switch (ast.property.name) {\r\n case 'r':\r\n case 'g':\r\n case 'b':\r\n case 'a':\r\n name = ast.object.name;\r\n return {\r\n name,\r\n property: ast.property.name,\r\n origin: 'user',\r\n signature: variableSignature,\r\n type: 'Number'\r\n };\r\n default:\r\n throw this.astErrorOutput('Unexpected expression', ast);\r\n }\r\n case 'this.constants.value':\r\n if (typeof ast.property.name !== 'string') {\r\n throw this.astErrorOutput('Unexpected expression', ast);\r\n }\r\n name = ast.property.name;\r\n type = this.getConstantType(name);\r\n if (!type) {\r\n throw this.astErrorOutput('Constant has no type', ast);\r\n }\r\n return {\r\n name,\r\n type,\r\n origin: 'constants',\r\n signature: variableSignature,\r\n };\r\n case 'this.constants.value[]':\r\n if (typeof ast.object.property.name !== 'string') {\r\n throw this.astErrorOutput('Unexpected expression', ast);\r\n }\r\n name = ast.object.property.name;\r\n type = this.getConstantType(name);\r\n if (!type) {\r\n throw this.astErrorOutput('Constant has no type', ast);\r\n }\r\n return {\r\n name,\r\n type,\r\n origin: 'constants',\r\n signature: variableSignature,\r\n xProperty: ast.property,\r\n };\r\n case 'this.constants.value[][]': {\r\n if (typeof ast.object.object.property.name !== 'string') {\r\n throw this.astErrorOutput('Unexpected expression', ast);\r\n }\r\n name = ast.object.object.property.name;\r\n type = this.getConstantType(name);\r\n if (!type) {\r\n throw this.astErrorOutput('Constant has no type', ast);\r\n }\r\n return {\r\n name,\r\n type,\r\n origin: 'constants',\r\n signature: variableSignature,\r\n yProperty: ast.object.property,\r\n xProperty: ast.property,\r\n };\r\n }\r\n case 'this.constants.value[][][]': {\r\n if (typeof ast.object.object.object.property.name !== 'string') {\r\n throw this.astErrorOutput('Unexpected expression', ast);\r\n }\r\n name = ast.object.object.object.property.name;\r\n type = this.getConstantType(name);\r\n if (!type) {\r\n throw this.astErrorOutput('Constant has no type', ast);\r\n }\r\n return {\r\n name,\r\n type,\r\n origin: 'constants',\r\n signature: variableSignature,\r\n zProperty: ast.object.object.property,\r\n yProperty: ast.object.property,\r\n xProperty: ast.property,\r\n };\r\n }\r\n case 'fn()[]':\r\n case '[][]':\r\n return {\r\n signature: variableSignature,\r\n property: ast.property,\r\n };\r\n default:\r\n throw this.astErrorOutput('Unexpected expression', ast);\r\n }\r\n }\r\n\r\n findIdentifierOrigin(astToFind) {\r\n const stack = [this.ast];\r\n\r\n while (stack.length > 0) {\r\n const atNode = stack[0];\r\n if (atNode.type === 'VariableDeclarator' && atNode.id && atNode.id.name && atNode.id.name === astToFind.name) {\r\n return atNode;\r\n }\r\n stack.shift();\r\n if (atNode.argument) {\r\n stack.push(atNode.argument);\r\n } else if (atNode.body) {\r\n stack.push(atNode.body);\r\n } else if (atNode.declarations) {\r\n stack.push(atNode.declarations);\r\n } else if (Array.isArray(atNode)) {\r\n for (let i = 0; i < atNode.length; i++) {\r\n stack.push(atNode[i]);\r\n }\r\n }\r\n }\r\n return null;\r\n }\r\n\r\n findLastReturn(ast) {\r\n const stack = [ast || this.ast];\r\n\r\n while (stack.length > 0) {\r\n const atNode = stack.pop();\r\n if (atNode.type === 'ReturnStatement') {\r\n return atNode;\r\n }\r\n if (atNode.type === 'FunctionDeclaration') {\r\n continue;\r\n }\r\n if (atNode.argument) {\r\n stack.push(atNode.argument);\r\n } else if (atNode.body) {\r\n stack.push(atNode.body);\r\n } else if (atNode.declarations) {\r\n stack.push(atNode.declarations);\r\n } else if (Array.isArray(atNode)) {\r\n for (let i = 0; i < atNode.length; i++) {\r\n stack.push(atNode[i]);\r\n }\r\n } else if (atNode.consequent) {\r\n stack.push(atNode.consequent);\r\n } else if (atNode.cases) {\r\n stack.push(atNode.cases);\r\n }\r\n }\r\n return null;\r\n }\r\n\r\n getInternalVariableName(name) {\r\n if (!this._internalVariableNames.hasOwnProperty(name)) {\r\n this._internalVariableNames[name] = 0;\r\n }\r\n this._internalVariableNames[name]++;\r\n if (this._internalVariableNames[name] === 1) {\r\n return name;\r\n }\r\n return name + this._internalVariableNames[name];\r\n }\r\n\r\n varWarn() {\r\n console.warn('var declarations are deprecated, weird things happen when falling back to CPU because var scope differs in javascript than in most languages. Use const or let');\r\n }\r\n}\r\n\r\nconst typeLookupMap = {\r\n 'Number': 'Number',\r\n 'Float': 'Float',\r\n 'Integer': 'Integer',\r\n 'Array': 'Number',\r\n 'Array(2)': 'Number',\r\n 'Array(3)': 'Number',\r\n 'Array(4)': 'Number',\r\n 'Array2D': 'Number',\r\n 'Array3D': 'Number',\r\n 'Input': 'Number',\r\n 'HTMLImage': 'Array(4)',\r\n 'HTMLVideo': 'Array(4)',\r\n 'HTMLImageArray': 'Array(4)',\r\n 'NumberTexture': 'Number',\r\n 'MemoryOptimizedNumberTexture': 'Number',\r\n 'Array1D(2)': 'Array(2)',\r\n 'Array1D(3)': 'Array(3)',\r\n 'Array1D(4)': 'Array(4)',\r\n 'Array2D(2)': 'Array(2)',\r\n 'Array2D(3)': 'Array(3)',\r\n 'Array2D(4)': 'Array(4)',\r\n 'Array3D(2)': 'Array(2)',\r\n 'Array3D(3)': 'Array(3)',\r\n 'Array3D(4)': 'Array(4)',\r\n 'ArrayTexture(1)': 'Number',\r\n 'ArrayTexture(2)': 'Array(2)',\r\n 'ArrayTexture(3)': 'Array(3)',\r\n 'ArrayTexture(4)': 'Array(4)',\r\n};\r\n","import { FunctionNode } from '../function-node';\r\n\r\n/**\r\n * @desc [INTERNAL] Represents a single function, inside JS\r\n *\r\n *This handles all the raw state, converted state, etc. Of a single function.
\r\n */\r\nexport class CPUFunctionNode extends FunctionNode {\r\n /**\r\n * @desc Parses the abstract syntax tree for to its *named function*\r\n * @param {Object} ast - the AST object to parse\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astFunction(ast, retArr) {\r\n\r\n // Setup function return type and name\r\n if (!this.isRootKernel) {\r\n retArr.push('function');\r\n retArr.push(' ');\r\n retArr.push(this.name);\r\n retArr.push('(');\r\n\r\n // Arguments handling\r\n for (let i = 0; i < this.argumentNames.length; ++i) {\r\n const argumentName = this.argumentNames[i];\r\n\r\n if (i > 0) {\r\n retArr.push(', ');\r\n }\r\n retArr.push('user_');\r\n retArr.push(argumentName);\r\n }\r\n\r\n // Function opening\r\n retArr.push(') {\\n');\r\n }\r\n\r\n // Body statement iteration\r\n for (let i = 0; i < ast.body.body.length; ++i) {\r\n this.astGeneric(ast.body.body[i], retArr);\r\n retArr.push('\\n');\r\n }\r\n\r\n if (!this.isRootKernel) {\r\n // Function closing\r\n retArr.push('}\\n');\r\n }\r\n return retArr;\r\n }\r\n\r\n /**\r\n * @desc Parses the abstract syntax tree for to *return* statement\r\n * @param {Object} ast - the AST object to parse\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astReturnStatement(ast, retArr) {\r\n const type = this.returnType || this.getType(ast.argument);\r\n\r\n if (!this.returnType) {\r\n this.returnType = type;\r\n }\r\n\r\n if (this.isRootKernel) {\r\n retArr.push(this.leadingReturnStatement);\r\n this.astGeneric(ast.argument, retArr);\r\n retArr.push(';\\n');\r\n retArr.push(this.followingReturnStatement);\r\n retArr.push('continue;\\n');\r\n } else if (this.isSubKernel) {\r\n retArr.push(`subKernelResult_${ this.name } = `);\r\n this.astGeneric(ast.argument, retArr);\r\n retArr.push(';');\r\n retArr.push(`return subKernelResult_${ this.name };`);\r\n } else {\r\n retArr.push('return ');\r\n this.astGeneric(ast.argument, retArr);\r\n retArr.push(';');\r\n }\r\n return retArr;\r\n }\r\n\r\n /**\r\n * @desc Parses the abstract syntax tree for *literal value*\r\n * @param {Object} ast - the AST object to parse\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astLiteral(ast, retArr) {\r\n\r\n // Reject non numeric literals\r\n if (isNaN(ast.value)) {\r\n throw this.astErrorOutput(\r\n 'Non-numeric literal not supported : ' + ast.value,\r\n ast\r\n );\r\n }\r\n\r\n retArr.push(ast.value);\r\n\r\n return retArr;\r\n }\r\n\r\n /**\r\n * @desc Parses the abstract syntax tree for *binary* expression\r\n * @param {Object} ast - the AST object to parse\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astBinaryExpression(ast, retArr) {\r\n retArr.push('(');\r\n this.astGeneric(ast.left, retArr);\r\n retArr.push(ast.operator);\r\n this.astGeneric(ast.right, retArr);\r\n retArr.push(')');\r\n return retArr;\r\n }\r\n\r\n /**\r\n * @desc Parses the abstract syntax tree for *identifier* expression\r\n * @param {Object} idtNode - An ast Node\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astIdentifierExpression(idtNode, retArr) {\r\n if (idtNode.type !== 'Identifier') {\r\n throw this.astErrorOutput(\r\n 'IdentifierExpression - not an Identifier',\r\n idtNode\r\n );\r\n }\r\n\r\n switch (idtNode.name) {\r\n case 'Infinity':\r\n retArr.push('Infinity');\r\n break;\r\n default:\r\n if (this.constants && this.constants.hasOwnProperty(idtNode.name)) {\r\n retArr.push('constants_' + idtNode.name);\r\n } else {\r\n retArr.push('user_' + idtNode.name);\r\n }\r\n }\r\n\r\n return retArr;\r\n }\r\n\r\n /**\r\n * @desc Parses the abstract syntax tree for *for-loop* expression\r\n * @param {Object} forNode - An ast Node\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the parsed webgl string\r\n */\r\n astForStatement(forNode, retArr) {\r\n if (forNode.type !== 'ForStatement') {\r\n throw this.astErrorOutput('Invalid for statement', forNode);\r\n }\r\n\r\n const initArr = [];\r\n const testArr = [];\r\n const updateArr = [];\r\n const bodyArr = [];\r\n let isSafe = null;\r\n\r\n if (forNode.init) {\r\n this.pushState('in-for-loop-init');\r\n this.astGeneric(forNode.init, initArr);\r\n for (let i = 0; i < initArr.length; i++) {\r\n if (initArr[i].includes && initArr[i].includes(',')) {\r\n isSafe = false;\r\n }\r\n }\r\n this.popState('in-for-loop-init');\r\n } else {\r\n isSafe = false;\r\n }\r\n\r\n if (forNode.test) {\r\n this.astGeneric(forNode.test, testArr);\r\n } else {\r\n isSafe = false;\r\n }\r\n\r\n if (forNode.update) {\r\n this.astGeneric(forNode.update, updateArr);\r\n } else {\r\n isSafe = false;\r\n }\r\n\r\n if (forNode.body) {\r\n this.pushState('loop-body');\r\n this.astGeneric(forNode.body, bodyArr);\r\n this.popState('loop-body');\r\n }\r\n\r\n // have all parts, now make them safe\r\n if (isSafe === null) {\r\n isSafe = this.isSafe(forNode.init) && this.isSafe(forNode.test);\r\n }\r\n\r\n if (isSafe) {\r\n retArr.push(`for (${initArr.join('')};${testArr.join('')};${updateArr.join('')}){\\n`);\r\n retArr.push(bodyArr.join(''));\r\n retArr.push('}\\n');\r\n } else {\r\n const iVariableName = this.getInternalVariableName('safeI');\r\n if (initArr.length > 0) {\r\n retArr.push(initArr.join(''), ';\\n');\r\n }\r\n retArr.push(`for (let ${iVariableName}=0;${iVariableName}Instantiates properties to the CPU Kernel.
\r\n */\r\nexport class CPUKernel extends Kernel {\r\n static getFeatures() {\r\n return this.features;\r\n }\r\n static get features() {\r\n return Object.freeze({\r\n kernelMap: true,\r\n isIntegerDivisionAccurate: true\r\n });\r\n }\r\n static get isSupported() {\r\n return true;\r\n }\r\n static isContextMatch(context) {\r\n return false;\r\n }\r\n /**\r\n * @desc The current mode in which gpu.js is executing.\r\n */\r\n static get mode() {\r\n return 'cpu';\r\n }\r\n\r\n static nativeFunctionArguments() {\r\n return null;\r\n }\r\n\r\n static nativeFunctionReturnType() {\r\n return null;\r\n }\r\n\r\n static combineKernels(combinedKernel) {\r\n return combinedKernel;\r\n }\r\n\r\n constructor(source, settings) {\r\n super(source, settings);\r\n this.mergeSettings(source.settings || settings);\r\n\r\n this._imageData = null;\r\n this._colorData = null;\r\n this._kernelString = null;\r\n this.thread = {\r\n x: 0,\r\n y: 0,\r\n z: 0\r\n };\r\n this.translatedSources = null;\r\n }\r\n\r\n initCanvas() {\r\n if (typeof document !== 'undefined') {\r\n return document.createElement('canvas');\r\n } else if (typeof OffscreenCanvas !== 'undefined') {\r\n return new OffscreenCanvas(0, 0);\r\n }\r\n }\r\n\r\n initContext() {\r\n if (!this.canvas) return null;\r\n return this.canvas.getContext('2d');\r\n }\r\n\r\n initPlugins(settings) {\r\n return [];\r\n }\r\n\r\n /**\r\n * @desc Validate settings related to Kernel, such as dimensions size, and auto output support.\r\n * @param {IArguments} args\r\n */\r\n validateSettings(args) {\r\n if (!this.output || this.output.length === 0) {\r\n if (args.length !== 1) {\r\n throw new Error('Auto output only supported for kernels with only one input');\r\n }\r\n\r\n const argType = utils.getVariableType(args[0], this.strictIntegers);\r\n if (argType === 'Array') {\r\n this.output = utils.getDimensions(argType);\r\n } else if (argType === 'NumberTexture' || argType === 'ArrayTexture(4)') {\r\n this.output = args[0].output;\r\n } else {\r\n throw new Error('Auto output not supported for input type: ' + argType);\r\n }\r\n }\r\n\r\n if (this.graphical) {\r\n if (this.output.length !== 2) {\r\n throw new Error('Output must have 2 dimensions on graphical mode');\r\n }\r\n }\r\n\r\n this.checkOutput();\r\n }\r\n\r\n translateSource() {\r\n this.leadingReturnStatement = this.output.length > 1 ? 'resultX[x] = ' : 'result[x] = ';\r\n if (this.subKernels) {\r\n const followingReturnStatement = []\r\n for (let i = 0; i < this.subKernels.length; i++) {\r\n const {\r\n name\r\n } = this.subKernels[i];\r\n followingReturnStatement.push(this.output.length > 1 ? `resultX_${ name }[x] = subKernelResult_${ name };\\n` : `result_${ name }[x] = subKernelResult_${ name };\\n`);\r\n }\r\n this.followingReturnStatement = followingReturnStatement.join('');\r\n }\r\n const functionBuilder = FunctionBuilder.fromKernel(this, CPUFunctionNode);\r\n this.translatedSources = functionBuilder.getPrototypes('kernel');\r\n if (!this.graphical && !this.returnType) {\r\n this.returnType = functionBuilder.getKernelResultType();\r\n }\r\n }\r\n\r\n /**\r\n * @desc Builds the Kernel, by generating the kernel\r\n * string using thread dimensions, and arguments\r\n * supplied to the kernel.\r\n *\r\n *If the graphical flag is enabled, canvas is used.
\r\n */\r\n build() {\r\n this.setupConstants();\r\n this.setupArguments(arguments);\r\n this.validateSettings(arguments);\r\n this.translateSource();\r\n\r\n if (this.graphical) {\r\n const {\r\n canvas,\r\n output\r\n } = this;\r\n if (!canvas) {\r\n throw new Error('no canvas available for using graphical output');\r\n }\r\n const width = output[0];\r\n const height = output[1] || 1;\r\n canvas.width = width;\r\n canvas.height = height;\r\n this._imageData = this.context.createImageData(width, height);\r\n this._colorData = new Uint8ClampedArray(width * height * 4);\r\n }\r\n\r\n const kernelString = this.getKernelString();\r\n this.kernelString = kernelString;\r\n\r\n if (this.debug) {\r\n console.log('Function output:');\r\n console.log(kernelString);\r\n }\r\n\r\n try {\r\n this.run = new Function([], kernelString).bind(this)();\r\n } catch (e) {\r\n console.error('An error occurred compiling the javascript: ', e);\r\n }\r\n }\r\n\r\n color(r, g, b, a) {\r\n if (typeof a === 'undefined') {\r\n a = 1;\r\n }\r\n\r\n r = Math.floor(r * 255);\r\n g = Math.floor(g * 255);\r\n b = Math.floor(b * 255);\r\n a = Math.floor(a * 255);\r\n\r\n const width = this.output[0];\r\n const height = this.output[1];\r\n\r\n const x = this.thread.x;\r\n const y = height - this.thread.y - 1;\r\n\r\n const index = x + y * width;\r\n\r\n this._colorData[index * 4 + 0] = r;\r\n this._colorData[index * 4 + 1] = g;\r\n this._colorData[index * 4 + 2] = b;\r\n this._colorData[index * 4 + 3] = a;\r\n }\r\n\r\n /**\r\n * @desc Generates kernel string for this kernel program.\r\n *\r\n *If sub-kernels are supplied, they are also factored in.\r\n * This string can be saved by calling the `toString` method\r\n * and then can be reused later.
\r\n *\r\n * @returns {String} result\r\n *\r\n */\r\n getKernelString() {\r\n if (this._kernelString !== null) return this._kernelString;\r\n\r\n let kernelThreadString = null;\r\n let {\r\n translatedSources\r\n } = this;\r\n if (translatedSources.length > 1) {\r\n translatedSources = translatedSources.filter(fn => {\r\n if (/^function/.test(fn)) return fn;\r\n kernelThreadString = fn;\r\n return false;\r\n })\r\n } else {\r\n kernelThreadString = translatedSources.shift();\r\n }\r\n return this._kernelString = ` const LOOP_MAX = ${ this._getLoopMaxString() };\r\n ${ this.injectedNative || '' }\r\n const _this = this;\r\n ${ this._processConstants() }\r\n return (${ this.argumentNames.map(argumentName => 'user_' + argumentName).join(', ') }) => {\r\n ${ this._processArguments() }\r\n ${ this.graphical ? this._graphicalKernelBody(kernelThreadString) : this._resultKernelBody(kernelThreadString) }\r\n ${ translatedSources.length > 0 ? translatedSources.join('\\n') : '' }\r\n };`;\r\n }\r\n\r\n /**\r\n * @desc Returns the *pre-compiled* Kernel as a JS Object String, that can be reused.\r\n */\r\n toString() {\r\n return cpuKernelString(this);\r\n }\r\n\r\n /**\r\n * @desc Get the maximum loop size String.\r\n * @returns {String} result\r\n */\r\n _getLoopMaxString() {\r\n return (\r\n this.loopMaxIterations ?\r\n ` ${ parseInt(this.loopMaxIterations) };` :\r\n ' 1000;'\r\n );\r\n }\r\n\r\n _processConstants() {\r\n if (!this.constants) return '';\r\n\r\n const result = [];\r\n for (let p in this.constants) {\r\n const type = this.constantTypes[p];\r\n switch (type) {\r\n case 'HTMLImage':\r\n case 'HTMLVideo':\r\n result.push(` const constants_${p} = this._mediaTo2DArray(this.constants.${p});\\n`);\r\n break;\r\n case 'HTMLImageArray':\r\n result.push(` const constants_${p} = this._imageTo3DArray(this.constants.${p});\\n`);\r\n break;\r\n case 'Input':\r\n result.push(` const constants_${p} = this.constants.${p}.value;\\n`);\r\n break;\r\n default:\r\n result.push(` const constants_${p} = this.constants.${p};\\n`);\r\n }\r\n }\r\n return result.join('');\r\n }\r\n\r\n _processArguments() {\r\n const result = [];\r\n for (let i = 0; i < this.argumentTypes.length; i++) {\r\n const variableName = `user_${this.argumentNames[i]}`;\r\n switch (this.argumentTypes[i]) {\r\n case 'HTMLImage':\r\n case 'HTMLVideo':\r\n result.push(` ${variableName} = this._mediaTo2DArray(${variableName});\\n`);\r\n break;\r\n case 'HTMLImageArray':\r\n result.push(` ${variableName} = this._imageTo3DArray(${variableName});\\n`);\r\n break;\r\n case 'Input':\r\n result.push(` ${variableName} = ${variableName}.value;\\n`);\r\n break;\r\n case 'ArrayTexture(1)':\r\n case 'ArrayTexture(2)':\r\n case 'ArrayTexture(3)':\r\n case 'ArrayTexture(4)':\r\n case 'NumberTexture':\r\n case 'MemoryOptimizedNumberTexture':\r\n result.push(`\r\n if (${variableName}.toArray) {\r\n if (!_this.textureCache) {\r\n _this.textureCache = [];\r\n _this.arrayCache = [];\r\n }\r\n const textureIndex = _this.textureCache.indexOf(${variableName});\r\n if (textureIndex !== -1) {\r\n ${variableName} = _this.arrayCache[textureIndex];\r\n } else {\r\n _this.textureCache.push(${variableName});\r\n ${variableName} = ${variableName}.toArray();\r\n _this.arrayCache.push(${variableName});\r\n }\r\n }`);\r\n break;\r\n }\r\n }\r\n return result.join('');\r\n }\r\n\r\n _mediaTo2DArray(media) {\r\n const canvas = this.canvas;\r\n const width = media.width > 0 ? media.width : media.videoWidth;\r\n const height = media.height > 0 ? media.height : media.videoHeight;\r\n if (canvas.width < width) {\r\n canvas.width = width;\r\n }\r\n if (canvas.height < height) {\r\n canvas.height = height;\r\n }\r\n const ctx = this.context;\r\n ctx.drawImage(media, 0, 0, width, height);\r\n const pixelsData = ctx.getImageData(0, 0, width, height).data;\r\n const imageArray = new Array(height);\r\n let index = 0;\r\n for (let y = height - 1; y >= 0; y--) {\r\n const row = imageArray[y] = new Array(width);\r\n for (let x = 0; x < width; x++) {\r\n const pixel = new Float32Array(4);\r\n pixel[0] = pixelsData[index++] / 255; // r\r\n pixel[1] = pixelsData[index++] / 255; // g\r\n pixel[2] = pixelsData[index++] / 255; // b\r\n pixel[3] = pixelsData[index++] / 255; // a\r\n row[x] = pixel;\r\n }\r\n }\r\n return imageArray;\r\n }\r\n\r\n getPixels(flip) {\r\n const [width, height] = this.output;\r\n // cpu is not flipped by default\r\n return flip ? utils.flipPixels(this._imageData.data, width, height) : this._imageData.data.slice(0);\r\n }\r\n\r\n _imageTo3DArray(images) {\r\n const imagesArray = new Array(images.length);\r\n for (let i = 0; i < images.length; i++) {\r\n imagesArray[i] = this._mediaTo2DArray(images[i]);\r\n }\r\n return imagesArray;\r\n }\r\n\r\n _resultKernelBody(kernelString) {\r\n switch (this.output.length) {\r\n case 1:\r\n return this._resultKernel1DLoop(kernelString) + this._kernelOutput();\r\n case 2:\r\n return this._resultKernel2DLoop(kernelString) + this._kernelOutput();\r\n case 3:\r\n return this._resultKernel3DLoop(kernelString) + this._kernelOutput();\r\n default:\r\n throw new Error('unsupported size kernel');\r\n }\r\n }\r\n\r\n _graphicalKernelBody(kernelThreadString) {\r\n switch (this.output.length) {\r\n case 2:\r\n return this._graphicalKernel2DLoop(kernelThreadString) + this._graphicalOutput();\r\n default:\r\n throw new Error('unsupported size kernel');\r\n }\r\n }\r\n\r\n _graphicalOutput() {\r\n return `\r\n this._imageData.data.set(this._colorData);\r\n this.context.putImageData(this._imageData, 0, 0);\r\n return;`\r\n }\r\n\r\n _getKernelResultTypeConstructorString() {\r\n switch (this.returnType) {\r\n case 'LiteralInteger':\r\n case 'Number':\r\n case 'Integer':\r\n case 'Float':\r\n return 'Float32Array';\r\n case 'Array(2)':\r\n case 'Array(3)':\r\n case 'Array(4)':\r\n return 'Array';\r\n default:\r\n if (this.graphical) {\r\n return 'Float32Array';\r\n }\r\n throw new Error(`unhandled returnType ${ this.returnType }`);\r\n }\r\n }\r\n\r\n _resultKernel1DLoop(kernelString) {\r\n const {\r\n output\r\n } = this;\r\n const constructorString = this._getKernelResultTypeConstructorString();\r\n return ` const outputX = _this.output[0];\r\n const result = new ${constructorString}(outputX);\r\n ${ this._mapSubKernels(subKernel => `const result_${ subKernel.name } = new ${constructorString}(outputX);\\n`).join(' ') }\r\n ${ this._mapSubKernels(subKernel => `let subKernelResult_${ subKernel.name };\\n`).join(' ') }\r\n for (let x = 0; x < outputX; x++) {\r\n this.thread.x = x;\r\n this.thread.y = 0;\r\n this.thread.z = 0;\r\n ${ kernelString }\r\n }`;\r\n }\r\n\r\n _resultKernel2DLoop(kernelString) {\r\n const {\r\n output\r\n } = this;\r\n const constructorString = this._getKernelResultTypeConstructorString();\r\n return ` const outputX = _this.output[0];\r\n const outputY = _this.output[1];\r\n const result = new Array(outputY);\r\n ${ this._mapSubKernels(subKernel => `const result_${ subKernel.name } = new Array(outputY);\\n`).join(' ') }\r\n ${ this._mapSubKernels(subKernel => `let subKernelResult_${ subKernel.name };\\n`).join(' ') }\r\n for (let y = 0; y < outputY; y++) {\r\n this.thread.z = 0;\r\n this.thread.y = y;\r\n const resultX = result[y] = new ${constructorString}(outputX);\r\n ${ this._mapSubKernels(subKernel => `const resultX_${ subKernel.name } = result_${subKernel.name}[y] = new ${constructorString}(outputX);\\n`).join('') }\r\n for (let x = 0; x < outputX; x++) {\r\n this.thread.x = x;\r\n ${ kernelString }\r\n }\r\n }`;\r\n }\r\n\r\n _graphicalKernel2DLoop(kernelString) {\r\n const {\r\n output\r\n } = this;\r\n const constructorString = this._getKernelResultTypeConstructorString();\r\n return ` const outputX = _this.output[0];\r\n const outputY = _this.output[1];\r\n ${ this._mapSubKernels(subKernel => `const result_${ subKernel.name } = new Array(outputY);\\n`).join(' ') }\r\n ${ this._mapSubKernels(subKernel => `let subKernelResult_${ subKernel.name };\\n`).join(' ') }\r\n for (let y = 0; y < outputY; y++) {\r\n this.thread.z = 0;\r\n this.thread.y = y;\r\n ${ this._mapSubKernels(subKernel => `const resultX_${ subKernel.name } = result_${subKernel.name}[y] = new ${constructorString}(outputX);\\n`).join('') }\r\n for (let x = 0; x < outputX; x++) {\r\n this.thread.x = x;\r\n ${ kernelString }\r\n }\r\n }`;\r\n }\r\n\r\n _resultKernel3DLoop(kernelString) {\r\n const {\r\n output\r\n } = this;\r\n const constructorString = this._getKernelResultTypeConstructorString();\r\n return ` const outputX = _this.output[0];\r\n const outputY = _this.output[1];\r\n const outputZ = _this.output[2];\r\n const result = new Array(outputZ);\r\n ${ this._mapSubKernels(subKernel => `const result_${ subKernel.name } = new Array(outputZ);\\n`).join(' ') }\r\n ${ this._mapSubKernels(subKernel => `let subKernelResult_${ subKernel.name };\\n`).join(' ') }\r\n for (let z = 0; z < outputZ; z++) {\r\n this.thread.z = z;\r\n const resultY = result[z] = new Array(outputY);\r\n ${ this._mapSubKernels(subKernel => `const resultY_${ subKernel.name } = result_${subKernel.name}[z] = new Array(outputY);\\n`).join(' ') }\r\n for (let y = 0; y < outputY; y++) {\r\n this.thread.y = y;\r\n const resultX = resultY[y] = new ${constructorString}(outputX);\r\n ${ this._mapSubKernels(subKernel => `const resultX_${ subKernel.name } = resultY_${subKernel.name}[y] = new ${constructorString}(outputX);\\n`).join(' ') }\r\n for (let x = 0; x < outputX; x++) {\r\n this.thread.x = x;\r\n ${ kernelString }\r\n }\r\n }\r\n }`;\r\n }\r\n\r\n _kernelOutput() {\r\n if (!this.subKernels) {\r\n return '\\n return result;';\r\n }\r\n return `\\n return {\r\n result: result,\r\n ${ this.subKernels.map(subKernel => `${ subKernel.property }: result_${ subKernel.name }`).join(',\\n ') }\r\n };`;\r\n }\r\n\r\n _mapSubKernels(fn) {\r\n return this.subKernels === null ? [''] :\r\n this.subKernels.map(fn);\r\n }\r\n\r\n\r\n\r\n destroy(removeCanvasReference) {\r\n if (removeCanvasReference) {\r\n delete this.canvas;\r\n }\r\n }\r\n\r\n static destroyContext(context) {}\r\n\r\n toJSON() {\r\n const json = super.toJSON();\r\n json.functionNodes = FunctionBuilder.fromKernel(this, CPUFunctionNode).toJSON();\r\n return json;\r\n }\r\n\r\n setOutput(output) {\r\n super.setOutput(output);\r\n const [width, height] = this.output;\r\n if (this.graphical) {\r\n this._imageData = this.context.createImageData(width, height);\r\n this._colorData = new Uint8ClampedArray(width * height * 4);\r\n }\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { Texture } from '../../../texture';\r\n\r\nexport class GLTextureFloat extends Texture {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'ArrayTexture(1)';\r\n }\r\n renderRawOutput() {\r\n const { context: gl } = this;\r\n const framebuffer = gl.createFramebuffer();\r\n gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);\r\n gl.framebufferTexture2D(\r\n gl.FRAMEBUFFER,\r\n gl.COLOR_ATTACHMENT0,\r\n gl.TEXTURE_2D,\r\n this.texture,\r\n 0\r\n );\r\n const result = new Float32Array(this.size[0] * this.size[1] * 4);\r\n gl.readPixels(0, 0, this.size[0], this.size[1], gl.RGBA, gl.FLOAT, result);\r\n return result;\r\n }\r\n renderValues() {\r\n return this.renderRawOutput();\r\n }\r\n toArray() {\r\n return utils.erectFloat(this.renderValues(), this.output[0]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureFloat } from './float';\r\n\r\nexport class GLTextureArray2Float extends GLTextureFloat {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'ArrayTexture(2)';\r\n }\r\n toArray() {\r\n return utils.erectArray2(this.renderValues(), this.output[0], this.output[1]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureFloat } from './float';\r\n\r\nexport class GLTextureArray2Float2D extends GLTextureFloat {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'ArrayTexture(2)';\r\n }\r\n toArray() {\r\n return utils.erect2DArray2(this.renderValues(), this.output[0], this.output[1]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureFloat } from './float';\r\n\r\nexport class GLTextureArray2Float3D extends GLTextureFloat {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'ArrayTexture(2)';\r\n }\r\n toArray() {\r\n return utils.erect3DArray2(this.renderValues(), this.output[0], this.output[1], this.output[2]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureFloat } from './float';\r\n\r\nexport class GLTextureArray3Float extends GLTextureFloat {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'ArrayTexture(3)';\r\n }\r\n toArray() {\r\n return utils.erectArray3(this.renderValues(), this.output[0]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureFloat } from './float';\r\n\r\nexport class GLTextureArray3Float2D extends GLTextureFloat {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'ArrayTexture(3)';\r\n }\r\n toArray() {\r\n return utils.erect2DArray3(this.renderValues(), this.output[0], this.output[1]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureFloat } from './float';\r\n\r\nexport class GLTextureArray3Float3D extends GLTextureFloat {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'ArrayTexture(3)';\r\n }\r\n toArray() {\r\n return utils.erect3DArray3(this.renderValues(), this.output[0], this.output[1], this.output[2]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureFloat } from './float';\r\n\r\nexport class GLTextureArray4Float extends GLTextureFloat {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'ArrayTexture(4)';\r\n }\r\n toArray() {\r\n return utils.erectArray4(this.renderValues(), this.output[0]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureFloat } from './float';\r\n\r\nexport class GLTextureArray4Float2D extends GLTextureFloat {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'ArrayTexture(4)';\r\n }\r\n toArray() {\r\n return utils.erect2DArray4(this.renderValues(), this.output[0], this.output[1]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureFloat } from './float';\r\n\r\nexport class GLTextureArray4Float3D extends GLTextureFloat {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'ArrayTexture(4)';\r\n }\r\n toArray() {\r\n return utils.erect3DArray4(this.renderValues(), this.output[0], this.output[1], this.output[2]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureFloat } from './float';\r\n\r\nexport class GLTextureFloat2D extends GLTextureFloat {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'ArrayTexture(1)';\r\n }\r\n toArray() {\r\n return utils.erect2DFloat(this.renderValues(), this.output[0], this.output[1]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureFloat } from './float';\r\n\r\nexport class GLTextureFloat3D extends GLTextureFloat {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'ArrayTexture(1)';\r\n }\r\n toArray() {\r\n return utils.erect3DFloat(this.renderValues(), this.output[0], this.output[1], this.output[2]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureFloat } from './float';\r\n\r\nexport class GLTextureMemoryOptimized extends GLTextureFloat {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'MemoryOptimizedNumberTexture';\r\n }\r\n toArray() {\r\n return utils.erectMemoryOptimizedFloat(this.renderValues(), this.output[0]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureFloat } from './float';\r\n\r\nexport class GLTextureMemoryOptimized2D extends GLTextureFloat {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'MemoryOptimizedNumberTexture';\r\n }\r\n toArray() {\r\n return utils.erectMemoryOptimized2DFloat(this.renderValues(), this.output[0], this.output[1]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureFloat } from './float';\r\n\r\nexport class GLTextureMemoryOptimized3D extends GLTextureFloat {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'MemoryOptimizedNumberTexture';\r\n }\r\n toArray() {\r\n return utils.erectMemoryOptimized3DFloat(this.renderValues(), this.output[0], this.output[1], this.output[2]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { Texture } from '../../../texture';\r\n\r\nexport class GLTextureUnsigned extends Texture {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'NumberTexture';\r\n }\r\n renderRawOutput() {\r\n const { context: gl } = this;\r\n const framebuffer = gl.createFramebuffer();\r\n gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);\r\n gl.framebufferTexture2D(\r\n gl.FRAMEBUFFER,\r\n gl.COLOR_ATTACHMENT0,\r\n gl.TEXTURE_2D,\r\n this.texture,\r\n 0\r\n );\r\n const result = new Uint8Array(this.size[0] * this.size[1] * 4);\r\n gl.readPixels(0, 0, this.size[0], this.size[1], gl.RGBA, gl.UNSIGNED_BYTE, result);\r\n return result;\r\n }\r\n renderValues() {\r\n return new Float32Array(this.renderRawOutput().buffer);\r\n }\r\n toArray() {\r\n return utils.erectPackedFloat(this.renderValues(), this.output[0]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureUnsigned } from './unsigned';\r\n\r\nexport class GLTextureUnsigned2D extends GLTextureUnsigned {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'NumberTexture';\r\n }\r\n toArray() {\r\n return utils.erect2DPackedFloat(this.renderValues(), this.output[0], this.output[1]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureUnsigned } from './unsigned';\r\n\r\nexport class GLTextureUnsigned3D extends GLTextureUnsigned {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'NumberTexture';\r\n }\r\n toArray() {\r\n return utils.erect3DPackedFloat(this.renderValues(), this.output[0], this.output[1], this.output[2]);\r\n }\r\n}\r\n","import { GLTextureUnsigned } from './unsigned';\r\n\r\nexport class GLTextureGraphical extends GLTextureUnsigned {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'ArrayTexture(4)';\r\n }\r\n toArray() {\r\n return this.renderValues();\r\n }\r\n}\r\n","import { Kernel } from '../kernel';\r\nimport { utils } from '../../utils';\r\nimport { GLTextureArray2Float } from './texture/array-2-float';\r\nimport { GLTextureArray2Float2D } from './texture/array-2-float-2d';\r\nimport { GLTextureArray2Float3D } from './texture/array-2-float-3d';\r\nimport { GLTextureArray3Float } from './texture/array-3-float';\r\nimport { GLTextureArray3Float2D } from './texture/array-3-float-2d';\r\nimport { GLTextureArray3Float3D } from './texture/array-3-float-3d';\r\nimport { GLTextureArray4Float } from './texture/array-4-float';\r\nimport { GLTextureArray4Float2D } from './texture/array-4-float-2d';\r\nimport { GLTextureArray4Float3D } from './texture/array-4-float-3d';\r\nimport { GLTextureFloat } from './texture/float';\r\nimport { GLTextureFloat2D } from './texture/float-2d';\r\nimport { GLTextureFloat3D } from './texture/float-3d';\r\nimport { GLTextureMemoryOptimized } from './texture/memory-optimized';\r\nimport { GLTextureMemoryOptimized2D } from './texture/memory-optimized-2d';\r\nimport { GLTextureMemoryOptimized3D } from './texture/memory-optimized-3d';\r\nimport { GLTextureUnsigned } from './texture/unsigned';\r\nimport { GLTextureUnsigned2D } from './texture/unsigned-2d';\r\nimport { GLTextureUnsigned3D } from './texture/unsigned-3d';\r\nimport { GLTextureGraphical } from './texture/graphical';\r\n\r\n/**\r\n * @abstract\r\n * @extends Kernel\r\n */\r\nexport class GLKernel extends Kernel {\r\n static get mode() {\r\n return 'gpu';\r\n }\r\n\r\n static getIsFloatRead() {\r\n const kernelString = `function kernelFunction() {\r\n return 1;\r\n }`;\r\n const kernel = new this(kernelString, {\r\n context: this.testContext,\r\n canvas: this.testCanvas,\r\n validate: false,\r\n output: [1],\r\n precision: 'single',\r\n returnType: 'Number',\r\n tactic: 'speed',\r\n });\r\n kernel.build();\r\n kernel.run();\r\n const result = kernel.renderOutput();\r\n kernel.destroy(true);\r\n return result[0] === 1;\r\n }\r\n\r\n static getIsIntegerDivisionAccurate() {\r\n function kernelFunction(v1, v2) {\r\n return v1[this.thread.x] / v2[this.thread.x];\r\n }\r\n const kernel = new this(kernelFunction.toString(), {\r\n context: this.testContext,\r\n canvas: this.testCanvas,\r\n validate: false,\r\n output: [2],\r\n returnType: 'Number',\r\n precision: 'unsigned',\r\n tactic: 'speed',\r\n });\r\n const args = [\r\n [6, 6030401],\r\n [3, 3991]\r\n ];\r\n kernel.build.apply(kernel, args);\r\n kernel.run.apply(kernel, args);\r\n const result = kernel.renderOutput();\r\n kernel.destroy(true);\r\n // have we not got whole numbers for 6/3 or 6030401/3991\r\n // add more here if others see this problem\r\n return result[0] === 2 && result[1] === 1511;\r\n }\r\n\r\n /**\r\n * @abstract\r\n */\r\n static get testCanvas() {\r\n throw new Error(`\"testCanvas\" not defined on ${ this.name }`);\r\n }\r\n\r\n /**\r\n * @abstract\r\n */\r\n static get testContext() {\r\n throw new Error(`\"testContext\" not defined on ${ this.name }`);\r\n }\r\n\r\n /**\r\n * @type {IKernelFeatures}\r\n */\r\n static get features() {\r\n throw new Error(`\"features\" not defined on ${ this.name }`);\r\n }\r\n\r\n /**\r\n * @abstract\r\n */\r\n static setupFeatureChecks() {\r\n throw new Error(`\"setupFeatureChecks\" not defined on ${ this.name }`);\r\n }\r\n\r\n /**\r\n * @desc Fix division by factor of 3 FP accuracy bug\r\n * @param {Boolean} fix - should fix\r\n */\r\n setFixIntegerDivisionAccuracy(fix) {\r\n this.fixIntegerDivisionAccuracy = fix;\r\n return this;\r\n }\r\n\r\n /**\r\n * @desc Toggle output mode\r\n * @param {String} flag - 'single' or 'unsigned'\r\n */\r\n setPrecision(flag) {\r\n this.precision = flag;\r\n return this;\r\n }\r\n\r\n /**\r\n * @desc Toggle texture output mode\r\n * @param {Boolean} flag - true to enable floatTextures\r\n * @deprecated\r\n */\r\n setFloatTextures(flag) {\r\n utils.warnDeprecated('method', 'setFloatTextures', 'setOptimizeFloatMemory');\r\n this.floatTextures = flag;\r\n return this;\r\n }\r\n\r\n /**\r\n * A highly readable very forgiving micro-parser for a glsl function that gets argument types\r\n * @param {String} source\r\n * @returns {{argumentTypes: String[], argumentNames: String[]}}\r\n */\r\n static nativeFunctionArguments(source) {\r\n const argumentTypes = [];\r\n const argumentNames = [];\r\n const states = [];\r\n const isStartingVariableName = /^[a-zA-Z_]/;\r\n const isVariableChar = /[a-zA-Z_0-9]/;\r\n let i = 0;\r\n let argumentName = null;\r\n let argumentType = null;\r\n while (i < source.length) {\r\n const char = source[i];\r\n const nextChar = source[i + 1];\r\n const state = states.length > 0 ? states[states.length - 1] : null;\r\n\r\n // begin MULTI_LINE_COMMENT handling\r\n if (state === 'FUNCTION_ARGUMENTS' && char === '/' && nextChar === '*') {\r\n states.push('MULTI_LINE_COMMENT');\r\n i += 2;\r\n continue;\r\n } else if (state === 'MULTI_LINE_COMMENT' && char === '*' && nextChar === '/') {\r\n states.pop();\r\n i += 2;\r\n continue;\r\n }\r\n // end MULTI_LINE_COMMENT handling\r\n\r\n // begin COMMENT handling\r\n else if (state === 'FUNCTION_ARGUMENTS' && char === '/' && nextChar === '/') {\r\n states.push('COMMENT');\r\n i += 2;\r\n continue;\r\n } else if (state === 'COMMENT' && char === '\\n') {\r\n states.pop();\r\n i++;\r\n continue;\r\n }\r\n // end COMMENT handling\r\n\r\n // being FUNCTION_ARGUMENTS handling\r\n else if (state === null && char === '(') {\r\n states.push('FUNCTION_ARGUMENTS');\r\n i++;\r\n continue;\r\n } else if (state === 'FUNCTION_ARGUMENTS') {\r\n if (char === ')') {\r\n states.pop();\r\n break;\r\n }\r\n if (char === 'f' && nextChar === 'l' && source[i + 2] === 'o' && source[i + 3] === 'a' && source[i + 4] === 't' && source[i + 5] === ' ') {\r\n states.push('DECLARE_VARIABLE');\r\n argumentType = 'float';\r\n argumentName = '';\r\n i += 6;\r\n continue;\r\n } else if (char === 'i' && nextChar === 'n' && source[i + 2] === 't' && source[i + 3] === ' ') {\r\n states.push('DECLARE_VARIABLE');\r\n argumentType = 'int';\r\n argumentName = '';\r\n i += 4;\r\n continue;\r\n } else if (char === 'v' && nextChar === 'e' && source[i + 2] === 'c' && source[i + 3] === '2' && source[i + 4] === ' ') {\r\n states.push('DECLARE_VARIABLE');\r\n argumentType = 'vec2';\r\n argumentName = '';\r\n i += 5;\r\n continue;\r\n } else if (char === 'v' && nextChar === 'e' && source[i + 2] === 'c' && source[i + 3] === '3' && source[i + 4] === ' ') {\r\n states.push('DECLARE_VARIABLE');\r\n argumentType = 'vec3';\r\n argumentName = '';\r\n i += 5;\r\n continue;\r\n } else if (char === 'v' && nextChar === 'e' && source[i + 2] === 'c' && source[i + 3] === '4' && source[i + 4] === ' ') {\r\n states.push('DECLARE_VARIABLE');\r\n argumentType = 'vec4';\r\n argumentName = '';\r\n i += 5;\r\n continue;\r\n }\r\n }\r\n // end FUNCTION_ARGUMENTS handling\r\n\r\n // begin DECLARE_VARIABLE handling\r\n else if (state === 'DECLARE_VARIABLE') {\r\n if (argumentName === '') {\r\n if (char === ' ') {\r\n i++;\r\n continue;\r\n }\r\n if (!isStartingVariableName.test(char)) {\r\n throw new Error('variable name is not expected string');\r\n }\r\n }\r\n argumentName += char;\r\n if (!isVariableChar.test(nextChar)) {\r\n states.pop();\r\n argumentNames.push(argumentName);\r\n argumentTypes.push(typeMap[argumentType]);\r\n }\r\n }\r\n // end DECLARE_VARIABLE handling\r\n\r\n // Progress to next character\r\n i++;\r\n }\r\n if (states.length > 0) {\r\n throw new Error('GLSL function was not parsable');\r\n }\r\n return {\r\n argumentNames,\r\n argumentTypes,\r\n };\r\n }\r\n\r\n static nativeFunctionReturnType(source) {\r\n return typeMap[source.match(/int|float|vec[2-4]/)[0]];\r\n }\r\n\r\n static combineKernels(combinedKernel, lastKernel) {\r\n combinedKernel.apply(null, arguments);\r\n const {\r\n texSize,\r\n context,\r\n threadDim\r\n } = lastKernel.texSize;\r\n let result;\r\n if (lastKernel.precision === 'single') {\r\n const w = texSize[0];\r\n const h = Math.ceil(texSize[1] / 4);\r\n result = new Float32Array(w * h * 4 * 4);\r\n context.readPixels(0, 0, w, h * 4, context.RGBA, context.FLOAT, result);\r\n } else {\r\n const bytes = new Uint8Array(texSize[0] * texSize[1] * 4);\r\n context.readPixels(0, 0, texSize[0], texSize[1], context.RGBA, context.UNSIGNED_BYTE, bytes);\r\n result = new Float32Array(bytes.buffer);\r\n }\r\n\r\n result = result.subarray(0, threadDim[0] * threadDim[1] * threadDim[2]);\r\n\r\n if (lastKernel.output.length === 1) {\r\n return result;\r\n } else if (lastKernel.output.length === 2) {\r\n return utils.splitArray(result, lastKernel.output[0]);\r\n } else if (lastKernel.output.length === 3) {\r\n const cube = utils.splitArray(result, lastKernel.output[0] * lastKernel.output[1]);\r\n return cube.map(function(x) {\r\n return utils.splitArray(x, lastKernel.output[0]);\r\n });\r\n }\r\n }\r\n\r\n constructor(source, settings) {\r\n super(source, settings);\r\n this.transferValues = null;\r\n this.formatValues = null;\r\n this.TextureConstructor = null;\r\n this.renderOutput = null;\r\n this.renderRawOutput = null;\r\n this.texSize = null;\r\n this.translatedSource = null;\r\n this.renderStrategy = null;\r\n this.compiledFragmentShader = null;\r\n this.compiledVertexShader = null;\r\n }\r\n\r\n checkTextureSize() {\r\n const { features } = this.constructor;\r\n if (this.texSize[0] > features.maxTextureSize || this.texSize[1] > features.maxTextureSize) {\r\n throw new Error(`Texture size [${this.texSize[0]},${this.texSize[1]}] generated by kernel is larger than supported size [${features.maxTextureSize},${features.maxTextureSize}]`);\r\n }\r\n }\r\n\r\n translateSource() {\r\n throw new Error(`\"translateSource\" not defined on ${this.constructor.name}`);\r\n }\r\n\r\n /**\r\n * Picks a render strategy for the now finally parsed kernel\r\n * @param args\r\n * @return {null|KernelOutput}\r\n */\r\n pickRenderStrategy(args) {\r\n if (this.graphical) {\r\n this.renderRawOutput = this.readPackedPixelsToUint8Array;\r\n this.transferValues = (pixels) => pixels;\r\n this.TextureConstructor = GLTextureGraphical;\r\n return null;\r\n }\r\n if (this.precision === 'unsigned') {\r\n this.renderRawOutput = this.readPackedPixelsToUint8Array;\r\n this.transferValues = this.readPackedPixelsToFloat32Array;\r\n if (this.pipeline) {\r\n this.renderOutput = this.renderTexture;\r\n if (this.subKernels !== null) {\r\n this.renderKernels = this.renderKernelsToTextures;\r\n }\r\n switch (this.returnType) {\r\n case 'LiteralInteger':\r\n case 'Float':\r\n case 'Number':\r\n case 'Integer':\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureUnsigned3D;\r\n this.renderStrategy = renderStrategy.PackedPixelTo3DFloat;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureUnsigned2D;\r\n this.renderStrategy = renderStrategy.PackedPixelTo2DFloat;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureUnsigned;\r\n this.renderStrategy = renderStrategy.PackedPixelToFloat;\r\n return null;\r\n }\r\n break;\r\n case 'Array(2)':\r\n case 'Array(3)':\r\n case 'Array(4)':\r\n return this.requestFallback(args);\r\n }\r\n } else {\r\n if (this.subKernels !== null) {\r\n this.renderKernels = this.renderKernelsToArrays;\r\n }\r\n switch (this.returnType) {\r\n case 'LiteralInteger':\r\n case 'Float':\r\n case 'Number':\r\n case 'Integer':\r\n this.renderOutput = this.renderValues;\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureUnsigned3D;\r\n this.renderStrategy = renderStrategy.PackedPixelTo3DFloat;\r\n this.formatValues = utils.erect3DPackedFloat;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureUnsigned2D;\r\n this.renderStrategy = renderStrategy.PackedPixelTo2DFloat;\r\n this.formatValues = utils.erect2DPackedFloat;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureUnsigned;\r\n this.renderStrategy = renderStrategy.PackedPixelToFloat;\r\n this.formatValues = utils.erectPackedFloat;\r\n return null;\r\n }\r\n\r\n break;\r\n case 'Array(2)':\r\n case 'Array(3)':\r\n case 'Array(4)':\r\n return this.requestFallback(args);\r\n }\r\n }\r\n } else if (this.precision === 'single') {\r\n this.renderRawOutput = this.readFloatPixelsToFloat32Array;\r\n this.transferValues = this.readFloatPixelsToFloat32Array;\r\n if (this.pipeline) {\r\n this.renderStrategy = renderStrategy.FloatTexture;\r\n this.renderOutput = this.renderTexture;\r\n if (this.subKernels !== null) {\r\n this.renderKernels = this.renderKernelsToTextures;\r\n }\r\n switch (this.returnType) {\r\n case 'LiteralInteger':\r\n case 'Float':\r\n case 'Number':\r\n case 'Integer':\r\n if (this.optimizeFloatMemory) {\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureMemoryOptimized3D;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureMemoryOptimized2D;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureMemoryOptimized;\r\n return null;\r\n }\r\n } else {\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureFloat3D;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureFloat2D;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureFloat;\r\n return null;\r\n }\r\n }\r\n break;\r\n case 'Array(2)':\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureArray2Float3D;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureArray2Float2D;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureArray2Float;\r\n return null;\r\n }\r\n break;\r\n case 'Array(3)':\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureArray3Float3D;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureArray3Float2D;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureArray3Float;\r\n return null;\r\n }\r\n break;\r\n case 'Array(4)':\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureArray4Float3D;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureArray4Float2D;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureArray4Float;\r\n return null;\r\n }\r\n }\r\n }\r\n this.renderOutput = this.renderValues;\r\n if (this.subKernels !== null) {\r\n this.renderKernels = this.renderKernelsToArrays;\r\n }\r\n if (this.optimizeFloatMemory) {\r\n switch (this.returnType) {\r\n case 'LiteralInteger':\r\n case 'Float':\r\n case 'Number':\r\n case 'Integer':\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureMemoryOptimized3D;\r\n this.renderStrategy = renderStrategy.MemoryOptimizedFloatPixelToMemoryOptimized3DFloat;\r\n this.formatValues = utils.erectMemoryOptimized3DFloat;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureMemoryOptimized2D;\r\n this.renderStrategy = renderStrategy.MemoryOptimizedFloatPixelToMemoryOptimized2DFloat;\r\n this.formatValues = utils.erectMemoryOptimized2DFloat;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureMemoryOptimized;\r\n this.renderStrategy = renderStrategy.MemoryOptimizedFloatPixelToMemoryOptimizedFloat;\r\n this.formatValues = utils.erectMemoryOptimizedFloat;\r\n return null;\r\n }\r\n break;\r\n case 'Array(2)':\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureArray2Float3D;\r\n this.renderStrategy = renderStrategy.FloatPixelTo3DArray2;\r\n this.formatValues = utils.erect3DArray2;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureArray2Float2D;\r\n this.renderStrategy = renderStrategy.FloatPixelTo2DArray2;\r\n this.formatValues = utils.erect2DArray2;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureArray2Float;\r\n this.renderStrategy = renderStrategy.FloatPixelToArray2;\r\n this.formatValues = utils.erectArray2;\r\n return null;\r\n }\r\n break;\r\n case 'Array(3)':\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureArray3Float3D;\r\n this.renderStrategy = renderStrategy.FloatPixelTo3DArray3;\r\n this.formatValues = utils.erect3DArray3;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureArray3Float2D;\r\n this.renderStrategy = renderStrategy.FloatPixelTo2DArray3;\r\n this.formatValues = utils.erect2DArray3;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureArray3Float;\r\n this.renderStrategy = renderStrategy.FloatPixelToArray3;\r\n this.formatValues = utils.erectArray3;\r\n return null;\r\n }\r\n break;\r\n case 'Array(4)':\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureArray4Float3D;\r\n this.renderStrategy = renderStrategy.FloatPixelTo3DArray4;\r\n this.formatValues = utils.erect3DArray4;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureArray4Float2D;\r\n this.renderStrategy = renderStrategy.FloatPixelTo2DArray4;\r\n this.formatValues = utils.erect2DArray4;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureArray4Float;\r\n this.renderStrategy = renderStrategy.FloatPixelToArray4;\r\n this.formatValues = utils.erectArray4;\r\n return null;\r\n }\r\n }\r\n } else {\r\n switch (this.returnType) {\r\n case 'LiteralInteger':\r\n case 'Float':\r\n case 'Number':\r\n case 'Integer':\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureFloat3D;\r\n this.renderStrategy = renderStrategy.FloatPixelTo3DFloat;\r\n this.formatValues = utils.erect3DFloat;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureFloat2D;\r\n this.renderStrategy = renderStrategy.FloatPixelTo2DFloat;\r\n this.formatValues = utils.erect2DFloat;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureFloat;\r\n this.renderStrategy = renderStrategy.FloatPixelToFloat;\r\n this.formatValues = utils.erectFloat;\r\n return null;\r\n }\r\n break;\r\n case 'Array(2)':\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureArray2Float3D;\r\n this.renderStrategy = renderStrategy.FloatPixelTo3DArray2;\r\n this.formatValues = utils.erect3DArray2;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureArray2Float2D;\r\n this.renderStrategy = renderStrategy.FloatPixelTo2DArray2;\r\n this.formatValues = utils.erect2DArray2;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureArray2Float;\r\n this.renderStrategy = renderStrategy.FloatPixelToArray2;\r\n this.formatValues = utils.erectArray2;\r\n return null;\r\n }\r\n break;\r\n case 'Array(3)':\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureArray3Float3D;\r\n this.renderStrategy = renderStrategy.FloatPixelTo3DArray3;\r\n this.formatValues = utils.erect3DArray3;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureArray3Float2D;\r\n this.renderStrategy = renderStrategy.FloatPixelTo2DArray3;\r\n this.formatValues = utils.erect2DArray3;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureArray3Float;\r\n this.renderStrategy = renderStrategy.FloatPixelToArray3;\r\n this.formatValues = utils.erectArray3;\r\n return null;\r\n }\r\n break;\r\n case 'Array(4)':\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureArray4Float3D;\r\n this.renderStrategy = renderStrategy.FloatPixelTo3DArray4;\r\n this.formatValues = utils.erect3DArray4;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureArray4Float2D;\r\n this.renderStrategy = renderStrategy.FloatPixelTo2DArray4;\r\n this.formatValues = utils.erect2DArray4;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureArray4Float;\r\n this.renderStrategy = renderStrategy.FloatPixelToArray4;\r\n this.formatValues = utils.erectArray4;\r\n return null;\r\n }\r\n }\r\n }\r\n } else {\r\n throw new Error(`unhandled precision of \"${this.precision}\"`);\r\n }\r\n\r\n throw new Error(`unhandled return type \"${this.returnType}\"`);\r\n }\r\n\r\n /**\r\n * @abstract\r\n * @returns String\r\n */\r\n getKernelString() {\r\n throw new Error(`abstract method call`);\r\n }\r\n\r\n getMainResultTexture() {\r\n switch (this.returnType) {\r\n case 'LiteralInteger':\r\n case 'Float':\r\n case 'Integer':\r\n case 'Number':\r\n return this.getMainResultNumberTexture();\r\n case 'Array(2)':\r\n return this.getMainResultArray2Texture();\r\n case 'Array(3)':\r\n return this.getMainResultArray3Texture();\r\n case 'Array(4)':\r\n return this.getMainResultArray4Texture();\r\n default:\r\n throw new Error(`unhandled returnType type ${ this.returnType }`);\r\n }\r\n }\r\n\r\n /**\r\n * @abstract\r\n * @returns String[]\r\n */\r\n getMainResultKernelNumberTexture() {\r\n throw new Error(`abstract method call`);\r\n }\r\n /**\r\n * @abstract\r\n * @returns String[]\r\n */\r\n getMainResultSubKernelNumberTexture() {\r\n throw new Error(`abstract method call`);\r\n }\r\n /**\r\n * @abstract\r\n * @returns String[]\r\n */\r\n getMainResultKernelArray2Texture() {\r\n throw new Error(`abstract method call`);\r\n }\r\n /**\r\n * @abstract\r\n * @returns String[]\r\n */\r\n getMainResultSubKernelArray2Texture() {\r\n throw new Error(`abstract method call`);\r\n }\r\n /**\r\n * @abstract\r\n * @returns String[]\r\n */\r\n getMainResultKernelArray3Texture() {\r\n throw new Error(`abstract method call`);\r\n }\r\n /**\r\n * @abstract\r\n * @returns String[]\r\n */\r\n getMainResultSubKernelArray3Texture() {\r\n throw new Error(`abstract method call`);\r\n }\r\n /**\r\n * @abstract\r\n * @returns String[]\r\n */\r\n getMainResultKernelArray4Texture() {\r\n throw new Error(`abstract method call`);\r\n }\r\n /**\r\n * @abstract\r\n * @returns String[]\r\n */\r\n getMainResultSubKernelArray4Texture() {\r\n throw new Error(`abstract method call`);\r\n }\r\n /**\r\n * @abstract\r\n * @returns String[]\r\n */\r\n getMainResultGraphical() {\r\n throw new Error(`abstract method call`);\r\n }\r\n /**\r\n * @abstract\r\n * @returns String[]\r\n */\r\n getMainResultMemoryOptimizedFloats() {\r\n throw new Error(`abstract method call`);\r\n }\r\n /**\r\n * @abstract\r\n * @returns String[]\r\n */\r\n getMainResultPackedPixels() {\r\n throw new Error(`abstract method call`);\r\n }\r\n\r\n getMainResultString() {\r\n if (this.graphical) {\r\n return this.getMainResultGraphical();\r\n } else if (this.precision === 'single') {\r\n if (this.optimizeFloatMemory) {\r\n return this.getMainResultMemoryOptimizedFloats();\r\n }\r\n return this.getMainResultTexture();\r\n } else {\r\n return this.getMainResultPackedPixels();\r\n }\r\n }\r\n\r\n getMainResultNumberTexture() {\r\n return utils.linesToString(this.getMainResultKernelNumberTexture()) +\r\n utils.linesToString(this.getMainResultSubKernelNumberTexture());\r\n }\r\n\r\n getMainResultArray2Texture() {\r\n return utils.linesToString(this.getMainResultKernelArray2Texture()) +\r\n utils.linesToString(this.getMainResultSubKernelArray2Texture());\r\n }\r\n\r\n getMainResultArray3Texture() {\r\n return utils.linesToString(this.getMainResultKernelArray3Texture()) +\r\n utils.linesToString(this.getMainResultSubKernelArray3Texture());\r\n }\r\n\r\n getMainResultArray4Texture() {\r\n return utils.linesToString(this.getMainResultKernelArray4Texture()) +\r\n utils.linesToString(this.getMainResultSubKernelArray4Texture());\r\n }\r\n\r\n /**\r\n *\r\n * @return {string}\r\n */\r\n getFloatTacticDeclaration() {\r\n switch (this.tactic) {\r\n case 'speed':\r\n return 'precision lowp float;\\n';\r\n case 'performance':\r\n return 'precision highp float;\\n';\r\n case 'balanced':\r\n default:\r\n return 'precision mediump float;\\n';\r\n }\r\n }\r\n\r\n /**\r\n *\r\n * @return {string}\r\n */\r\n getIntTacticDeclaration() {\r\n switch (this.tactic) {\r\n case 'speed':\r\n return 'precision lowp int;\\n';\r\n case 'performance':\r\n return 'precision highp int;\\n';\r\n case 'balanced':\r\n default:\r\n return 'precision mediump int;\\n';\r\n }\r\n }\r\n\r\n /**\r\n *\r\n * @return {string}\r\n */\r\n getSampler2DTacticDeclaration() {\r\n switch (this.tactic) {\r\n case 'speed':\r\n return 'precision lowp sampler2D;\\n';\r\n case 'performance':\r\n return 'precision highp sampler2D;\\n';\r\n case 'balanced':\r\n default:\r\n return 'precision mediump sampler2D;\\n';\r\n }\r\n }\r\n\r\n getSampler2DArrayTacticDeclaration() {\r\n switch (this.tactic) {\r\n case 'speed':\r\n return 'precision lowp sampler2DArray;\\n';\r\n case 'performance':\r\n return 'precision highp sampler2DArray;\\n';\r\n case 'balanced':\r\n default:\r\n return 'precision mediump sampler2DArray;\\n';\r\n }\r\n }\r\n\r\n renderTexture() {\r\n return new this.TextureConstructor({\r\n texture: this.outputTexture,\r\n size: this.texSize,\r\n dimensions: this.threadDim,\r\n output: this.output,\r\n context: this.context,\r\n });\r\n }\r\n readPackedPixelsToUint8Array() {\r\n if (this.precision !== 'unsigned') throw new Error('Requires this.precision to be \"unsigned\"');\r\n const {\r\n texSize,\r\n context: gl\r\n } = this;\r\n const result = new Uint8Array(texSize[0] * texSize[1] * 4);\r\n gl.readPixels(0, 0, texSize[0], texSize[1], gl.RGBA, gl.UNSIGNED_BYTE, result);\r\n return result;\r\n }\r\n\r\n readPackedPixelsToFloat32Array() {\r\n return new Float32Array(this.readPackedPixelsToUint8Array().buffer);\r\n }\r\n\r\n readFloatPixelsToFloat32Array() {\r\n if (this.precision !== 'single') throw new Error('Requires this.precision to be \"single\"');\r\n const {\r\n texSize,\r\n context: gl\r\n } = this;\r\n const w = texSize[0];\r\n const h = texSize[1];\r\n const result = new Float32Array(w * h * 4);\r\n gl.readPixels(0, 0, w, h, gl.RGBA, gl.FLOAT, result);\r\n return result;\r\n }\r\n\r\n readMemoryOptimizedFloatPixelsToFloat32Array() {\r\n if (this.precision !== 'single') throw new Error('Requires this.precision to be \"single\"');\r\n const {\r\n texSize,\r\n context: gl\r\n } = this;\r\n const w = texSize[0];\r\n const h = texSize[1];\r\n const result = new Float32Array(w * h * 4);\r\n gl.readPixels(0, 0, w, h, gl.RGBA, gl.FLOAT, result);\r\n return result;\r\n }\r\n\r\n /**\r\n *\r\n * @param {Boolean} [flip]\r\n * @return {Uint8Array}\r\n */\r\n getPixels(flip) {\r\n const {\r\n context: gl,\r\n output\r\n } = this;\r\n const [width, height] = output;\r\n const pixels = new Uint8Array(width * height * 4);\r\n gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);\r\n // flipped by default, so invert\r\n return new Uint8ClampedArray((flip ? pixels : utils.flipPixels(pixels, width, height)).buffer);\r\n }\r\n\r\n renderKernelsToArrays() {\r\n const result = {\r\n result: this.renderOutput(),\r\n };\r\n for (let i = 0; i < this.subKernels.length; i++) {\r\n result[this.subKernels[i].property] = new this.TextureConstructor({\r\n texture: this.subKernelOutputTextures[i],\r\n size: this.texSize,\r\n dimensions: this.threadDim,\r\n output: this.output,\r\n context: this.context,\r\n }).toArray();\r\n }\r\n return result;\r\n }\r\n\r\n renderKernelsToTextures() {\r\n const result = {\r\n result: this.renderOutput(),\r\n };\r\n for (let i = 0; i < this.subKernels.length; i++) {\r\n result[this.subKernels[i].property] = new this.TextureConstructor({\r\n texture: this.subKernelOutputTextures[i],\r\n size: this.texSize,\r\n dimensions: this.threadDim,\r\n output: this.output,\r\n context: this.context,\r\n });\r\n }\r\n return result;\r\n }\r\n\r\n setOutput(output) {\r\n super.setOutput(output);\r\n if (this.program) {\r\n this.threadDim = [this.output[0], this.output[1] || 1, this.output[2] || 1];\r\n this.texSize = utils.getKernelTextureSize({\r\n optimizeFloatMemory: this.optimizeFloatMemory,\r\n precision: this.precision,\r\n }, this.output);\r\n const { context: gl } = this;\r\n gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);\r\n this.updateMaxTexSize();\r\n this.framebuffer.width = this.texSize[0];\r\n this.framebuffer.height = this.texSize[1];\r\n this.context.viewport(0, 0, this.maxTexSize[0], this.maxTexSize[1]);\r\n this.canvas.width = this.maxTexSize[0];\r\n this.canvas.height = this.maxTexSize[1];\r\n this._setupOutputTexture();\r\n if (this.subKernels && this.subKernels.length > 0) {\r\n this._setupSubOutputTextures();\r\n }\r\n }\r\n return this;\r\n }\r\n renderValues() {\r\n return this.formatValues(\r\n this.transferValues(),\r\n this.output[0],\r\n this.output[1],\r\n this.output[2]\r\n );\r\n }\r\n}\r\n\r\nexport const renderStrategy = Object.freeze({\r\n PackedPixelToUint8Array: Symbol('PackedPixelToUint8Array'),\r\n PackedPixelToFloat: Symbol('PackedPixelToFloat'),\r\n PackedPixelTo2DFloat: Symbol('PackedPixelTo2DFloat'),\r\n PackedPixelTo3DFloat: Symbol('PackedPixelTo3DFloat'),\r\n PackedTexture: Symbol('PackedTexture'),\r\n FloatPixelToFloat32Array: Symbol('FloatPixelToFloat32Array'),\r\n FloatPixelToFloat: Symbol('FloatPixelToFloat'),\r\n FloatPixelTo2DFloat: Symbol('FloatPixelTo2DFloat'),\r\n FloatPixelTo3DFloat: Symbol('FloatPixelTo3DFloat'),\r\n FloatPixelToArray2: Symbol('FloatPixelToArray2'),\r\n FloatPixelTo2DArray2: Symbol('FloatPixelTo2DArray2'),\r\n FloatPixelTo3DArray2: Symbol('FloatPixelTo3DArray2'),\r\n FloatPixelToArray3: Symbol('FloatPixelToArray3'),\r\n FloatPixelTo2DArray3: Symbol('FloatPixelTo2DArray3'),\r\n FloatPixelTo3DArray3: Symbol('FloatPixelTo3DArray3'),\r\n FloatPixelToArray4: Symbol('FloatPixelToArray4'),\r\n FloatPixelTo2DArray4: Symbol('FloatPixelTo2DArray4'),\r\n FloatPixelTo3DArray4: Symbol('FloatPixelTo3DArray4'),\r\n FloatTexture: Symbol('FloatTexture'),\r\n MemoryOptimizedFloatPixelToMemoryOptimizedFloat: Symbol('MemoryOptimizedFloatPixelToFloat'),\r\n MemoryOptimizedFloatPixelToMemoryOptimized2DFloat: Symbol('MemoryOptimizedFloatPixelTo2DFloat'),\r\n MemoryOptimizedFloatPixelToMemoryOptimized3DFloat: Symbol('MemoryOptimizedFloatPixelTo3DFloat'),\r\n});\r\n\r\nconst typeMap = {\r\n int: 'Integer',\r\n float: 'Number',\r\n vec2: 'Array(2)',\r\n vec3: 'Array(3)',\r\n vec4: 'Array(4)',\r\n};\r\n","import { utils } from '../../utils';\r\nimport { FunctionNode } from '../function-node';\r\n// Closure capture for the ast function, prevent collision with existing AST functions\r\n// The prefixes to use\r\nconst jsMathPrefix = 'Math.';\r\nconst localPrefix = 'this.';\r\n\r\n/**\r\n * @desc [INTERNAL] Takes in a function node, and does all the AST voodoo required to toString its respective WebGL code\r\n * @extends FunctionNode\r\n * @returns the converted WebGL function string\r\n */\r\nexport class WebGLFunctionNode extends FunctionNode {\r\n constructor(source, settings) {\r\n super(source, settings);\r\n if (settings && settings.hasOwnProperty('fixIntegerDivisionAccuracy')) {\r\n this.fixIntegerDivisionAccuracy = settings.fixIntegerDivisionAccuracy;\r\n }\r\n }\r\n\r\n /**\r\n * @desc Parses the abstract syntax tree for to its *named function*\r\n * @param {Object} ast - the AST object to parse\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astFunction(ast, retArr) {\r\n // Setup function return type and name\r\n if (this.isRootKernel) {\r\n retArr.push('void');\r\n } else {\r\n // looking up return type, this is a little expensive, and can be avoided if returnType is set\r\n let lastReturn = null;\r\n if (!this.returnType) {\r\n const lastReturn = this.findLastReturn();\r\n if (lastReturn) {\r\n this.returnType = this.getType(ast.body);\r\n if (this.returnType === 'LiteralInteger') {\r\n this.returnType = 'Number';\r\n }\r\n }\r\n }\r\n\r\n const { returnType } = this;\r\n if (!returnType) {\r\n retArr.push('void');\r\n } else {\r\n const type = typeMap[returnType];\r\n if (!type) {\r\n throw new Error(`unknown type ${returnType}`);\r\n }\r\n retArr.push(type);\r\n }\r\n }\r\n retArr.push(' ');\r\n retArr.push(this.name);\r\n retArr.push('(');\r\n\r\n if (!this.isRootKernel) {\r\n // Arguments handling\r\n for (let i = 0; i < this.argumentNames.length; ++i) {\r\n const argumentName = this.argumentNames[i];\r\n\r\n if (i > 0) {\r\n retArr.push(', ');\r\n }\r\n let argumentType = this.argumentTypes[this.argumentNames.indexOf(argumentName)];\r\n // The type is too loose ended, here we descide to solidify a type, lets go with float\r\n if (!argumentType) {\r\n throw this.astErrorOutput(`Unknown argument ${argumentName} type`, ast);\r\n }\r\n if (argumentType === 'LiteralInteger') {\r\n this.argumentTypes[i] = argumentType = 'Number';\r\n }\r\n const type = typeMap[argumentType];\r\n if (!type) {\r\n throw this.astErrorOutput('Unexpected expression', ast);\r\n }\r\n retArr.push(type);\r\n retArr.push(' ');\r\n retArr.push('user_');\r\n retArr.push(argumentName);\r\n }\r\n }\r\n\r\n // Function opening\r\n retArr.push(') {\\n');\r\n\r\n // Body statement iteration\r\n for (let i = 0; i < ast.body.body.length; ++i) {\r\n this.astGeneric(ast.body.body[i], retArr);\r\n retArr.push('\\n');\r\n }\r\n\r\n // Function closing\r\n retArr.push('}\\n');\r\n return retArr;\r\n }\r\n\r\n /**\r\n * @desc Parses the abstract syntax tree for to *return* statement\r\n * @param {Object} ast - the AST object to parse\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astReturnStatement(ast, retArr) {\r\n if (!ast.argument) throw this.astErrorOutput('Unexpected return statement', ast);\r\n this.pushState('skip-literal-correction');\r\n const type = this.getType(ast.argument);\r\n this.popState('skip-literal-correction');\r\n\r\n const result = [];\r\n\r\n if (!this.returnType) {\r\n if (type === 'LiteralInteger' || type === 'Integer') {\r\n this.returnType = 'Number';\r\n } else {\r\n this.returnType = type;\r\n }\r\n }\r\n\r\n switch (this.returnType) {\r\n case 'LiteralInteger':\r\n case 'Number':\r\n case 'Float':\r\n switch (type) {\r\n case 'Integer':\r\n result.push('float(');\r\n this.astGeneric(ast.argument, result);\r\n result.push(')');\r\n break;\r\n case 'LiteralInteger':\r\n this.castLiteralToFloat(ast.argument, result);\r\n\r\n // Running astGeneric forces the LiteralInteger to pick a type, and here, if we are returning a float, yet\r\n // the LiteralInteger has picked to be an integer because of constraints on it we cast it to float.\r\n if (this.getType(ast) === 'Integer') {\r\n result.unshift('float(');\r\n result.push(')');\r\n }\r\n break;\r\n default:\r\n this.astGeneric(ast.argument, result);\r\n }\r\n break;\r\n case 'Integer':\r\n switch (type) {\r\n case 'Float':\r\n case 'Number':\r\n this.castValueToInteger(ast.argument, result);\r\n break;\r\n case 'LiteralInteger':\r\n this.castLiteralToInteger(ast.argument, result);\r\n break;\r\n default:\r\n this.astGeneric(ast.argument, result);\r\n }\r\n break;\r\n case 'Array(4)':\r\n case 'Array(3)':\r\n case 'Array(2)':\r\n case 'Input':\r\n this.astGeneric(ast.argument, result);\r\n break;\r\n default:\r\n throw this.astErrorOutput(`unhandled return type ${this.returnType}`, ast);\r\n }\r\n\r\n if (this.isRootKernel) {\r\n retArr.push(`kernelResult = ${ result.join('') };`);\r\n retArr.push('return;');\r\n } else if (this.isSubKernel) {\r\n retArr.push(`subKernelResult_${ this.name } = ${ result.join('') };`);\r\n retArr.push(`return subKernelResult_${ this.name };`);\r\n } else {\r\n retArr.push(`return ${ result.join('') };`);\r\n }\r\n return retArr;\r\n }\r\n\r\n /**\r\n * @desc Parses the abstract syntax tree for *literal value*\r\n *\r\n * @param {Object} ast - the AST object to parse\r\n * @param {Array} retArr - return array string\r\n *\r\n * @returns {Array} the append retArr\r\n */\r\n astLiteral(ast, retArr) {\r\n // Reject non numeric literals\r\n if (isNaN(ast.value)) {\r\n throw this.astErrorOutput(\r\n 'Non-numeric literal not supported : ' + ast.value,\r\n ast\r\n );\r\n }\r\n\r\n const key = `${ast.start},${ast.end}`;\r\n if (Number.isInteger(ast.value)) {\r\n if (this.isState('in-for-loop-init') || this.isState('casting-to-integer') || this.isState('building-integer')) {\r\n this.literalTypes[key] = 'Integer';\r\n retArr.push(`${ast.value}`);\r\n } else if (this.isState('casting-to-float') || this.isState('building-float')) {\r\n this.literalTypes[key] = 'Number';\r\n retArr.push(`${ast.value}.0`);\r\n } else {\r\n this.literalTypes[key] = 'Number';\r\n retArr.push(`${ast.value}.0`);\r\n }\r\n } else if (this.isState('casting-to-integer') || this.isState('building-integer')) {\r\n this.literalTypes[key] = 'Integer';\r\n retArr.push(Math.round(ast.value));\r\n } else {\r\n this.literalTypes[key] = 'Number';\r\n retArr.push(`${ast.value}`);\r\n }\r\n return retArr;\r\n }\r\n\r\n /**\r\n * @desc Parses the abstract syntax tree for *binary* expression\r\n * @param {Object} ast - the AST object to parse\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astBinaryExpression(ast, retArr) {\r\n if (this.checkAndUpconvertOperator(ast, retArr)) {\r\n return retArr;\r\n }\r\n\r\n if (this.fixIntegerDivisionAccuracy && ast.operator === '/') {\r\n retArr.push('div_with_int_check(');\r\n this.pushState('building-float');\r\n switch (this.getType(ast.left)) {\r\n case 'Integer':\r\n this.castValueToFloat(ast.left, retArr);\r\n break;\r\n case 'LiteralInteger':\r\n this.castLiteralToFloat(ast.left, retArr);\r\n break;\r\n default:\r\n this.astGeneric(ast.left, retArr);\r\n }\r\n retArr.push(', ');\r\n switch (this.getType(ast.right)) {\r\n case 'Integer':\r\n this.castValueToFloat(ast.right, retArr);\r\n break;\r\n case 'LiteralInteger':\r\n this.castLiteralToFloat(ast.right, retArr);\r\n break;\r\n default:\r\n this.astGeneric(ast.right, retArr);\r\n }\r\n this.popState('building-float');\r\n retArr.push(')');\r\n return retArr;\r\n }\r\n\r\n retArr.push('(');\r\n const leftType = this.getType(ast.left) || 'Number';\r\n const rightType = this.getType(ast.right) || 'Number';\r\n if (!leftType || !rightType) {\r\n throw this.astErrorOutput(`Unhandled binary expression`, ast);\r\n }\r\n const key = leftType + ' & ' + rightType;\r\n switch (key) {\r\n case 'Integer & Integer':\r\n this.pushState('building-integer');\r\n this.astGeneric(ast.left, retArr);\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.astGeneric(ast.right, retArr);\r\n this.popState('building-integer');\r\n break;\r\n case 'Number & Float':\r\n case 'Float & Number':\r\n case 'Float & Float':\r\n case 'Number & Number':\r\n this.pushState('building-float');\r\n this.astGeneric(ast.left, retArr);\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.astGeneric(ast.right, retArr);\r\n this.popState('building-float');\r\n break;\r\n case 'LiteralInteger & LiteralInteger':\r\n if (this.isState('casting-to-integer') || this.isState('building-integer')) {\r\n this.pushState('building-integer');\r\n this.astGeneric(ast.left, retArr);\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.astGeneric(ast.right, retArr);\r\n this.popState('building-integer');\r\n } else {\r\n this.pushState('building-float');\r\n this.castLiteralToFloat(ast.left, retArr);\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.castLiteralToFloat(ast.right, retArr);\r\n this.popState('building-float');\r\n }\r\n break;\r\n\r\n case 'Integer & Float':\r\n case 'Integer & Number':\r\n if (ast.operator === '>' || ast.operator === '<' && ast.right.type === 'Literal') {\r\n // if right value is actually a float, don't loose that information, cast left to right rather than the usual right to left\r\n if (!Number.isInteger(ast.right.value)) {\r\n this.pushState('building-float');\r\n this.castValueToFloat(ast.left, retArr);\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.astGeneric(ast.right, retArr);\r\n this.popState('building-float');\r\n break;\r\n }\r\n }\r\n this.pushState('building-integer');\r\n this.astGeneric(ast.left, retArr);\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.pushState('casting-to-integer');\r\n if (ast.right.type === 'Literal') {\r\n const literalResult = [];\r\n this.astGeneric(ast.right, literalResult);\r\n const literalType = this.getType(ast.right);\r\n if (literalType === 'Integer') {\r\n retArr.push(literalResult.join(''));\r\n } else {\r\n throw this.astErrorOutput(`Unhandled binary expression with literal`, ast);\r\n }\r\n } else {\r\n retArr.push('int(');\r\n this.astGeneric(ast.right, retArr);\r\n retArr.push(')');\r\n }\r\n this.popState('casting-to-integer');\r\n this.popState('building-integer');\r\n break;\r\n case 'Integer & LiteralInteger':\r\n this.pushState('building-integer');\r\n this.astGeneric(ast.left, retArr);\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.castLiteralToInteger(ast.right, retArr);\r\n this.popState('building-integer');\r\n break;\r\n\r\n case 'Number & Integer':\r\n this.pushState('building-float');\r\n this.astGeneric(ast.left, retArr);\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.castValueToFloat(ast.right, retArr);\r\n this.popState('building-float');\r\n break;\r\n case 'Float & LiteralInteger':\r\n case 'Number & LiteralInteger':\r\n if (this.isState('in-for-loop-test')) {\r\n this.pushState('building-integer');\r\n retArr.push('int(');\r\n this.astGeneric(ast.left, retArr);\r\n retArr.push(')');\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.castLiteralToInteger(ast.right, retArr);\r\n this.popState('building-integer');\r\n } else {\r\n this.pushState('building-float');\r\n this.astGeneric(ast.left, retArr);\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.castLiteralToFloat(ast.right, retArr);\r\n this.popState('building-float');\r\n }\r\n break;\r\n case 'LiteralInteger & Float':\r\n case 'LiteralInteger & Number':\r\n if (this.isState('in-for-loop-test') || this.isState('in-for-loop-init') || this.isState('casting-to-integer')) {\r\n this.pushState('building-integer');\r\n this.castLiteralToInteger(ast.left, retArr);\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.castValueToInteger(ast.right, retArr);\r\n this.popState('building-integer');\r\n } else {\r\n this.pushState('building-float');\r\n this.astGeneric(ast.left, retArr);\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.pushState('casting-to-float');\r\n this.astGeneric(ast.right, retArr);\r\n this.popState('casting-to-float');\r\n this.popState('building-float');\r\n }\r\n break;\r\n case 'LiteralInteger & Integer':\r\n this.pushState('building-integer');\r\n this.castLiteralToInteger(ast.left, retArr);\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.astGeneric(ast.right, retArr);\r\n this.popState('building-integer');\r\n break;\r\n\r\n case 'Boolean & Boolean':\r\n this.pushState('building-boolean');\r\n this.astGeneric(ast.left, retArr);\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.astGeneric(ast.right, retArr);\r\n this.popState('building-boolean');\r\n break;\r\n\r\n case 'Float & Integer':\r\n this.pushState('building-float');\r\n this.astGeneric(ast.left, retArr);\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.castValueToFloat(ast.right, retArr);\r\n this.popState('building-float');\r\n break;\r\n\r\n default:\r\n throw this.astErrorOutput(`Unhandled binary expression between ${key}`, ast);\r\n }\r\n retArr.push(')');\r\n\r\n return retArr;\r\n }\r\n\r\n checkAndUpconvertOperator(ast, retArr) {\r\n const bitwiseResult = this.checkAndUpconvertBitwiseOperators(ast, retArr);\r\n if (bitwiseResult) {\r\n return bitwiseResult;\r\n }\r\n const upconvertableOperators = {\r\n '%': 'mod',\r\n '**': 'pow',\r\n };\r\n const foundOperator = upconvertableOperators[ast.operator];\r\n if (!foundOperator) return null;\r\n retArr.push(foundOperator);\r\n retArr.push('(');\r\n switch (this.getType(ast.left)) {\r\n case 'Integer':\r\n this.castValueToFloat(ast.left, retArr);\r\n break;\r\n case 'LiteralInteger':\r\n this.castLiteralToFloat(ast.left, retArr);\r\n break;\r\n default:\r\n this.astGeneric(ast.left, retArr);\r\n }\r\n retArr.push(',');\r\n switch (this.getType(ast.right)) {\r\n case 'Integer':\r\n this.castValueToFloat(ast.right, retArr);\r\n break;\r\n case 'LiteralInteger':\r\n this.castLiteralToFloat(ast.right, retArr);\r\n break;\r\n default:\r\n this.astGeneric(ast.right, retArr);\r\n }\r\n retArr.push(')');\r\n return retArr;\r\n }\r\n\r\n checkAndUpconvertBitwiseOperators(ast, retArr) {\r\n const upconvertableOperators = {\r\n '&': 'bitwiseAnd',\r\n '|': 'bitwiseOr',\r\n '^': 'bitwiseXOR',\r\n '<<': 'bitwiseZeroFillLeftShift',\r\n '>>': 'bitwiseSignedRightShift',\r\n '>>>': 'bitwiseZeroFillRightShift',\r\n };\r\n const foundOperator = upconvertableOperators[ast.operator];\r\n if (!foundOperator) return null;\r\n retArr.push(foundOperator);\r\n retArr.push('(');\r\n const leftType = this.getType(ast.left);\r\n switch (leftType) {\r\n case 'Number':\r\n case 'Float':\r\n this.castValueToInteger(ast.left, retArr);\r\n break;\r\n case 'LiteralInteger':\r\n this.castLiteralToInteger(ast.left, retArr);\r\n break;\r\n default:\r\n this.astGeneric(ast.left, retArr);\r\n }\r\n retArr.push(',');\r\n const rightType = this.getType(ast.right);\r\n switch (rightType) {\r\n case 'Number':\r\n case 'Float':\r\n this.castValueToInteger(ast.right, retArr);\r\n break;\r\n case 'LiteralInteger':\r\n this.castLiteralToInteger(ast.right, retArr);\r\n break;\r\n default:\r\n this.astGeneric(ast.right, retArr);\r\n }\r\n retArr.push(')');\r\n return retArr;\r\n }\r\n\r\n checkAndUpconvertBitwiseUnary(ast, retArr) {\r\n const upconvertableOperators = {\r\n '~': 'bitwiseNot',\r\n };\r\n const foundOperator = upconvertableOperators[ast.operator];\r\n if (!foundOperator) return null;\r\n retArr.push(foundOperator);\r\n retArr.push('(');\r\n switch (this.getType(ast.argument)) {\r\n case 'Number':\r\n case 'Float':\r\n this.castValueToInteger(ast.argument, retArr);\r\n break;\r\n case 'LiteralInteger':\r\n this.castLiteralToInteger(ast.argument, retArr);\r\n break;\r\n default:\r\n this.astGeneric(ast.argument, retArr);\r\n }\r\n retArr.push(')');\r\n return retArr;\r\n }\r\n\r\n /**\r\n *\r\n * @param {Object} ast\r\n * @param {Array} retArr\r\n * @return {String[]}\r\n */\r\n castLiteralToInteger(ast, retArr) {\r\n this.pushState('casting-to-integer');\r\n this.astGeneric(ast, retArr);\r\n this.popState('casting-to-integer');\r\n return retArr;\r\n }\r\n\r\n /**\r\n *\r\n * @param {Object} ast\r\n * @param {Array} retArr\r\n * @return {String[]}\r\n */\r\n castLiteralToFloat(ast, retArr) {\r\n this.pushState('casting-to-float');\r\n this.astGeneric(ast, retArr);\r\n this.popState('casting-to-float');\r\n return retArr;\r\n }\r\n\r\n /**\r\n *\r\n * @param {Object} ast\r\n * @param {Array} retArr\r\n * @return {String[]}\r\n */\r\n castValueToInteger(ast, retArr) {\r\n this.pushState('casting-to-integer');\r\n retArr.push('int(');\r\n this.astGeneric(ast, retArr);\r\n retArr.push(')');\r\n this.popState('casting-to-integer');\r\n return retArr;\r\n }\r\n\r\n /**\r\n *\r\n * @param {Object} ast\r\n * @param {Array} retArr\r\n * @return {String[]}\r\n */\r\n castValueToFloat(ast, retArr) {\r\n this.pushState('casting-to-float');\r\n retArr.push('float(');\r\n this.astGeneric(ast, retArr);\r\n retArr.push(')');\r\n this.popState('casting-to-float');\r\n return retArr;\r\n }\r\n\r\n /**\r\n * @desc Parses the abstract syntax tree for *identifier* expression\r\n * @param {Object} idtNode - An ast Node\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astIdentifierExpression(idtNode, retArr) {\r\n if (idtNode.type !== 'Identifier') {\r\n throw this.astErrorOutput('IdentifierExpression - not an Identifier', idtNode);\r\n }\r\n\r\n const type = this.getType(idtNode);\r\n\r\n if (idtNode.name === 'Infinity') {\r\n // https://stackoverflow.com/a/47543127/1324039\r\n retArr.push('3.402823466e+38');\r\n } else if (type === 'Boolean') {\r\n if (this.argumentNames.indexOf(idtNode.name) > -1) {\r\n retArr.push(`bool(user_${idtNode.name})`);\r\n } else {\r\n retArr.push(`user_${idtNode.name}`);\r\n }\r\n } else {\r\n retArr.push(`user_${idtNode.name}`);\r\n }\r\n\r\n return retArr;\r\n }\r\n\r\n /**\r\n * @desc Parses the abstract syntax tree for *for-loop* expression\r\n * @param {Object} forNode - An ast Node\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the parsed webgl string\r\n */\r\n astForStatement(forNode, retArr) {\r\n if (forNode.type !== 'ForStatement') {\r\n throw this.astErrorOutput('Invalid for statement', forNode);\r\n }\r\n\r\n const initArr = [];\r\n const testArr = [];\r\n const updateArr = [];\r\n const bodyArr = [];\r\n let isSafe = null;\r\n\r\n if (forNode.init) {\r\n this.pushState('in-for-loop-init');\r\n this.astGeneric(forNode.init, initArr);\r\n const { declarations } = forNode.init;\r\n for (let i = 0; i < declarations.length; i++) {\r\n if (declarations[i].init && declarations[i].init.type !== 'Literal') {\r\n isSafe = false;\r\n }\r\n }\r\n if (isSafe) {\r\n for (let i = 0; i < initArr.length; i++) {\r\n if (initArr[i].includes && initArr[i].includes(',')) {\r\n isSafe = false;\r\n }\r\n }\r\n }\r\n this.popState('in-for-loop-init');\r\n } else {\r\n isSafe = false;\r\n }\r\n\r\n if (forNode.test) {\r\n this.pushState('in-for-loop-test');\r\n this.astGeneric(forNode.test, testArr);\r\n this.popState('in-for-loop-test');\r\n } else {\r\n isSafe = false;\r\n }\r\n\r\n if (forNode.update) {\r\n this.astGeneric(forNode.update, updateArr);\r\n } else {\r\n isSafe = false;\r\n }\r\n\r\n if (forNode.body) {\r\n this.pushState('loop-body');\r\n this.astGeneric(forNode.body, bodyArr);\r\n this.popState('loop-body');\r\n }\r\n\r\n // have all parts, now make them safe\r\n if (isSafe === null) {\r\n isSafe = this.isSafe(forNode.init) && this.isSafe(forNode.test);\r\n }\r\n\r\n if (isSafe) {\r\n retArr.push(`for (${initArr.join('')};${testArr.join('')};${updateArr.join('')}){\\n`);\r\n retArr.push(bodyArr.join(''));\r\n retArr.push('}\\n');\r\n } else {\r\n const iVariableName = this.getInternalVariableName('safeI');\r\n if (initArr.length > 0) {\r\n retArr.push(initArr.join(''), ';\\n');\r\n }\r\n retArr.push(`for (int ${iVariableName}=0;${iVariableName}This method calls a helper method *renderOutput* to return the result.
\r\n * @returns {Float32Array|Float32Array[]|Float32Array[][]|void} Result The final output of the program, as float, and as Textures for reuse.\r\n * @abstract\r\n */\r\n run() {\r\n throw new Error(`\"run\" not defined on ${ this.constructor.name }`)\r\n }\r\n\r\n /**\r\n * @abstract\r\n * @return {Object}\r\n */\r\n initCanvas() {\r\n throw new Error(`\"initCanvas\" not defined on ${ this.constructor.name }`);\r\n }\r\n\r\n /**\r\n * @abstract\r\n * @return {Object}\r\n */\r\n initContext() {\r\n throw new Error(`\"initContext\" not defined on ${ this.constructor.name }`);\r\n }\r\n\r\n /**\r\n * @param {IFunctionSettings} settings\r\n * @return {Object};\r\n * @abstract\r\n */\r\n initPlugins(settings) {\r\n throw new Error(`\"initPlugins\" not defined on ${ this.constructor.name }`);\r\n }\r\n\r\n /**\r\n * @desc Setup the parameter types for the parameters\r\n * supplied to the Kernel function\r\n *\r\n * @param {IArguments} args - The actual parameters sent to the Kernel\r\n */\r\n setupArguments(args) {\r\n this.kernelArguments = [];\r\n if (!this.argumentTypes) {\r\n if (!this.argumentTypes) {\r\n this.argumentTypes = [];\r\n for (let i = 0; i < args.length; i++) {\r\n const argType = getVariableType(args[i], this.strictIntegers);\r\n const type = argType === 'Integer' ? 'Number' : argType;\r\n this.argumentTypes.push(type);\r\n this.kernelArguments.push({\r\n type\r\n });\r\n }\r\n }\r\n } else {\r\n for (let i = 0; i < this.argumentTypes.length; i++) {\r\n this.kernelArguments.push({\r\n type: this.argumentTypes[i]\r\n });\r\n }\r\n }\r\n\r\n // setup sizes\r\n this.argumentSizes = new Array(args.length);\r\n this.argumentBitRatios = new Int32Array(args.length);\r\n\r\n for (let i = 0; i < args.length; i++) {\r\n const arg = args[i];\r\n this.argumentSizes[i] = arg.constructor === Input ? arg.size : null;\r\n this.argumentBitRatios[i] = this.getBitRatio(arg);\r\n }\r\n\r\n if (this.argumentNames.length !== args.length) {\r\n throw new Error(`arguments are miss-aligned`);\r\n }\r\n }\r\n\r\n /**\r\n * Setup constants\r\n */\r\n setupConstants() {\r\n this.kernelConstants = [];\r\n let needsConstantTypes = this.constantTypes === null;\r\n if (needsConstantTypes) {\r\n this.constantTypes = {};\r\n }\r\n this.constantBitRatios = {};\r\n if (this.constants) {\r\n for (let name in this.constants) {\r\n if (needsConstantTypes) {\r\n const type = getVariableType(this.constants[name], this.strictIntegers);\r\n this.constantTypes[name] = type;\r\n this.kernelConstants.push({\r\n name,\r\n type\r\n });\r\n } else {\r\n this.kernelConstants.push({\r\n name,\r\n type: this.constantTypes[name]\r\n });\r\n }\r\n this.constantBitRatios[name] = this.getBitRatio(this.constants[name]);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n *\r\n * @param flag\r\n * @return {Kernel}\r\n */\r\n setOptimizeFloatMemory(flag) {\r\n this.optimizeFloatMemory = flag;\r\n return this;\r\n }\r\n\r\n /**\r\n * @desc Set output dimensions of the kernel function\r\n * @param {Array|Object} output - The output array to set the kernel output size to\r\n */\r\n setOutput(output) {\r\n if (output.hasOwnProperty('x')) {\r\n if (output.hasOwnProperty('y')) {\r\n if (output.hasOwnProperty('z')) {\r\n this.output = [output.x, output.y, output.z];\r\n } else {\r\n this.output = [output.x, output.y];\r\n }\r\n } else {\r\n this.output = [output.x];\r\n }\r\n } else {\r\n this.output = output;\r\n }\r\n return this;\r\n }\r\n\r\n /**\r\n * @desc Toggle debug mode\r\n * @param {Boolean} flag - true to enable debug\r\n */\r\n setDebug(flag) {\r\n this.debug = flag;\r\n return this;\r\n }\r\n\r\n /**\r\n * @desc Toggle graphical output mode\r\n * @param {Boolean} flag - true to enable graphical output\r\n */\r\n setGraphical(flag) {\r\n this.graphical = flag;\r\n this.precision = 'unsigned';\r\n return this;\r\n }\r\n\r\n /**\r\n * @desc Set the maximum number of loop iterations\r\n * @param {number} max - iterations count\r\n *\r\n */\r\n setLoopMaxIterations(max) {\r\n this.loopMaxIterations = max;\r\n return this;\r\n }\r\n\r\n /**\r\n * @desc Set Constants\r\n */\r\n setConstants(constants) {\r\n this.constants = constants;\r\n return this;\r\n }\r\n\r\n /**\r\n *\r\n * @param [IKernelValueTypes] constantTypes\r\n * @return {Kernel}\r\n */\r\n setConstantTypes(constantTypes) {\r\n this.constantTypes = constantTypes;\r\n return this;\r\n }\r\n\r\n /**\r\n *\r\n * @param {IFunction[]|KernelFunction[]} functions\r\n * @return {Kernel}\r\n */\r\n setFunctions(functions) {\r\n if (typeof functions[0] === 'function') {\r\n this.functions = functions.map(source => functionToIFunction(source));\r\n } else {\r\n this.functions = functions;\r\n }\r\n return this;\r\n }\r\n\r\n /**\r\n *\r\n * @param {IGPUNativeFunction} nativeFunctions\r\n * @return {Kernel}\r\n */\r\n setNativeFunctions(nativeFunctions) {\r\n this.nativeFunctions = nativeFunctions;\r\n return this;\r\n }\r\n\r\n /**\r\n *\r\n * @param {String} injectedNative\r\n * @return {Kernel}\r\n */\r\n setInjectedNative(injectedNative) {\r\n this.injectedNative = injectedNative;\r\n return this;\r\n }\r\n\r\n /**\r\n * Set writing to texture on/off\r\n * @param flag\r\n * @return {Kernel}\r\n */\r\n setPipeline(flag) {\r\n this.pipeline = flag;\r\n return this;\r\n }\r\n\r\n /**\r\n * Set precision to 'unsigned' or 'single'\r\n * @param {String} flag 'unsigned' or 'single'\r\n * @return {Kernel}\r\n */\r\n setPrecision(flag) {\r\n this.precision = flag;\r\n return this;\r\n }\r\n\r\n /**\r\n * @param flag\r\n * @return {Kernel}\r\n * @deprecated\r\n */\r\n setOutputToTexture(flag) {\r\n warnDeprecated('method', 'setOutputToTexture', 'setPipeline');\r\n this.pipeline = flag;\r\n return this;\r\n }\r\n\r\n /**\r\n * Set to immutable\r\n * @param flag\r\n * @return {Kernel}\r\n */\r\n setImmutable(flag) {\r\n this.immutable = flag;\r\n return this;\r\n }\r\n\r\n /**\r\n * @desc Bind the canvas to kernel\r\n * @param {Object} canvas\r\n */\r\n setCanvas(canvas) {\r\n this.canvas = canvas;\r\n return this;\r\n }\r\n\r\n /**\r\n * @param {Boolean} flag\r\n * @return {Kernel}\r\n */\r\n setStrictIntegers(flag) {\r\n this.strictIntegers = flag;\r\n return this;\r\n }\r\n\r\n /**\r\n *\r\n * @param flag\r\n * @return {Kernel}\r\n */\r\n setDynamicOutput(flag) {\r\n this.dynamicOutput = flag;\r\n return this;\r\n }\r\n\r\n /**\r\n * @deprecated\r\n * @param flag\r\n * @return {Kernel}\r\n */\r\n setHardcodeConstants(flag) {\r\n warnDeprecated('method', 'setHardcodeConstants');\r\n this.setDynamicOutput(flag);\r\n this.setDynamicArguments(flag);\r\n return this;\r\n }\r\n\r\n /**\r\n *\r\n * @param flag\r\n * @return {Kernel}\r\n */\r\n setDynamicArguments(flag) {\r\n this.dynamicArguments = flag;\r\n return this;\r\n }\r\n\r\n /**\r\n * @param {Boolean} flag\r\n * @return {Kernel}\r\n */\r\n setUseLegacyEncoder(flag) {\r\n this.useLegacyEncoder = flag;\r\n return this;\r\n }\r\n\r\n /**\r\n *\r\n * @param {Boolean} flag\r\n * @return {Kernel}\r\n */\r\n setWarnVarUsage(flag) {\r\n this.warnVarUsage = flag;\r\n return this;\r\n }\r\n\r\n /**\r\n * @deprecated\r\n * @returns {Object}\r\n */\r\n getCanvas() {\r\n warnDeprecated('method', 'getCanvas');\r\n return this.canvas;\r\n }\r\n\r\n /**\r\n * @deprecated\r\n * @returns {Object}\r\n */\r\n getWebGl() {\r\n warnDeprecated('method', 'getWebGl');\r\n return this.context;\r\n }\r\n\r\n /**\r\n * @desc Bind the webGL instance to kernel\r\n * @param {WebGLRenderingContext} context - webGl instance to bind\r\n */\r\n setContext(context) {\r\n this.context = context;\r\n return this;\r\n }\r\n\r\n /**\r\n *\r\n * @param [IKernelValueTypes|GPUVariableType[]] argumentTypes\r\n * @return {Kernel}\r\n */\r\n setArgumentTypes(argumentTypes) {\r\n if (Array.isArray(argumentTypes)) {\r\n this.argumentTypes = argumentTypes;\r\n } else {\r\n this.argumentTypes = [];\r\n for (const p in argumentTypes) {\r\n const argumentIndex = this.argumentNames.indexOf(p);\r\n if (argumentIndex === -1) throw new Error(`unable to find argument ${ p }`);\r\n this.argumentTypes[argumentIndex] = argumentTypes[p];\r\n }\r\n }\r\n return this;\r\n }\r\n\r\n /**\r\n *\r\n * @param [Tactic] tactic\r\n * @return {Kernel}\r\n */\r\n setTactic(tactic) {\r\n this.tactic = tactic;\r\n return this;\r\n }\r\n\r\n requestFallback(args) {\r\n if (!this.onRequestFallback) {\r\n throw new Error(`\"onRequestFallback\" not defined on ${ this.constructor.name }`);\r\n }\r\n this.fallbackRequested = true;\r\n return this.onRequestFallback(args);\r\n }\r\n\r\n /**\r\n * @desc Validate settings\r\n * @abstract\r\n */\r\n validateSettings() {\r\n throw new Error(`\"validateSettings\" not defined on ${ this.constructor.name }`);\r\n }\r\n\r\n /**\r\n * @desc Add a sub kernel to the root kernel instance.\r\n * This is what `createKernelMap` uses.\r\n *\r\n * @param {ISubKernel} subKernel - function (as a String) of the subKernel to add\r\n */\r\n addSubKernel(subKernel) {\r\n if (this.subKernels === null) {\r\n this.subKernels = [];\r\n }\r\n if (!subKernel.source) throw new Error('subKernel missing \"source\" property');\r\n if (!subKernel.property && isNaN(subKernel.property)) throw new Error('subKernel missing \"property\" property');\r\n if (!subKernel.name) throw new Error('subKernel missing \"name\" property');\r\n this.subKernels.push(subKernel);\r\n return this;\r\n }\r\n\r\n /**\r\n * @desc Destroys all memory associated with this kernel\r\n * @param {Boolean} [removeCanvasReferences] remove any associated canvas references\r\n */\r\n destroy(removeCanvasReferences) {\r\n throw new Error(`\"destroy\" called on ${ this.constructor.name }`);\r\n }\r\n\r\n /**\r\n * bit storage ratio of source to target 'buffer', i.e. if 8bit array -> 32bit tex = 4\r\n * @param value\r\n * @returns {number}\r\n */\r\n getBitRatio(value) {\r\n if (this.precision === 'single') {\r\n // 8 and 16 are upconverted to float32\r\n return 4;\r\n } else if (Array.isArray(value[0])) {\r\n return this.getBitRatio(value[0]);\r\n } else if (value.constructor === Input) {\r\n return this.getBitRatio(value.value);\r\n }\r\n switch (value.constructor) {\r\n case Uint8ClampedArray:\r\n case Uint8Array:\r\n case Int8Array:\r\n return 1;\r\n case Uint16Array:\r\n case Int16Array:\r\n return 2;\r\n case Float32Array:\r\n case Int32Array:\r\n default:\r\n return 4;\r\n }\r\n }\r\n\r\n /**\r\n * @returns {number[]}\r\n */\r\n getPixels() {\r\n throw new Error(`\"getPixels\" called on ${ this.constructor.name }`);\r\n }\r\n\r\n checkOutput() {\r\n if (!this.output || !isArray(this.output)) throw new Error('kernel.output not an array');\r\n if (this.output.length < 1) throw new Error('kernel.output is empty, needs at least 1 value');\r\n for (let i = 0; i < this.output.length; i++) {\r\n if (isNaN(this.output[i]) || this.output[i] < 1) {\r\n throw new Error(`${ this.constructor.name }.output[${ i }] incorrectly defined as \\`${ this.output[i] }\\`, needs to be numeric, and greater than 0`);\r\n }\r\n }\r\n }\r\n\r\n toJSON() {\r\n const settings = {\r\n output: this.output,\r\n threadDim: this.threadDim,\r\n pipeline: this.pipeline,\r\n argumentNames: this.argumentNames,\r\n argumentsTypes: this.argumentTypes,\r\n constants: this.constants,\r\n pluginNames: this.plugins ? this.plugins.map(plugin => plugin.name) : null,\r\n returnType: this.returnType,\r\n };\r\n return {\r\n settings\r\n };\r\n }\r\n}\r\n","/**\r\n * @desc This handles all the raw state, converted state, etc. of a single function.\r\n * [INTERNAL] A collection of functionNodes.\r\n * @class\r\n */\r\nexport class FunctionBuilder {\r\n /**\r\n *\r\n * @param {Kernel} kernel\r\n * @param {FunctionNode} FunctionNode\r\n * @param {object} [extraNodeOptions]\r\n * @returns {FunctionBuilder}\r\n * @static\r\n */\r\n static fromKernel(kernel, FunctionNode, extraNodeOptions) {\r\n const {\r\n kernelArguments,\r\n kernelConstants,\r\n argumentNames,\r\n argumentSizes,\r\n argumentBitRatios,\r\n constants,\r\n constantBitRatios,\r\n debug,\r\n loopMaxIterations,\r\n nativeFunctions,\r\n output,\r\n optimizeFloatMemory,\r\n precision,\r\n plugins,\r\n source,\r\n subKernels,\r\n functions,\r\n leadingReturnStatement,\r\n followingReturnStatement,\r\n dynamicArguments,\r\n dynamicOutput,\r\n warnVarUsage,\r\n } = kernel;\r\n\r\n const argumentTypes = new Array(kernelArguments.length);\r\n const constantTypes = {};\r\n\r\n for (let i = 0; i < kernelArguments.length; i++) {\r\n argumentTypes[i] = kernelArguments[i].type;\r\n }\r\n\r\n for (let i = 0; i < kernelConstants.length; i++) {\r\n const kernelConstant = kernelConstants[i]\r\n constantTypes[kernelConstant.name] = kernelConstant.type;\r\n }\r\n\r\n const needsArgumentType = (functionName, index) => {\r\n return functionBuilder.needsArgumentType(functionName, index);\r\n };\r\n\r\n const assignArgumentType = (functionName, index, type) => {\r\n functionBuilder.assignArgumentType(functionName, index, type);\r\n };\r\n\r\n const lookupReturnType = (functionName, ast, requestingNode) => {\r\n return functionBuilder.lookupReturnType(functionName, ast, requestingNode);\r\n };\r\n\r\n const lookupFunctionArgumentTypes = (functionName) => {\r\n return functionBuilder.lookupFunctionArgumentTypes(functionName);\r\n };\r\n\r\n const lookupFunctionArgumentName = (functionName, argumentIndex) => {\r\n return functionBuilder.lookupFunctionArgumentName(functionName, argumentIndex);\r\n };\r\n\r\n const lookupFunctionArgumentBitRatio = (functionName, argumentName) => {\r\n return functionBuilder.lookupFunctionArgumentBitRatio(functionName, argumentName);\r\n };\r\n\r\n const triggerImplyArgumentType = (functionName, i, argumentType, requestingNode) => {\r\n functionBuilder.assignArgumentType(functionName, i, argumentType, requestingNode);\r\n };\r\n\r\n const triggerTrackArgumentSynonym = (functionName, argumentName, calleeFunctionName, argumentIndex) => {\r\n functionBuilder.trackArgumentSynonym(functionName, argumentName, calleeFunctionName, argumentIndex);\r\n };\r\n\r\n const lookupArgumentSynonym = (originFunctionName, functionName, argumentName) => {\r\n return functionBuilder.lookupArgumentSynonym(originFunctionName, functionName, argumentName);\r\n };\r\n\r\n const onFunctionCall = (functionName, calleeFunctionName, args) => {\r\n functionBuilder.trackFunctionCall(functionName, calleeFunctionName, args);\r\n };\r\n\r\n const onNestedFunction = (ast, returnType) => {\r\n const argumentNames = [];\r\n for (let i = 0; i < ast.params.length; i++) {\r\n argumentNames.push(ast.params[i].name);\r\n }\r\n const nestedFunction = new FunctionNode(null, Object.assign({}, nodeOptions, {\r\n returnType: null,\r\n ast,\r\n name: ast.id.name,\r\n argumentNames,\r\n lookupReturnType,\r\n lookupFunctionArgumentTypes,\r\n lookupFunctionArgumentName,\r\n lookupFunctionArgumentBitRatio,\r\n needsArgumentType,\r\n assignArgumentType,\r\n triggerImplyArgumentType,\r\n triggerTrackArgumentSynonym,\r\n lookupArgumentSynonym,\r\n onFunctionCall,\r\n warnVarUsage,\r\n }));\r\n nestedFunction.traceFunctionAST(ast);\r\n functionBuilder.addFunctionNode(nestedFunction);\r\n };\r\n\r\n const nodeOptions = Object.assign({\r\n isRootKernel: false,\r\n onNestedFunction,\r\n lookupReturnType,\r\n lookupFunctionArgumentTypes,\r\n lookupFunctionArgumentName,\r\n lookupFunctionArgumentBitRatio,\r\n needsArgumentType,\r\n assignArgumentType,\r\n triggerImplyArgumentType,\r\n triggerTrackArgumentSynonym,\r\n lookupArgumentSynonym,\r\n onFunctionCall,\r\n optimizeFloatMemory,\r\n precision,\r\n constants,\r\n constantTypes,\r\n constantBitRatios,\r\n debug,\r\n loopMaxIterations,\r\n output,\r\n plugins,\r\n dynamicArguments,\r\n dynamicOutput,\r\n }, extraNodeOptions || {});\r\n\r\n const rootNodeOptions = Object.assign({}, nodeOptions, {\r\n isRootKernel: true,\r\n name: 'kernel',\r\n argumentNames,\r\n argumentTypes,\r\n argumentSizes,\r\n argumentBitRatios,\r\n leadingReturnStatement,\r\n followingReturnStatement,\r\n });\r\n\r\n if (typeof source === 'object' && source.functionNodes) {\r\n return new FunctionBuilder().fromJSON(source.functionNodes, FunctionNode);\r\n }\r\n\r\n const rootNode = new FunctionNode(source, rootNodeOptions);\r\n\r\n let functionNodes = null;\r\n if (functions) {\r\n functionNodes = functions.map((fn) => new FunctionNode(fn.source, {\r\n returnType: fn.returnType,\r\n argumentTypes: fn.argumentTypes,\r\n output,\r\n plugins,\r\n constants,\r\n constantTypes,\r\n constantBitRatios,\r\n optimizeFloatMemory,\r\n precision,\r\n lookupReturnType,\r\n lookupFunctionArgumentTypes,\r\n lookupFunctionArgumentName,\r\n lookupFunctionArgumentBitRatio,\r\n needsArgumentType,\r\n assignArgumentType,\r\n triggerImplyArgumentType,\r\n triggerTrackArgumentSynonym,\r\n lookupArgumentSynonym,\r\n onFunctionCall,\r\n }));\r\n }\r\n\r\n let subKernelNodes = null;\r\n if (subKernels) {\r\n subKernelNodes = subKernels.map((subKernel) => {\r\n const { name, source } = subKernel;\r\n return new FunctionNode(source, Object.assign({}, nodeOptions, {\r\n name,\r\n isSubKernel: true,\r\n isRootKernel: false,\r\n }));\r\n });\r\n }\r\n\r\n const functionBuilder = new FunctionBuilder({\r\n kernel,\r\n rootNode,\r\n functionNodes,\r\n nativeFunctions,\r\n subKernelNodes\r\n });\r\n\r\n return functionBuilder;\r\n }\r\n\r\n /**\r\n *\r\n * @param {IFunctionBuilderSettings} [settings]\r\n */\r\n constructor(settings) {\r\n settings = settings || {};\r\n this.kernel = settings.kernel;\r\n this.rootNode = settings.rootNode;\r\n this.functionNodes = settings.functionNodes || [];\r\n this.subKernelNodes = settings.subKernelNodes || [];\r\n this.nativeFunctions = settings.nativeFunctions || [];\r\n this.functionMap = {};\r\n this.nativeFunctionNames = [];\r\n this.lookupChain = [];\r\n this.argumentChain = [];\r\n this.functionNodeDependencies = {};\r\n this.functionCalls = {};\r\n\r\n if (this.rootNode) {\r\n this.functionMap['kernel'] = this.rootNode;\r\n }\r\n\r\n if (this.functionNodes) {\r\n for (let i = 0; i < this.functionNodes.length; i++) {\r\n this.functionMap[this.functionNodes[i].name] = this.functionNodes[i];\r\n }\r\n }\r\n\r\n if (this.subKernelNodes) {\r\n for (let i = 0; i < this.subKernelNodes.length; i++) {\r\n this.functionMap[this.subKernelNodes[i].name] = this.subKernelNodes[i];\r\n }\r\n }\r\n\r\n if (this.nativeFunctions) {\r\n for (let i = 0; i < this.nativeFunctions.length; i++) {\r\n const nativeFunction = this.nativeFunctions[i];\r\n this.nativeFunctionNames.push(nativeFunction.name);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * @desc Add the function node directly\r\n *\r\n * @param {FunctionNode} functionNode - functionNode to add\r\n *\r\n */\r\n addFunctionNode(functionNode) {\r\n if (!functionNode.name) throw new Error('functionNode.name needs set');\r\n this.functionMap[functionNode.name] = functionNode;\r\n if (functionNode.isRootKernel) {\r\n this.rootNode = functionNode;\r\n }\r\n }\r\n\r\n /**\r\n * @desc Trace all the depending functions being called, from a single function\r\n *\r\n * This allow for 'unneeded' functions to be automatically optimized out.\r\n * Note that the 0-index, is the starting function trace.\r\n *\r\n * @param {String} functionName - Function name to trace from, default to 'kernel'\r\n * @param {String[]} [retList] - Returning list of function names that is traced. Including itself.\r\n *\r\n * @returns {String[]} Returning list of function names that is traced. Including itself.\r\n */\r\n traceFunctionCalls(functionName, retList) {\r\n functionName = functionName || 'kernel';\r\n retList = retList || [];\r\n\r\n if (this.nativeFunctionNames.indexOf(functionName) > -1) {\r\n if (retList.indexOf(functionName) === -1) {\r\n retList.push(functionName);\r\n }\r\n return retList;\r\n }\r\n\r\n const functionNode = this.functionMap[functionName];\r\n if (functionNode) {\r\n // Check if function already exists\r\n const functionIndex = retList.indexOf(functionName);\r\n if (functionIndex === -1) {\r\n retList.push(functionName);\r\n functionNode.toString(); //ensure JS trace is done\r\n for (let i = 0; i < functionNode.calledFunctions.length; ++i) {\r\n this.traceFunctionCalls(functionNode.calledFunctions[i], retList);\r\n }\r\n } else {\r\n /**\r\n * https://github.com/gpujs/gpu.js/issues/207\r\n * if dependent function is already in the list, because a function depends on it, and because it has\r\n * already been traced, we know that we must move the dependent function to the end of the the retList.\r\n * */\r\n const dependantFunctionName = retList.splice(functionIndex, 1)[0];\r\n retList.push(dependantFunctionName);\r\n }\r\n }\r\n\r\n return retList;\r\n }\r\n\r\n /**\r\n * @desc Return the string for a function\r\n * @param {String} functionName - Function name to trace from. If null, it returns the WHOLE builder stack\r\n * @returns {String} The full string, of all the various functions. Trace optimized if functionName given\r\n */\r\n getPrototypeString(functionName) {\r\n return this.getPrototypes(functionName).join('\\n');\r\n }\r\n\r\n /**\r\n * @desc Return the string for a function\r\n * @param {String} [functionName] - Function name to trace from. If null, it returns the WHOLE builder stack\r\n * @returns {Array} The full string, of all the various functions. Trace optimized if functionName given\r\n */\r\n getPrototypes(functionName) {\r\n if (this.rootNode) {\r\n this.rootNode.toString();\r\n }\r\n if (functionName) {\r\n return this.getPrototypesFromFunctionNames(this.traceFunctionCalls(functionName, []).reverse());\r\n }\r\n return this.getPrototypesFromFunctionNames(Object.keys(this.functionMap));\r\n }\r\n\r\n /**\r\n * @desc Get string from function names\r\n * @param {String[]} functionList - List of function to build string\r\n * @returns {String} The string, of all the various functions. Trace optimized if functionName given\r\n */\r\n getStringFromFunctionNames(functionList) {\r\n const ret = [];\r\n for (let i = 0; i < functionList.length; ++i) {\r\n const node = this.functionMap[functionList[i]];\r\n if (node) {\r\n ret.push(this.functionMap[functionList[i]].toString());\r\n }\r\n }\r\n return ret.join('\\n');\r\n }\r\n\r\n /**\r\n * @desc Return string of all functions converted\r\n * @param {String[]} functionList - List of function names to build the string.\r\n * @returns {Array} Prototypes of all functions converted\r\n */\r\n getPrototypesFromFunctionNames(functionList) {\r\n const ret = [];\r\n for (let i = 0; i < functionList.length; ++i) {\r\n const functionName = functionList[i];\r\n const functionIndex = this.nativeFunctionNames.indexOf(functionName);\r\n if (functionIndex > -1) {\r\n ret.push(this.nativeFunctions[functionIndex].source);\r\n continue;\r\n }\r\n const node = this.functionMap[functionName];\r\n if (node) {\r\n ret.push(node.toString());\r\n }\r\n }\r\n return ret;\r\n }\r\n\r\n toJSON() {\r\n return this.traceFunctionCalls(this.rootNode.name).reverse().map(name => {\r\n const nativeIndex = this.nativeFunctions.indexOf(name);\r\n if (nativeIndex > -1) {\r\n return {\r\n name,\r\n source: this.nativeFunctions[nativeIndex].source\r\n };\r\n } else if (this.functionMap[name]) {\r\n return this.functionMap[name].toJSON();\r\n } else {\r\n throw new Error(`function ${ name } not found`);\r\n }\r\n });\r\n }\r\n\r\n fromJSON(jsonFunctionNodes, FunctionNode) {\r\n this.functionMap = {};\r\n for (let i = 0; i < jsonFunctionNodes.length; i++) {\r\n const jsonFunctionNode = jsonFunctionNodes[i];\r\n this.functionMap[jsonFunctionNode.settings.name] = new FunctionNode(jsonFunctionNode.ast, jsonFunctionNode.settings);\r\n }\r\n return this;\r\n }\r\n\r\n /**\r\n * @desc Get string for a particular function name\r\n * @param {String} functionName - Function name to trace from. If null, it returns the WHOLE builder stack\r\n * @returns {String} settings - The string, of all the various functions. Trace optimized if functionName given\r\n */\r\n getString(functionName) {\r\n if (functionName) {\r\n return this.getStringFromFunctionNames(this.traceFunctionCalls(functionName).reverse());\r\n }\r\n return this.getStringFromFunctionNames(Object.keys(this.functionMap));\r\n }\r\n\r\n lookupReturnType(functionName, ast, requestingNode) {\r\n if (ast.type !== 'CallExpression') {\r\n throw new Error(`expected ast type of \"CallExpression\", but is ${ ast.type }`);\r\n }\r\n if (this._isNativeFunction(functionName)) {\r\n return this._lookupNativeFunctionReturnType(functionName);\r\n } else if (this._isFunction(functionName)) {\r\n const node = this._getFunction(functionName);\r\n if (node.returnType) {\r\n return node.returnType;\r\n } else {\r\n for (let i = 0; i < this.lookupChain.length; i++) {\r\n // detect circlical logic\r\n if (this.lookupChain[i].ast === ast) {\r\n // detect if arguments have not resolved, preventing a return type\r\n // if so, go ahead and resolve them, so we can resolve the return type\r\n if (node.argumentTypes.length === 0 && ast.arguments.length > 0) {\r\n const args = ast.arguments;\r\n for (let j = 0; j < args.length; j++) {\r\n this.lookupChain.push({\r\n name: requestingNode.name,\r\n ast: args[i],\r\n requestingNode\r\n });\r\n node.argumentTypes[j] = requestingNode.getType(args[j]);\r\n this.lookupChain.pop();\r\n }\r\n return node.returnType = node.getType(node.getJsAST());\r\n }\r\n\r\n throw new Error('circlical logic detected!');\r\n }\r\n }\r\n // get ready for a ride!\r\n this.lookupChain.push({\r\n name: requestingNode.name,\r\n ast,\r\n requestingNode\r\n });\r\n const type = node.getType(node.getJsAST());\r\n this.lookupChain.pop();\r\n return node.returnType = type;\r\n }\r\n }\r\n\r\n // function not found, maybe native?\r\n return null;\r\n\r\n /**\r\n * first iteration\r\n * kernel.outputs = Array\r\n * kernel.targets = Array\r\n * kernel.returns = null\r\n * kernel.calls.calcErrorOutput = [kernel.output, kernel.targets]\r\n * kernel.calls.calcDeltas = [calcErrorOutput.returns, kernel.output]\r\n * calcErrorOutput.output = null\r\n * calcErrorOutput.targets = null\r\n * calcErrorOutput.returns = null\r\n * calcDeltasSigmoid.error = null\r\n * calcDeltasSigmoid.output = Number\r\n * calcDeltasSigmoid.returns = null\r\n *\r\n * resolvable are:\r\n * calcErrorOutput.output\r\n * calcErrorOutput.targets\r\n * calcErrorOutput.returns\r\n *\r\n * second iteration\r\n * kernel.outputs = Array\r\n * kernel.targets = Array\r\n * kernel.returns = null\r\n * kernel.calls.calcErrorOutput = [kernel.output, kernel.targets]\r\n * kernel.calls.calcDeltas = [calcErrorOutput.returns, kernel.output]\r\n * calcErrorOutput.output = Number\r\n * calcErrorOutput.targets = Array\r\n * calcErrorOutput.returns = Number\r\n * calcDeltasSigmoid.error = null\r\n * calcDeltasSigmoid.output = Number\r\n * calcDeltasSigmoid.returns = null\r\n *\r\n * resolvable are:\r\n * calcDeltasSigmoid.error\r\n * calcDeltasSigmoid.returns\r\n * kernel.returns\r\n *\r\n * third iteration\r\n * kernel.outputs = Array\r\n * kernel.targets = Array\r\n * kernel.returns = Number\r\n * kernel.calls.calcErrorOutput = [kernel.output, kernel.targets]\r\n * kernel.calls.calcDeltas = [calcErrorOutput.returns, kernel.output]\r\n * calcErrorOutput.output = Number\r\n * calcErrorOutput.targets = Array\r\n * calcErrorOutput.returns = Number\r\n * calcDeltasSigmoid.error = Number\r\n * calcDeltasSigmoid.output = Number\r\n * calcDeltasSigmoid.returns = Number\r\n *\r\n *\r\n */\r\n }\r\n\r\n _getFunction(functionName) {\r\n if (!this._isFunction(functionName)) {\r\n new Error(`Function ${functionName} not found`);\r\n }\r\n return this.functionMap[functionName];\r\n }\r\n\r\n _isFunction(functionName) {\r\n return Boolean(this.functionMap[functionName]);\r\n }\r\n\r\n _getNativeFunction(functionName) {\r\n for (let i = 0; i < this.nativeFunctions.length; i++) {\r\n if (this.nativeFunctions[i].name === functionName) return this.nativeFunctions[i];\r\n }\r\n return null;\r\n }\r\n\r\n _isNativeFunction(functionName) {\r\n return Boolean(this._getNativeFunction(functionName));\r\n }\r\n\r\n _lookupNativeFunctionReturnType(functionName) {\r\n let nativeFunction = this._getNativeFunction(functionName);\r\n if (nativeFunction) {\r\n return nativeFunction.returnType;\r\n }\r\n throw new Error(`Native function ${ functionName } not found`);\r\n }\r\n\r\n lookupFunctionArgumentTypes(functionName) {\r\n if (this._isNativeFunction(functionName)) {\r\n return this._getNativeFunction(functionName).argumentTypes;\r\n } else if (this._isFunction(functionName)) {\r\n return this._getFunction(functionName).argumentTypes;\r\n }\r\n return null;\r\n }\r\n\r\n lookupFunctionArgumentName(functionName, argumentIndex) {\r\n return this._getFunction(functionName).argumentNames[argumentIndex];\r\n }\r\n\r\n lookupFunctionArgumentBitRatio(functionName, argumentName) {\r\n if (!this._isFunction(functionName)) {\r\n throw new Error('function not found');\r\n }\r\n if (this.rootNode.name === functionName) {\r\n const i = this.rootNode.argumentNames.indexOf(argumentName);\r\n if (i !== -1) {\r\n return this.rootNode.argumentBitRatios[i];\r\n } else {\r\n throw new Error('argument bit ratio not found');\r\n }\r\n } else {\r\n const node = this._getFunction(functionName);\r\n const argumentSynonym = node.argumentSynonym[node.synonymIndex];\r\n if (!argumentSynonym) {\r\n throw new Error('argument synonym not found');\r\n }\r\n return this.lookupFunctionArgumentBitRatio(argumentSynonym.functionName, argumentSynonym.argumentName);\r\n }\r\n }\r\n\r\n needsArgumentType(functionName, i) {\r\n if (!this._isFunction(functionName)) return false;\r\n const fnNode = this._getFunction(functionName);\r\n return !fnNode.argumentTypes[i];\r\n }\r\n\r\n assignArgumentType(functionName, i, argumentType, requestingNode) {\r\n if (!this._isFunction(functionName)) return;\r\n const fnNode = this._getFunction(functionName);\r\n if (!fnNode.argumentTypes[i]) {\r\n fnNode.argumentTypes[i] = argumentType;\r\n }\r\n }\r\n\r\n trackArgumentSynonym(functionName, argumentName, calleeFunctionName, argumentIndex) {\r\n if (!this._isFunction(calleeFunctionName)) return;\r\n const node = this._getFunction(calleeFunctionName);\r\n if (!node.argumentSynonym) {\r\n node.argumentSynonym = {};\r\n }\r\n const calleeArgumentName = node.argumentNames[argumentIndex];\r\n if (!node.argumentSynonym[calleeArgumentName]) {\r\n node.argumentSynonym[calleeArgumentName] = {};\r\n }\r\n node.synonymIndex++;\r\n node.argumentSynonym[node.synonymIndex] = {\r\n functionName,\r\n argumentName,\r\n calleeArgumentName,\r\n calleeFunctionName,\r\n };\r\n }\r\n\r\n lookupArgumentSynonym(originFunctionName, functionName, argumentName) {\r\n if (originFunctionName === functionName) return argumentName;\r\n if (!this._isFunction(functionName)) return null;\r\n const node = this._getFunction(functionName);\r\n const argumentSynonym = node.argumentSynonym[node.synonymUseIndex];\r\n if (!argumentSynonym) return null;\r\n if (argumentSynonym.calleeArgumentName !== argumentName) return null;\r\n node.synonymUseIndex++;\r\n if (originFunctionName !== functionName) {\r\n return this.lookupArgumentSynonym(originFunctionName, argumentSynonym.functionName, argumentSynonym.argumentName);\r\n }\r\n return argumentSynonym.argumentName;\r\n }\r\n\r\n trackFunctionCall(functionName, calleeFunctionName, args) {\r\n if (!this.functionNodeDependencies[functionName]) {\r\n this.functionNodeDependencies[functionName] = new Set();\r\n this.functionCalls[functionName] = [];\r\n }\r\n this.functionNodeDependencies[functionName].add(calleeFunctionName);\r\n this.functionCalls[functionName].push(args);\r\n }\r\n\r\n getKernelResultType() {\r\n return this.rootNode.returnType || this.rootNode.getType(this.rootNode.ast);\r\n }\r\n\r\n getSubKernelResultType(index) {\r\n const subKernelNode = this.subKernelNodes[index];\r\n let called = false;\r\n for (let functionCallIndex = 0; functionCallIndex < this.rootNode.functionCalls.length; functionCallIndex++) {\r\n const functionCall = this.rootNode.functionCalls[functionCallIndex];\r\n if (functionCall.ast.callee.name === subKernelNode.name) {\r\n called = true;\r\n }\r\n }\r\n if (!called) {\r\n throw new Error(`SubKernel ${ subKernelNode.name } never called by kernel`);\r\n }\r\n return subKernelNode.returnType || subKernelNode.getType(subKernelNode.getJsAST());\r\n }\r\n\r\n getReturnTypes() {\r\n const result = {\r\n [this.rootNode.name]: this.rootNode.getType(this.rootNode.ast),\r\n };\r\n const list = this.traceFunctionCalls(this.rootNode.name);\r\n for (let i = 0; i < list.length; i++) {\r\n const functionName = list[i];\r\n const functionNode = this.functionMap[functionName];\r\n result[functionName] = functionNode.getType(functionNode.ast);\r\n }\r\n return result;\r\n }\r\n}\r\n","export class FunctionTracer {\r\n constructor(ast) {\r\n this.runningContexts = [];\r\n this.contexts = [];\r\n this.functionCalls = [];\r\n this.declarations = [];\r\n this.identifiers = [];\r\n this.functions = [];\r\n this.returnStatements = [];\r\n this.inLoopInit = false;\r\n this.scan(ast);\r\n }\r\n\r\n get currentContext() {\r\n return this.runningContexts.length > 0 ? this.runningContexts[this.runningContexts.length - 1] : null;\r\n }\r\n\r\n newContext(run) {\r\n const newContext = Object.assign({}, this.currentContext);\r\n this.contexts.push(newContext);\r\n this.runningContexts.push(newContext);\r\n run();\r\n this.runningContexts.pop();\r\n }\r\n\r\n /**\r\n * Recursively scans AST for declarations and functions, and add them to their respective context\r\n * @param ast\r\n */\r\n scan(ast) {\r\n if (Array.isArray(ast)) {\r\n for (let i = 0; i < ast.length; i++) {\r\n this.scan(ast[i]);\r\n }\r\n return;\r\n }\r\n switch (ast.type) {\r\n case 'Program':\r\n this.scan(ast.body);\r\n break;\r\n case 'BlockStatement':\r\n this.newContext(() => {\r\n this.scan(ast.body);\r\n });\r\n break;\r\n case 'AssignmentExpression':\r\n case 'LogicalExpression':\r\n this.scan(ast.left);\r\n this.scan(ast.right);\r\n break;\r\n case 'BinaryExpression':\r\n this.scan(ast.left);\r\n this.scan(ast.right);\r\n break;\r\n case 'UpdateExpression':\r\n case 'UnaryExpression':\r\n this.scan(ast.argument);\r\n break;\r\n case 'VariableDeclaration':\r\n this.scan(ast.declarations);\r\n break;\r\n case 'VariableDeclarator':\r\n const { currentContext } = this;\r\n const declaration = {\r\n ast: ast,\r\n context: currentContext,\r\n name: ast.id.name,\r\n origin: 'declaration',\r\n forceInteger: this.inLoopInit,\r\n assignable: !this.inLoopInit && !currentContext.hasOwnProperty(ast.id.name),\r\n };\r\n currentContext[ast.id.name] = declaration;\r\n this.declarations.push(declaration);\r\n this.scan(ast.id);\r\n this.scan(ast.init);\r\n break;\r\n case 'FunctionExpression':\r\n case 'FunctionDeclaration':\r\n if (this.runningContexts.length === 0) {\r\n this.scan(ast.body);\r\n } else {\r\n this.functions.push(ast);\r\n }\r\n break;\r\n case 'IfStatement':\r\n this.scan(ast.test);\r\n this.scan(ast.consequent);\r\n if (ast.alternate) this.scan(ast.alternate);\r\n break;\r\n case 'ForStatement':\r\n this.newContext(() => {\r\n this.inLoopInit = true;\r\n this.scan(ast.init);\r\n this.inLoopInit = false;\r\n this.scan(ast.test);\r\n this.scan(ast.update);\r\n this.newContext(() => {\r\n this.scan(ast.body);\r\n });\r\n });\r\n break;\r\n case 'DoWhileStatement':\r\n case 'WhileStatement':\r\n this.newContext(() => {\r\n this.scan(ast.body);\r\n this.scan(ast.test);\r\n });\r\n break;\r\n case 'Identifier':\r\n this.identifiers.push({\r\n context: this.currentContext,\r\n ast,\r\n });\r\n break;\r\n case 'ReturnStatement':\r\n this.returnStatements.push(ast);\r\n this.scan(ast.argument);\r\n break;\r\n case 'MemberExpression':\r\n this.scan(ast.object);\r\n this.scan(ast.property);\r\n break;\r\n case 'ExpressionStatement':\r\n this.scan(ast.expression);\r\n break;\r\n case 'CallExpression':\r\n this.functionCalls.push({\r\n context: this.currentContext,\r\n ast,\r\n });\r\n this.scan(ast.arguments);\r\n break;\r\n case 'ArrayExpression':\r\n this.scan(ast.elements);\r\n break;\r\n case 'ConditionalExpression':\r\n this.scan(ast.test);\r\n this.scan(ast.alternate);\r\n this.scan(ast.consequent);\r\n break;\r\n case 'SwitchStatement':\r\n this.scan(ast.discriminant);\r\n this.scan(ast.cases);\r\n break;\r\n case 'SwitchCase':\r\n this.scan(ast.test);\r\n this.scan(ast.consequent);\r\n break;\r\n case 'ThisExpression':\r\n this.scan(ast.left);\r\n this.scan(ast.right);\r\n break;\r\n case 'Literal':\r\n case 'DebuggerStatement':\r\n case 'EmptyStatement':\r\n case 'BreakStatement':\r\n case 'ContinueStatement':\r\n break;\r\n default:\r\n throw new Error(`unhandled type \"${ast.type}\"`);\r\n }\r\n }\r\n}\r\n","import { parse } from 'acorn';\r\nimport { FunctionTracer } from './function-tracer';\r\nimport {\r\n getArgumentNamesFromString,\r\n getAstString,\r\n getFunctionNameFromString,\r\n isFunctionString,\r\n} from '../common';\r\n\r\n/**\r\n *\r\n * @desc Represents a single function, inside JS, webGL, or openGL.\r\n *This handles all the raw state, converted state, etc. Of a single function.
\r\n */\r\nexport class FunctionNode {\r\n /**\r\n *\r\n * @param {string|object} source\r\n * @param {IFunctionSettings} [settings]\r\n */\r\n constructor(source, settings) {\r\n if (!source && !settings.ast) {\r\n throw new Error('source parameter is missing');\r\n }\r\n settings = settings || {};\r\n this.source = source;\r\n this.ast = null;\r\n this.name = typeof source === 'string' ? settings.isRootKernel ?\r\n 'kernel' :\r\n (settings.name || getFunctionNameFromString(source)) : null;\r\n this.calledFunctions = [];\r\n this.constants = {};\r\n this.constantTypes = {};\r\n this.constantBitRatios = {};\r\n this.isRootKernel = false;\r\n this.isSubKernel = false;\r\n this.debug = null;\r\n this.declarations = null;\r\n this.functions = null;\r\n this.identifiers = null;\r\n this.contexts = null;\r\n this.functionCalls = null;\r\n this.states = [];\r\n this.needsArgumentType = null;\r\n this.assignArgumentType = null;\r\n this.lookupReturnType = null;\r\n this.lookupFunctionArgumentTypes = null;\r\n this.lookupFunctionArgumentBitRatio = null;\r\n this.triggerImplyArgumentType = null;\r\n this.triggerImplyArgumentBitRatio = null;\r\n this.onNestedFunction = null;\r\n this.onFunctionCall = null;\r\n this.optimizeFloatMemory = null;\r\n this.precision = null;\r\n this.loopMaxIterations = null;\r\n this.argumentNames = (typeof this.source === 'string' ? getArgumentNamesFromString(this.source) : null);\r\n this.argumentTypes = [];\r\n this.argumentSizes = [];\r\n this.argumentBitRatios = null;\r\n this.returnType = null;\r\n this.output = [];\r\n this.plugins = null;\r\n this.leadingReturnStatement = null;\r\n this.followingReturnStatement = null;\r\n this.dynamicOutput = null;\r\n this.dynamicArguments = null;\r\n this.strictTypingChecking = false;\r\n this.fixIntegerDivisionAccuracy = null;\r\n this.warnVarUsage = true;\r\n\r\n if (settings) {\r\n for (const p in settings) {\r\n if (!settings.hasOwnProperty(p)) continue;\r\n if (!this.hasOwnProperty(p)) continue;\r\n this[p] = settings[p];\r\n }\r\n }\r\n\r\n this.literalTypes = {};\r\n\r\n this.validate();\r\n this._string = null;\r\n this._internalVariableNames = {};\r\n }\r\n\r\n validate() {\r\n if (typeof this.source !== 'string' && !this.ast) {\r\n throw new Error('this.source not a string');\r\n }\r\n\r\n if (!this.ast && !isFunctionString(this.source)) {\r\n throw new Error('this.source not a function string');\r\n }\r\n\r\n if (!this.name) {\r\n throw new Error('this.name could not be set');\r\n }\r\n\r\n if (this.argumentTypes.length > 0 && this.argumentTypes.length !== this.argumentNames.length) {\r\n throw new Error(`argumentTypes count of ${ this.argumentTypes.length } exceeds ${ this.argumentNames.length }`);\r\n }\r\n\r\n if (this.output.length < 1) {\r\n throw new Error('this.output is not big enough');\r\n }\r\n }\r\n\r\n /**\r\n * @param {String} name\r\n * @returns {boolean}\r\n */\r\n isIdentifierConstant(name) {\r\n if (!this.constants) return false;\r\n return this.constants.hasOwnProperty(name);\r\n }\r\n\r\n isInput(argumentName) {\r\n return this.argumentTypes[this.argumentNames.indexOf(argumentName)] === 'Input';\r\n }\r\n\r\n pushState(state) {\r\n this.states.push(state);\r\n }\r\n\r\n popState(state) {\r\n if (this.state !== state) {\r\n throw new Error(`Cannot popState ${ state } when in ${ this.state }`);\r\n }\r\n this.states.pop();\r\n }\r\n\r\n isState(state) {\r\n return this.state === state;\r\n }\r\n\r\n get state() {\r\n return this.states[this.states.length - 1];\r\n }\r\n\r\n /**\r\n * @function\r\n * @name astMemberExpressionUnroll\r\n * @desc Parses the abstract syntax tree for binary expression.\r\n *\r\n *Utility function for astCallExpression.
\r\n *\r\n * @param {Object} ast - the AST object to parse\r\n *\r\n * @returns {String} the function namespace call, unrolled\r\n */\r\n astMemberExpressionUnroll(ast) {\r\n if (ast.type === 'Identifier') {\r\n return ast.name;\r\n } else if (ast.type === 'ThisExpression') {\r\n return 'this';\r\n }\r\n\r\n if (ast.type === 'MemberExpression') {\r\n if (ast.object && ast.property) {\r\n //babel sniffing\r\n if (ast.object.hasOwnProperty('name') && ast.object.name[0] === '_') {\r\n return this.astMemberExpressionUnroll(ast.property);\r\n }\r\n\r\n return (\r\n this.astMemberExpressionUnroll(ast.object) +\r\n '.' +\r\n this.astMemberExpressionUnroll(ast.property)\r\n );\r\n }\r\n }\r\n\r\n //babel sniffing\r\n if (ast.hasOwnProperty('expressions')) {\r\n const firstExpression = ast.expressions[0];\r\n if (firstExpression.type === 'Literal' && firstExpression.value === 0 && ast.expressions.length === 2) {\r\n return this.astMemberExpressionUnroll(ast.expressions[1]);\r\n }\r\n }\r\n\r\n // Failure, unknown expression\r\n throw this.astErrorOutput('Unknown astMemberExpressionUnroll', ast);\r\n }\r\n\r\n /**\r\n * @desc Parses the class function JS, and returns its Abstract Syntax Tree object.\r\n * This is used internally to convert to shader code\r\n *\r\n * @param {Object} [inParser] - Parser to use, assumes in scope 'parser' if null or undefined\r\n *\r\n * @returns {Object} The function AST Object, note that result is cached under this.ast;\r\n */\r\n getJsAST(inParser) {\r\n if (this.ast) {\r\n return this.ast;\r\n }\r\n if (typeof this.source === 'object') {\r\n this.traceFunctionAST(this.source);\r\n return this.ast = this.source;\r\n }\r\n\r\n const parser = inParser && inParser.hasOwnProperty('parse') ? inParser.parse : parse\r\n if (inParser === null) {\r\n throw new Error('Missing JS to AST parser');\r\n }\r\n\r\n const ast = Object.freeze(parser(`const parser_${ this.name } = ${ this.source };`, {\r\n locations: true\r\n }));\r\n // take out the function object, outside the var declarations\r\n const functionAST = ast.body[0].declarations[0].init;\r\n this.traceFunctionAST(functionAST);\r\n\r\n if (!ast) {\r\n throw new Error('Failed to parse JS code');\r\n }\r\n\r\n return this.ast = functionAST;\r\n }\r\n\r\n traceFunctionAST(ast) {\r\n const { contexts, declarations, functions, identifiers, functionCalls } = new FunctionTracer(ast);\r\n this.contexts = contexts;\r\n this.identifiers = identifiers;\r\n this.functionCalls = functionCalls;\r\n this.declarations = [];\r\n this.functions = functions;\r\n for (let i = 0; i < declarations.length; i++) {\r\n const declaration = declarations[i];\r\n const { ast, context, name, origin, forceInteger, assignable } = declaration;\r\n const { init } = ast;\r\n const dependencies = this.getDependencies(init);\r\n let valueType = null;\r\n\r\n if (forceInteger) {\r\n valueType = 'Integer';\r\n } else {\r\n if (init) {\r\n const realType = this.getType(init);\r\n switch (realType) {\r\n case 'Integer':\r\n case 'Float':\r\n case 'Number':\r\n if (init.type === 'MemberExpression') {\r\n valueType = realType;\r\n } else {\r\n valueType = 'Number';\r\n }\r\n break;\r\n case 'LiteralInteger':\r\n valueType = 'Number';\r\n break;\r\n default:\r\n valueType = realType;\r\n }\r\n }\r\n }\r\n this.declarations.push({\r\n valueType,\r\n dependencies,\r\n isSafe: this.isSafeDependencies(dependencies),\r\n ast,\r\n name,\r\n context,\r\n origin,\r\n assignable,\r\n });\r\n }\r\n\r\n for (let i = 0; i < functions.length; i++) {\r\n this.onNestedFunction(functions[i]);\r\n }\r\n }\r\n\r\n getDeclaration(ast) {\r\n for (let i = 0; i < this.identifiers.length; i++) {\r\n const identifier = this.identifiers[i];\r\n if (ast === identifier.ast && identifier.context.hasOwnProperty(ast.name)) {\r\n for (let j = 0; j < this.declarations.length; j++) {\r\n const declaration = this.declarations[j];\r\n if (declaration.name === ast.name && declaration.context[ast.name] === identifier.context[ast.name]) {\r\n return declaration;\r\n }\r\n }\r\n }\r\n }\r\n return null;\r\n }\r\n\r\n /**\r\n * @desc Return the type of parameter sent to subKernel/Kernel.\r\n * @param {Object} ast - Identifier\r\n * @returns {String} Type of the parameter\r\n */\r\n getVariableType(ast) {\r\n if (ast.type !== 'Identifier') {\r\n throw new Error(`ast of ${ast.type} not \"Identifier\"`);\r\n }\r\n let type = null;\r\n const argumentIndex = this.argumentNames.indexOf(ast.name);\r\n if (argumentIndex === -1) {\r\n const declaration = this.getDeclaration(ast);\r\n if (declaration) {\r\n return declaration.valueType;\r\n }\r\n } else {\r\n const argumentType = this.argumentTypes[argumentIndex];\r\n if (argumentType) {\r\n type = argumentType;\r\n }\r\n }\r\n if (!type && this.strictTypingChecking) {\r\n throw new Error(`Declaration of ${name} not found`);\r\n }\r\n return type;\r\n }\r\n\r\n /**\r\n * Generally used to lookup the value type returned from a member expressions\r\n * @param {String} type\r\n * @return {String}\r\n */\r\n getLookupType(type) {\r\n if (!typeLookupMap.hasOwnProperty(type)) {\r\n throw new Error(`unknown typeLookupMap ${ type }`);\r\n }\r\n return typeLookupMap[type];\r\n }\r\n\r\n getConstantType(constantName) {\r\n if (this.constantTypes[constantName]) {\r\n const type = this.constantTypes[constantName];\r\n if (type === 'Float') {\r\n return 'Number';\r\n } else {\r\n return type;\r\n }\r\n }\r\n throw new Error(`Type for constant \"${ constantName }\" not declared`);\r\n }\r\n\r\n toString() {\r\n if (this._string) return this._string;\r\n return this._string = this.astGeneric(this.getJsAST(), []).join('').trim();\r\n }\r\n\r\n toJSON() {\r\n const settings = {\r\n source: this.source,\r\n name: this.name,\r\n constants: this.constants,\r\n constantTypes: this.constantTypes,\r\n isRootKernel: this.isRootKernel,\r\n isSubKernel: this.isSubKernel,\r\n debug: this.debug,\r\n output: this.output,\r\n loopMaxIterations: this.loopMaxIterations,\r\n argumentNames: this.argumentNames,\r\n argumentTypes: this.argumentTypes,\r\n argumentSizes: this.argumentSizes,\r\n returnType: this.returnType,\r\n leadingReturnStatement: this.leadingReturnStatement,\r\n followingReturnStatement: this.followingReturnStatement,\r\n };\r\n\r\n return {\r\n ast: this.ast,\r\n settings\r\n };\r\n }\r\n\r\n /**\r\n * Recursively looks up type for ast expression until it's found\r\n * @param ast\r\n * @returns {String|null}\r\n */\r\n getType(ast) {\r\n if (Array.isArray(ast)) {\r\n return this.getType(ast[ast.length - 1]);\r\n }\r\n switch (ast.type) {\r\n case 'BlockStatement':\r\n return this.getType(ast.body);\r\n case 'ArrayExpression':\r\n return `Array(${ ast.elements.length })`;\r\n case 'Literal':\r\n const literalKey = `${ast.start},${ast.end}`;\r\n if (this.literalTypes[literalKey]) {\r\n return this.literalTypes[literalKey];\r\n }\r\n if (Number.isInteger(ast.value)) {\r\n return 'LiteralInteger';\r\n } else if (ast.value === true || ast.value === false) {\r\n return 'Boolean';\r\n } else {\r\n return 'Number';\r\n }\r\n case 'AssignmentExpression':\r\n return this.getType(ast.left);\r\n case 'CallExpression':\r\n if (this.isAstMathFunction(ast)) {\r\n return 'Number';\r\n }\r\n if (!ast.callee || !ast.callee.name) {\r\n if (ast.callee.type === 'SequenceExpression' && ast.callee.expressions[ast.callee.expressions.length - 1].property.name) {\r\n const functionName = ast.callee.expressions[ast.callee.expressions.length - 1].property.name;\r\n this.inferArgumentTypesIfNeeded(functionName, ast.arguments);\r\n return this.lookupReturnType(functionName, ast, this);\r\n }\r\n throw this.astErrorOutput('Unknown call expression', ast);\r\n }\r\n if (ast.callee && ast.callee.name) {\r\n const functionName = ast.callee.name;\r\n this.inferArgumentTypesIfNeeded(functionName, ast.arguments);\r\n return this.lookupReturnType(functionName, ast, this);\r\n }\r\n throw this.astErrorOutput(`Unhandled getType Type \"${ ast.type }\"`, ast);\r\n case 'BinaryExpression':\r\n // modulos is Number\r\n switch (ast.operator) {\r\n case '%':\r\n case '/':\r\n if (this.fixIntegerDivisionAccuracy) {\r\n return 'Number';\r\n } else {\r\n break;\r\n }\r\n case '>':\r\n case '<':\r\n return 'Boolean';\r\n case '&':\r\n case '|':\r\n case '^':\r\n case '<<':\r\n case '>>':\r\n case '>>>':\r\n return 'Integer';\r\n }\r\n const type = this.getType(ast.left);\r\n if (this.isState('skip-literal-correction')) return type;\r\n if (type === 'LiteralInteger') {\r\n const rightType = this.getType(ast.right);\r\n if (rightType === 'LiteralInteger') {\r\n if (ast.left.value % 1 === 0) {\r\n return 'Integer';\r\n } else {\r\n return 'Float';\r\n }\r\n }\r\n return rightType;\r\n }\r\n return typeLookupMap[type] || type;\r\n case 'UpdateExpression':\r\n return this.getType(ast.argument);\r\n case 'UnaryExpression':\r\n if (ast.operator === '~') {\r\n return 'Integer';\r\n }\r\n return this.getType(ast.argument);\r\n case 'VariableDeclaration': {\r\n const declarations = ast.declarations;\r\n let lastType;\r\n for (let i = 0; i < declarations.length; i++) {\r\n const declaration = declarations[i];\r\n lastType = this.getType(declaration);\r\n }\r\n if (!lastType) {\r\n throw this.astErrorOutput(`Unable to find type for declaration`, ast);\r\n }\r\n return lastType;\r\n }\r\n case 'VariableDeclarator':\r\n const declaration = this.getDeclaration(ast.id);\r\n if (!declaration) {\r\n throw this.astErrorOutput(`Unable to find declarator`, ast);\r\n }\r\n\r\n if (!declaration.valueType) {\r\n throw this.astErrorOutput(`Unable to find declarator valueType`, ast);\r\n }\r\n\r\n return declaration.valueType;\r\n case 'Identifier':\r\n if (ast.name === 'Infinity') {\r\n return 'Number';\r\n }\r\n if (this.isAstVariable(ast)) {\r\n const signature = this.getVariableSignature(ast);\r\n if (signature === 'value') {\r\n const type = this.getVariableType(ast);\r\n if (!type) {\r\n throw this.astErrorOutput(`Unable to find identifier valueType`, ast);\r\n }\r\n return type;\r\n }\r\n }\r\n const origin = this.findIdentifierOrigin(ast);\r\n if (origin && origin.init) {\r\n return this.getType(origin.init);\r\n }\r\n return null;\r\n case 'ReturnStatement':\r\n return this.getType(ast.argument);\r\n case 'MemberExpression':\r\n if (this.isAstMathFunction(ast)) {\r\n switch (ast.property.name) {\r\n case 'ceil':\r\n return 'Integer';\r\n case 'floor':\r\n return 'Integer';\r\n case 'round':\r\n return 'Integer';\r\n }\r\n return 'Number';\r\n }\r\n if (this.isAstVariable(ast)) {\r\n const variableSignature = this.getVariableSignature(ast);\r\n switch (variableSignature) {\r\n case 'value[]':\r\n return this.getLookupType(this.getVariableType(ast.object));\r\n case 'value[][]':\r\n return this.getLookupType(this.getVariableType(ast.object.object));\r\n case 'value[][][]':\r\n return this.getLookupType(this.getVariableType(ast.object.object.object));\r\n case 'value[][][][]':\r\n return this.getLookupType(this.getVariableType(ast.object.object.object.object));\r\n case 'value.thread.value':\r\n case 'this.thread.value':\r\n return 'Integer';\r\n case 'this.output.value':\r\n return this.dynamicOutput ? 'Integer' : 'LiteralInteger';\r\n case 'this.constants.value':\r\n return this.getConstantType(ast.property.name);\r\n case 'this.constants.value[]':\r\n return this.getLookupType(this.getConstantType(ast.object.property.name));\r\n case 'this.constants.value[][]':\r\n return this.getLookupType(this.getConstantType(ast.object.object.property.name));\r\n case 'this.constants.value[][][]':\r\n return this.getLookupType(this.getConstantType(ast.object.object.object.property.name));\r\n case 'this.constants.value[][][][]':\r\n return this.getLookupType(this.getConstantType(ast.object.object.object.object.property.name));\r\n case 'fn()[]':\r\n return this.getLookupType(this.getType(ast.object));\r\n case 'fn()[][]':\r\n return this.getLookupType(this.getType(ast.object));\r\n case 'fn()[][][]':\r\n return this.getLookupType(this.getType(ast.object));\r\n case 'value.value':\r\n if (this.isAstMathVariable(ast)) {\r\n return 'Number';\r\n }\r\n switch (ast.property.name) {\r\n case 'r':\r\n return this.getLookupType(this.getVariableType(ast.object));\r\n case 'g':\r\n return this.getLookupType(this.getVariableType(ast.object));\r\n case 'b':\r\n return this.getLookupType(this.getVariableType(ast.object));\r\n case 'a':\r\n return this.getLookupType(this.getVariableType(ast.object));\r\n }\r\n case '[][]':\r\n return 'Number';\r\n }\r\n throw this.astErrorOutput('Unhandled getType MemberExpression', ast);\r\n }\r\n throw this.astErrorOutput('Unhandled getType MemberExpression', ast);\r\n case 'ConditionalExpression':\r\n return this.getType(ast.consequent);\r\n case 'FunctionDeclaration':\r\n case 'FunctionExpression':\r\n const lastReturn = this.findLastReturn(ast.body);\r\n if (lastReturn) {\r\n return this.getType(lastReturn);\r\n }\r\n return null;\r\n case 'IfStatement':\r\n return this.getType(ast.consequent);\r\n default:\r\n throw this.astErrorOutput(`Unhandled getType Type \"${ ast.type }\"`, ast);\r\n }\r\n }\r\n\r\n inferArgumentTypesIfNeeded(functionName, args) {\r\n // ensure arguments are filled in, so when we lookup return type, we already can infer it\r\n for (let i = 0; i < args.length; i++) {\r\n if (!this.needsArgumentType(functionName, i)) continue;\r\n const type = this.getType(args[i]);\r\n if (!type) {\r\n throw this.astErrorOutput(`Unable to infer argument ${i}`, args[i]);\r\n }\r\n this.assignArgumentType(functionName, i, type);\r\n }\r\n }\r\n\r\n isAstMathVariable(ast) {\r\n const mathProperties = [\r\n 'E',\r\n 'PI',\r\n 'SQRT2',\r\n 'SQRT1_2',\r\n 'LN2',\r\n 'LN10',\r\n 'LOG2E',\r\n 'LOG10E',\r\n ];\r\n return ast.type === 'MemberExpression' &&\r\n ast.object && ast.object.type === 'Identifier' &&\r\n ast.object.name === 'Math' &&\r\n ast.property &&\r\n ast.property.type === 'Identifier' &&\r\n mathProperties.indexOf(ast.property.name) > -1;\r\n }\r\n\r\n isAstMathFunction(ast) {\r\n const mathFunctions = [\r\n 'abs',\r\n 'acos',\r\n 'asin',\r\n 'atan',\r\n 'atan2',\r\n 'ceil',\r\n 'cos',\r\n 'exp',\r\n 'floor',\r\n 'log',\r\n 'log2',\r\n 'max',\r\n 'min',\r\n 'pow',\r\n 'random',\r\n 'round',\r\n 'sign',\r\n 'sin',\r\n 'sqrt',\r\n 'tan',\r\n ];\r\n return ast.type === 'CallExpression' &&\r\n ast.callee &&\r\n ast.callee.type === 'MemberExpression' &&\r\n ast.callee.object &&\r\n ast.callee.object.type === 'Identifier' &&\r\n ast.callee.object.name === 'Math' &&\r\n ast.callee.property &&\r\n ast.callee.property.type === 'Identifier' &&\r\n mathFunctions.indexOf(ast.callee.property.name) > -1;\r\n }\r\n\r\n isAstVariable(ast) {\r\n return ast.type === 'Identifier' || ast.type === 'MemberExpression';\r\n }\r\n\r\n isSafe(ast) {\r\n return this.isSafeDependencies(this.getDependencies(ast));\r\n }\r\n\r\n isSafeDependencies(dependencies) {\r\n return dependencies && dependencies.every ? dependencies.every(dependency => dependency.isSafe) : true;\r\n }\r\n\r\n /**\r\n *\r\n * @param ast\r\n * @param dependencies\r\n * @param isNotSafe\r\n * @return {Array}\r\n */\r\n getDependencies(ast, dependencies, isNotSafe) {\r\n if (!dependencies) {\r\n dependencies = [];\r\n }\r\n if (!ast) return null;\r\n if (Array.isArray(ast)) {\r\n for (let i = 0; i < ast.length; i++) {\r\n this.getDependencies(ast[i], dependencies, isNotSafe);\r\n }\r\n return dependencies;\r\n }\r\n switch (ast.type) {\r\n case 'AssignmentExpression':\r\n this.getDependencies(ast.left, dependencies, isNotSafe);\r\n this.getDependencies(ast.right, dependencies, isNotSafe);\r\n return dependencies;\r\n case 'ConditionalExpression':\r\n this.getDependencies(ast.test, dependencies, isNotSafe);\r\n this.getDependencies(ast.alternate, dependencies, isNotSafe);\r\n this.getDependencies(ast.consequent, dependencies, isNotSafe);\r\n return dependencies;\r\n case 'Literal':\r\n dependencies.push({\r\n origin: 'literal',\r\n value: ast.value,\r\n isSafe: isNotSafe === true ? false : ast.value > -Infinity && ast.value < Infinity && !isNaN(ast.value)\r\n });\r\n break;\r\n case 'VariableDeclarator':\r\n return this.getDependencies(ast.init, dependencies, isNotSafe);\r\n case 'Identifier':\r\n const declaration = this.getDeclaration(ast);\r\n if (declaration) {\r\n dependencies.push({\r\n name: ast.name,\r\n origin: 'declaration',\r\n isSafe: isNotSafe ? false : this.isSafeDependencies(declaration.dependencies),\r\n });\r\n } else if (this.argumentNames.indexOf(ast.name) > -1) {\r\n dependencies.push({\r\n name: ast.name,\r\n origin: 'argument',\r\n isSafe: false,\r\n });\r\n } else if (this.strictTypingChecking) {\r\n throw new Error(`Cannot find identifier origin \"${ast.name}\"`);\r\n }\r\n break;\r\n case 'FunctionDeclaration':\r\n return this.getDependencies(ast.body.body[ast.body.body.length - 1], dependencies, isNotSafe);\r\n case 'ReturnStatement':\r\n return this.getDependencies(ast.argument, dependencies);\r\n case 'BinaryExpression':\r\n isNotSafe = (ast.operator === '/' || ast.operator === '*');\r\n this.getDependencies(ast.left, dependencies, isNotSafe);\r\n this.getDependencies(ast.right, dependencies, isNotSafe);\r\n return dependencies;\r\n case 'UnaryExpression':\r\n case 'UpdateExpression':\r\n return this.getDependencies(ast.argument, dependencies, isNotSafe);\r\n case 'VariableDeclaration':\r\n return this.getDependencies(ast.declarations, dependencies, isNotSafe);\r\n case 'ArrayExpression':\r\n dependencies.push({\r\n origin: 'declaration',\r\n isSafe: true,\r\n });\r\n return dependencies;\r\n case 'CallExpression':\r\n dependencies.push({\r\n origin: 'function',\r\n isSafe: true,\r\n });\r\n return dependencies;\r\n case 'MemberExpression':\r\n const details = this.getMemberExpressionDetails(ast);\r\n switch (details.signature) {\r\n case 'value[]':\r\n this.getDependencies(ast.object, dependencies, isNotSafe);\r\n break;\r\n case 'value[][]':\r\n this.getDependencies(ast.object.object, dependencies, isNotSafe);\r\n break;\r\n case 'value[][][]':\r\n this.getDependencies(ast.object.object.object, dependencies, isNotSafe);\r\n break;\r\n case 'this.output.value':\r\n if (this.dynamicOutput) {\r\n dependencies.push({\r\n name: details.name,\r\n origin: 'output',\r\n isSafe: false,\r\n });\r\n }\r\n break;\r\n }\r\n if (details) {\r\n if (details.property) {\r\n this.getDependencies(details.property, dependencies, isNotSafe);\r\n }\r\n if (details.xProperty) {\r\n this.getDependencies(details.xProperty, dependencies, isNotSafe);\r\n }\r\n if (details.yProperty) {\r\n this.getDependencies(details.yProperty, dependencies, isNotSafe);\r\n }\r\n if (details.zProperty) {\r\n this.getDependencies(details.zProperty, dependencies, isNotSafe);\r\n }\r\n return dependencies;\r\n }\r\n default:\r\n throw this.astErrorOutput(`Unhandled type ${ ast.type } in getDependencies`, ast);\r\n }\r\n return dependencies;\r\n }\r\n\r\n getVariableSignature(ast) {\r\n if (!this.isAstVariable(ast)) {\r\n throw new Error(`ast of type \"${ ast.type }\" is not a variable signature`);\r\n }\r\n if (ast.type === 'Identifier') {\r\n return 'value';\r\n }\r\n const signature = [];\r\n while (true) {\r\n if (!ast) break;\r\n if (ast.computed) {\r\n signature.push('[]');\r\n } else if (ast.type === 'ThisExpression') {\r\n signature.unshift('this');\r\n } else if (ast.property && ast.property.name) {\r\n if (\r\n ast.property.name === 'x' ||\r\n ast.property.name === 'y' ||\r\n ast.property.name === 'z'\r\n ) {\r\n signature.unshift('.value');\r\n } else if (\r\n ast.property.name === 'constants' ||\r\n ast.property.name === 'thread' ||\r\n ast.property.name === 'output'\r\n ) {\r\n signature.unshift('.' + ast.property.name);\r\n } else {\r\n signature.unshift('.value');\r\n }\r\n } else if (ast.name) {\r\n signature.unshift('value');\r\n } else if (ast.callee && ast.callee.name) {\r\n signature.unshift('fn()');\r\n } else if (ast.elements) {\r\n signature.unshift('[]');\r\n } else {\r\n signature.unshift('unknown');\r\n }\r\n ast = ast.object;\r\n }\r\n\r\n const signatureString = signature.join('');\r\n const allowedExpressions = [\r\n 'value',\r\n 'value[]',\r\n 'value[][]',\r\n 'value[][][]',\r\n 'value[][][][]',\r\n 'value.value',\r\n 'value.thread.value',\r\n 'this.thread.value',\r\n 'this.output.value',\r\n 'this.constants.value',\r\n 'this.constants.value[]',\r\n 'this.constants.value[][]',\r\n 'this.constants.value[][][]',\r\n 'this.constants.value[][][][]',\r\n 'fn()[]',\r\n 'fn()[][]',\r\n 'fn()[][][]',\r\n '[][]',\r\n ];\r\n if (allowedExpressions.indexOf(signatureString) > -1) {\r\n return signatureString;\r\n }\r\n return null;\r\n }\r\n\r\n build() {\r\n return this.toString().length > 0;\r\n }\r\n\r\n /**\r\n * @desc Parses the abstract syntax tree for generically to its respective function\r\n * @param {Object} ast - the AST object to parse\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the parsed string array\r\n */\r\n astGeneric(ast, retArr) {\r\n if (ast === null) {\r\n throw this.astErrorOutput('NULL ast', ast);\r\n } else {\r\n if (Array.isArray(ast)) {\r\n for (let i = 0; i < ast.length; i++) {\r\n this.astGeneric(ast[i], retArr);\r\n }\r\n return retArr;\r\n }\r\n\r\n switch (ast.type) {\r\n case 'FunctionDeclaration':\r\n return this.astFunctionDeclaration(ast, retArr);\r\n case 'FunctionExpression':\r\n return this.astFunctionExpression(ast, retArr);\r\n case 'ReturnStatement':\r\n return this.astReturnStatement(ast, retArr);\r\n case 'Literal':\r\n return this.astLiteral(ast, retArr);\r\n case 'BinaryExpression':\r\n return this.astBinaryExpression(ast, retArr);\r\n case 'Identifier':\r\n return this.astIdentifierExpression(ast, retArr);\r\n case 'AssignmentExpression':\r\n return this.astAssignmentExpression(ast, retArr);\r\n case 'ExpressionStatement':\r\n return this.astExpressionStatement(ast, retArr);\r\n case 'EmptyStatement':\r\n return this.astEmptyStatement(ast, retArr);\r\n case 'BlockStatement':\r\n return this.astBlockStatement(ast, retArr);\r\n case 'IfStatement':\r\n return this.astIfStatement(ast, retArr);\r\n case 'SwitchStatement':\r\n return this.astSwitchStatement(ast, retArr);\r\n case 'BreakStatement':\r\n return this.astBreakStatement(ast, retArr);\r\n case 'ContinueStatement':\r\n return this.astContinueStatement(ast, retArr);\r\n case 'ForStatement':\r\n return this.astForStatement(ast, retArr);\r\n case 'WhileStatement':\r\n return this.astWhileStatement(ast, retArr);\r\n case 'DoWhileStatement':\r\n return this.astDoWhileStatement(ast, retArr);\r\n case 'VariableDeclaration':\r\n return this.astVariableDeclaration(ast, retArr);\r\n case 'VariableDeclarator':\r\n return this.astVariableDeclarator(ast, retArr);\r\n case 'ThisExpression':\r\n return this.astThisExpression(ast, retArr);\r\n case 'SequenceExpression':\r\n return this.astSequenceExpression(ast, retArr);\r\n case 'UnaryExpression':\r\n return this.astUnaryExpression(ast, retArr);\r\n case 'UpdateExpression':\r\n return this.astUpdateExpression(ast, retArr);\r\n case 'LogicalExpression':\r\n return this.astLogicalExpression(ast, retArr);\r\n case 'MemberExpression':\r\n return this.astMemberExpression(ast, retArr);\r\n case 'CallExpression':\r\n return this.astCallExpression(ast, retArr);\r\n case 'ArrayExpression':\r\n return this.astArrayExpression(ast, retArr);\r\n case 'DebuggerStatement':\r\n return this.astDebuggerStatement(ast, retArr);\r\n case 'ConditionalExpression':\r\n return this.astConditionalExpression(ast, retArr);\r\n }\r\n\r\n throw this.astErrorOutput('Unknown ast type : ' + ast.type, ast);\r\n }\r\n }\r\n /**\r\n * @desc To throw the AST error, with its location.\r\n * @param {string} error - the error message output\r\n * @param {Object} ast - the AST object where the error is\r\n */\r\n astErrorOutput(error, ast) {\r\n if (typeof this.source !== 'string') {\r\n return new Error(error);\r\n }\r\n\r\n const debugString = getAstString(this.source, ast);\r\n const leadingSource = this.source.substr(ast.start);\r\n const splitLines = leadingSource.split(/\\n/);\r\n const lineBefore = splitLines.length > 0 ? splitLines[splitLines.length - 1] : 0;\r\n return new Error(`${error} on line ${ splitLines.length }, position ${ lineBefore.length }:\\n ${ debugString }`);\r\n }\r\n\r\n astDebuggerStatement(arrNode, retArr) {\r\n return retArr;\r\n }\r\n\r\n astConditionalExpression(ast, retArr) {\r\n if (ast.type !== 'ConditionalExpression') {\r\n throw this.astErrorOutput('Not a conditional expression', ast);\r\n }\r\n retArr.push('(');\r\n this.astGeneric(ast.test, retArr);\r\n retArr.push('?');\r\n this.astGeneric(ast.consequent, retArr);\r\n retArr.push(':');\r\n this.astGeneric(ast.alternate, retArr);\r\n retArr.push(')');\r\n return retArr;\r\n }\r\n\r\n /**\r\n * @abstract\r\n * @param {Object} ast\r\n * @param {String[]} retArr\r\n * @returns {String[]}\r\n */\r\n astFunction(ast, retArr) {\r\n throw new Error(`\"astFunction\" not defined on ${ this.constructor.name }`);\r\n }\r\n\r\n /**\r\n * @desc Parses the abstract syntax tree for to its *named function declaration*\r\n * @param {Object} ast - the AST object to parse\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astFunctionDeclaration(ast, retArr) {\r\n if (this.isChildFunction(ast)) {\r\n return retArr;\r\n }\r\n return this.astFunction(ast, retArr);\r\n }\r\n astFunctionExpression(ast, retArr) {\r\n if (this.isChildFunction(ast)) {\r\n return retArr;\r\n }\r\n return this.astFunction(ast, retArr);\r\n }\r\n isChildFunction(ast) {\r\n for (let i = 0; i < this.functions.length; i++) {\r\n if (this.functions[i] === ast) {\r\n return true;\r\n }\r\n }\r\n return false;\r\n }\r\n astReturnStatement(ast, retArr) {\r\n return retArr;\r\n }\r\n astLiteral(ast, retArr) {\r\n this.literalTypes[`${ast.start},${ast.end}`] = 'Number';\r\n return retArr;\r\n }\r\n astBinaryExpression(ast, retArr) {\r\n return retArr;\r\n }\r\n astIdentifierExpression(ast, retArr) {\r\n return retArr;\r\n }\r\n astAssignmentExpression(ast, retArr) {\r\n return retArr;\r\n }\r\n /**\r\n * @desc Parses the abstract syntax tree for *generic expression* statement\r\n * @param {Object} esNode - An ast Node\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astExpressionStatement(esNode, retArr) {\r\n this.astGeneric(esNode.expression, retArr);\r\n retArr.push(';');\r\n return retArr;\r\n }\r\n /**\r\n * @desc Parses the abstract syntax tree for an *Empty* Statement\r\n * @param {Object} eNode - An ast Node\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astEmptyStatement(eNode, retArr) {\r\n return retArr;\r\n }\r\n astBlockStatement(ast, retArr) {\r\n return retArr;\r\n }\r\n astIfStatement(ast, retArr) {\r\n return retArr;\r\n }\r\n astSwitchStatement(ast, retArr) {\r\n return retArr;\r\n }\r\n /**\r\n * @desc Parses the abstract syntax tree for *Break* Statement\r\n * @param {Object} brNode - An ast Node\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astBreakStatement(brNode, retArr) {\r\n retArr.push('break;');\r\n return retArr;\r\n }\r\n /**\r\n * @desc Parses the abstract syntax tree for *Continue* Statement\r\n * @param {Object} crNode - An ast Node\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astContinueStatement(crNode, retArr) {\r\n retArr.push('continue;\\n');\r\n return retArr;\r\n }\r\n astForStatement(ast, retArr) {\r\n return retArr;\r\n }\r\n astWhileStatement(ast, retArr) {\r\n return retArr;\r\n }\r\n astDoWhileStatement(ast, retArr) {\r\n return retArr;\r\n }\r\n /**\r\n * @desc Parses the abstract syntax tree for *Variable Declaration*\r\n * @param {Object} varDecNode - An ast Node\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astVariableDeclaration(varDecNode, retArr) {\r\n const declarations = varDecNode.declarations;\r\n if (!declarations || !declarations[0] || !declarations[0].init) {\r\n throw this.astErrorOutput('Unexpected expression', varDecNode);\r\n }\r\n const result = [];\r\n const firstDeclaration = declarations[0];\r\n const init = firstDeclaration.init;\r\n let type = this.isState('in-for-loop-init') ? 'Integer' : this.getType(init);\r\n if (type === 'LiteralInteger') {\r\n // We had the choice to go either float or int, choosing float\r\n type = 'Number';\r\n }\r\n const markupType = typeMap[type];\r\n if (!markupType) {\r\n throw this.astErrorOutput(`Markup type ${ markupType } not handled`, varDecNode);\r\n }\r\n let dependencies = this.getDependencies(firstDeclaration.init);\r\n throw new Error('remove me');\r\n this.declarations[firstDeclaration.id.name] = Object.freeze({\r\n type,\r\n dependencies,\r\n isSafe: dependencies.every(dependency => dependency.isSafe)\r\n });\r\n const initResult = [`${type} user_${firstDeclaration.id.name}=`];\r\n this.astGeneric(init, initResult);\r\n result.push(initResult.join(''));\r\n\r\n // first declaration is done, now any added ones setup\r\n for (let i = 1; i < declarations.length; i++) {\r\n const declaration = declarations[i];\r\n dependencies = this.getDependencies(declaration);\r\n throw new Error('Remove me');\r\n this.declarations[declaration.id.name] = Object.freeze({\r\n type,\r\n dependencies,\r\n isSafe: false\r\n });\r\n this.astGeneric(declaration, result);\r\n }\r\n\r\n retArr.push(retArr, result.join(','));\r\n retArr.push(';');\r\n return retArr;\r\n }\r\n /**\r\n * @desc Parses the abstract syntax tree for *Variable Declarator*\r\n * @param {Object} iVarDecNode - An ast Node\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astVariableDeclarator(iVarDecNode, retArr) {\r\n this.astGeneric(iVarDecNode.id, retArr);\r\n if (iVarDecNode.init !== null) {\r\n retArr.push('=');\r\n this.astGeneric(iVarDecNode.init, retArr);\r\n }\r\n return retArr;\r\n }\r\n astThisExpression(ast, retArr) {\r\n return retArr;\r\n }\r\n astSequenceExpression(sNode, retArr) {\r\n for (let i = 0; i < sNode.expressions.length; i++) {\r\n if (i > 0) {\r\n retArr.push(',');\r\n }\r\n this.astGeneric(sNode.expressions, retArr);\r\n }\r\n return retArr;\r\n }\r\n /**\r\n * @desc Parses the abstract syntax tree for *Unary* Expression\r\n * @param {Object} uNode - An ast Node\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astUnaryExpression(uNode, retArr) {\r\n const unaryResult = this.checkAndUpconvertBitwiseUnary(uNode, retArr);\r\n if (unaryResult) {\r\n return retArr;\r\n }\r\n\r\n if (uNode.prefix) {\r\n retArr.push(uNode.operator);\r\n this.astGeneric(uNode.argument, retArr);\r\n } else {\r\n this.astGeneric(uNode.argument, retArr);\r\n retArr.push(uNode.operator);\r\n }\r\n\r\n return retArr;\r\n }\r\n\r\n checkAndUpconvertBitwiseUnary(uNode, retArr) {}\r\n\r\n /**\r\n * @desc Parses the abstract syntax tree for *Update* Expression\r\n * @param {Object} uNode - An ast Node\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astUpdateExpression(uNode, retArr) {\r\n if (uNode.prefix) {\r\n retArr.push(uNode.operator);\r\n this.astGeneric(uNode.argument, retArr);\r\n } else {\r\n this.astGeneric(uNode.argument, retArr);\r\n retArr.push(uNode.operator);\r\n }\r\n\r\n return retArr;\r\n }\r\n /**\r\n * @desc Parses the abstract syntax tree for *Logical* Expression\r\n * @param {Object} logNode - An ast Node\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astLogicalExpression(logNode, retArr) {\r\n retArr.push('(');\r\n this.astGeneric(logNode.left, retArr);\r\n retArr.push(logNode.operator);\r\n this.astGeneric(logNode.right, retArr);\r\n retArr.push(')');\r\n return retArr;\r\n }\r\n astMemberExpression(ast, retArr) {\r\n return retArr;\r\n }\r\n astCallExpression(ast, retArr) {\r\n return retArr;\r\n }\r\n astArrayExpression(ast, retArr) {\r\n return retArr;\r\n }\r\n\r\n /**\r\n *\r\n * @param ast\r\n * @return {IFunctionNodeMemberExpressionDetails}\r\n */\r\n getMemberExpressionDetails(ast) {\r\n if (ast.type !== 'MemberExpression') {\r\n throw this.astErrorOutput(`Expression ${ ast.type } not a MemberExpression`, ast);\r\n }\r\n let name = null;\r\n let type = null;\r\n const variableSignature = this.getVariableSignature(ast);\r\n switch (variableSignature) {\r\n case 'value':\r\n return null;\r\n case 'value.thread.value':\r\n case 'this.thread.value':\r\n case 'this.output.value':\r\n return {\r\n signature: variableSignature,\r\n type: 'Integer',\r\n name: ast.property.name\r\n };\r\n case 'value[]':\r\n if (typeof ast.object.name !== 'string') {\r\n throw this.astErrorOutput('Unexpected expression', ast);\r\n }\r\n name = ast.object.name;\r\n return {\r\n name,\r\n origin: 'user',\r\n signature: variableSignature,\r\n type: this.getVariableType(ast.object),\r\n xProperty: ast.property\r\n };\r\n case 'value[][]':\r\n if (typeof ast.object.object.name !== 'string') {\r\n throw this.astErrorOutput('Unexpected expression', ast);\r\n }\r\n name = ast.object.object.name;\r\n return {\r\n name,\r\n origin: 'user',\r\n signature: variableSignature,\r\n type: this.getVariableType(ast.object.object),\r\n yProperty: ast.object.property,\r\n xProperty: ast.property,\r\n };\r\n case 'value[][][]':\r\n if (typeof ast.object.object.object.name !== 'string') {\r\n throw this.astErrorOutput('Unexpected expression', ast);\r\n }\r\n name = ast.object.object.object.name;\r\n return {\r\n name,\r\n origin: 'user',\r\n signature: variableSignature,\r\n type: this.getVariableType(ast.object.object.object),\r\n zProperty: ast.object.object.property,\r\n yProperty: ast.object.property,\r\n xProperty: ast.property,\r\n };\r\n case 'value[][][][]':\r\n if (typeof ast.object.object.object.object.name !== 'string') {\r\n throw this.astErrorOutput('Unexpected expression', ast);\r\n }\r\n name = ast.object.object.object.object.name;\r\n return {\r\n name,\r\n origin: 'user',\r\n signature: variableSignature,\r\n type: this.getVariableType(ast.object.object.object.object),\r\n zProperty: ast.object.object.property,\r\n yProperty: ast.object.property,\r\n xProperty: ast.property,\r\n };\r\n case 'value.value':\r\n if (typeof ast.property.name !== 'string') {\r\n throw this.astErrorOutput('Unexpected expression', ast);\r\n }\r\n if (this.isAstMathVariable(ast)) {\r\n name = ast.property.name;\r\n return {\r\n name,\r\n origin: 'Math',\r\n type: 'Number',\r\n signature: variableSignature,\r\n };\r\n }\r\n switch (ast.property.name) {\r\n case 'r':\r\n case 'g':\r\n case 'b':\r\n case 'a':\r\n name = ast.object.name;\r\n return {\r\n name,\r\n property: ast.property.name,\r\n origin: 'user',\r\n signature: variableSignature,\r\n type: 'Number'\r\n };\r\n default:\r\n throw this.astErrorOutput('Unexpected expression', ast);\r\n }\r\n case 'this.constants.value':\r\n if (typeof ast.property.name !== 'string') {\r\n throw this.astErrorOutput('Unexpected expression', ast);\r\n }\r\n name = ast.property.name;\r\n type = this.getConstantType(name);\r\n if (!type) {\r\n throw this.astErrorOutput('Constant has no type', ast);\r\n }\r\n return {\r\n name,\r\n type,\r\n origin: 'constants',\r\n signature: variableSignature,\r\n };\r\n case 'this.constants.value[]':\r\n if (typeof ast.object.property.name !== 'string') {\r\n throw this.astErrorOutput('Unexpected expression', ast);\r\n }\r\n name = ast.object.property.name;\r\n type = this.getConstantType(name);\r\n if (!type) {\r\n throw this.astErrorOutput('Constant has no type', ast);\r\n }\r\n return {\r\n name,\r\n type,\r\n origin: 'constants',\r\n signature: variableSignature,\r\n xProperty: ast.property,\r\n };\r\n case 'this.constants.value[][]': {\r\n if (typeof ast.object.object.property.name !== 'string') {\r\n throw this.astErrorOutput('Unexpected expression', ast);\r\n }\r\n name = ast.object.object.property.name;\r\n type = this.getConstantType(name);\r\n if (!type) {\r\n throw this.astErrorOutput('Constant has no type', ast);\r\n }\r\n return {\r\n name,\r\n type,\r\n origin: 'constants',\r\n signature: variableSignature,\r\n yProperty: ast.object.property,\r\n xProperty: ast.property,\r\n };\r\n }\r\n case 'this.constants.value[][][]': {\r\n if (typeof ast.object.object.object.property.name !== 'string') {\r\n throw this.astErrorOutput('Unexpected expression', ast);\r\n }\r\n name = ast.object.object.object.property.name;\r\n type = this.getConstantType(name);\r\n if (!type) {\r\n throw this.astErrorOutput('Constant has no type', ast);\r\n }\r\n return {\r\n name,\r\n type,\r\n origin: 'constants',\r\n signature: variableSignature,\r\n zProperty: ast.object.object.property,\r\n yProperty: ast.object.property,\r\n xProperty: ast.property,\r\n };\r\n }\r\n case 'fn()[]':\r\n case '[][]':\r\n return {\r\n signature: variableSignature,\r\n property: ast.property,\r\n };\r\n default:\r\n throw this.astErrorOutput('Unexpected expression', ast);\r\n }\r\n }\r\n\r\n findIdentifierOrigin(astToFind) {\r\n const stack = [this.ast];\r\n\r\n while (stack.length > 0) {\r\n const atNode = stack[0];\r\n if (atNode.type === 'VariableDeclarator' && atNode.id && atNode.id.name && atNode.id.name === astToFind.name) {\r\n return atNode;\r\n }\r\n stack.shift();\r\n if (atNode.argument) {\r\n stack.push(atNode.argument);\r\n } else if (atNode.body) {\r\n stack.push(atNode.body);\r\n } else if (atNode.declarations) {\r\n stack.push(atNode.declarations);\r\n } else if (Array.isArray(atNode)) {\r\n for (let i = 0; i < atNode.length; i++) {\r\n stack.push(atNode[i]);\r\n }\r\n }\r\n }\r\n return null;\r\n }\r\n\r\n findLastReturn(ast) {\r\n const stack = [ast || this.ast];\r\n\r\n while (stack.length > 0) {\r\n const atNode = stack.pop();\r\n if (atNode.type === 'ReturnStatement') {\r\n return atNode;\r\n }\r\n if (atNode.type === 'FunctionDeclaration') {\r\n continue;\r\n }\r\n if (atNode.argument) {\r\n stack.push(atNode.argument);\r\n } else if (atNode.body) {\r\n stack.push(atNode.body);\r\n } else if (atNode.declarations) {\r\n stack.push(atNode.declarations);\r\n } else if (Array.isArray(atNode)) {\r\n for (let i = 0; i < atNode.length; i++) {\r\n stack.push(atNode[i]);\r\n }\r\n } else if (atNode.consequent) {\r\n stack.push(atNode.consequent);\r\n } else if (atNode.cases) {\r\n stack.push(atNode.cases);\r\n }\r\n }\r\n return null;\r\n }\r\n\r\n getInternalVariableName(name) {\r\n if (!this._internalVariableNames.hasOwnProperty(name)) {\r\n this._internalVariableNames[name] = 0;\r\n }\r\n this._internalVariableNames[name]++;\r\n if (this._internalVariableNames[name] === 1) {\r\n return name;\r\n }\r\n return name + this._internalVariableNames[name];\r\n }\r\n\r\n varWarn() {\r\n console.warn('var declarations are deprecated, weird things happen when falling back to CPU because var scope differs in javascript than in most languages. Use const or let');\r\n }\r\n}\r\n\r\nconst typeLookupMap = {\r\n 'Number': 'Number',\r\n 'Float': 'Float',\r\n 'Integer': 'Integer',\r\n 'Array': 'Number',\r\n 'Array(2)': 'Number',\r\n 'Array(3)': 'Number',\r\n 'Array(4)': 'Number',\r\n 'Array2D': 'Number',\r\n 'Array3D': 'Number',\r\n 'Input': 'Number',\r\n 'HTMLImage': 'Array(4)',\r\n 'HTMLVideo': 'Array(4)',\r\n 'HTMLImageArray': 'Array(4)',\r\n 'NumberTexture': 'Number',\r\n 'MemoryOptimizedNumberTexture': 'Number',\r\n 'Array1D(2)': 'Array(2)',\r\n 'Array1D(3)': 'Array(3)',\r\n 'Array1D(4)': 'Array(4)',\r\n 'Array2D(2)': 'Array(2)',\r\n 'Array2D(3)': 'Array(3)',\r\n 'Array2D(4)': 'Array(4)',\r\n 'Array3D(2)': 'Array(2)',\r\n 'Array3D(3)': 'Array(3)',\r\n 'Array3D(4)': 'Array(4)',\r\n 'ArrayTexture(1)': 'Number',\r\n 'ArrayTexture(2)': 'Array(2)',\r\n 'ArrayTexture(3)': 'Array(3)',\r\n 'ArrayTexture(4)': 'Array(4)',\r\n};\r\n","import { FunctionNode } from '../function-node';\r\n\r\n/**\r\n * @desc [INTERNAL] Represents a single function, inside JS\r\n *\r\n *This handles all the raw state, converted state, etc. Of a single function.
\r\n */\r\nexport class CPUFunctionNode extends FunctionNode {\r\n /**\r\n * @desc Parses the abstract syntax tree for to its *named function*\r\n * @param {Object} ast - the AST object to parse\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astFunction(ast, retArr) {\r\n\r\n // Setup function return type and name\r\n if (!this.isRootKernel) {\r\n retArr.push('function');\r\n retArr.push(' ');\r\n retArr.push(this.name);\r\n retArr.push('(');\r\n\r\n // Arguments handling\r\n for (let i = 0; i < this.argumentNames.length; ++i) {\r\n const argumentName = this.argumentNames[i];\r\n\r\n if (i > 0) {\r\n retArr.push(', ');\r\n }\r\n retArr.push('user_');\r\n retArr.push(argumentName);\r\n }\r\n\r\n // Function opening\r\n retArr.push(') {\\n');\r\n }\r\n\r\n // Body statement iteration\r\n for (let i = 0; i < ast.body.body.length; ++i) {\r\n this.astGeneric(ast.body.body[i], retArr);\r\n retArr.push('\\n');\r\n }\r\n\r\n if (!this.isRootKernel) {\r\n // Function closing\r\n retArr.push('}\\n');\r\n }\r\n return retArr;\r\n }\r\n\r\n /**\r\n * @desc Parses the abstract syntax tree for to *return* statement\r\n * @param {Object} ast - the AST object to parse\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astReturnStatement(ast, retArr) {\r\n const type = this.returnType || this.getType(ast.argument);\r\n\r\n if (!this.returnType) {\r\n this.returnType = type;\r\n }\r\n\r\n if (this.isRootKernel) {\r\n retArr.push(this.leadingReturnStatement);\r\n this.astGeneric(ast.argument, retArr);\r\n retArr.push(';\\n');\r\n retArr.push(this.followingReturnStatement);\r\n retArr.push('continue;\\n');\r\n } else if (this.isSubKernel) {\r\n retArr.push(`subKernelResult_${ this.name } = `);\r\n this.astGeneric(ast.argument, retArr);\r\n retArr.push(';');\r\n retArr.push(`return subKernelResult_${ this.name };`);\r\n } else {\r\n retArr.push('return ');\r\n this.astGeneric(ast.argument, retArr);\r\n retArr.push(';');\r\n }\r\n return retArr;\r\n }\r\n\r\n /**\r\n * @desc Parses the abstract syntax tree for *literal value*\r\n * @param {Object} ast - the AST object to parse\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astLiteral(ast, retArr) {\r\n\r\n // Reject non numeric literals\r\n if (isNaN(ast.value)) {\r\n throw this.astErrorOutput(\r\n 'Non-numeric literal not supported : ' + ast.value,\r\n ast\r\n );\r\n }\r\n\r\n retArr.push(ast.value);\r\n\r\n return retArr;\r\n }\r\n\r\n /**\r\n * @desc Parses the abstract syntax tree for *binary* expression\r\n * @param {Object} ast - the AST object to parse\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astBinaryExpression(ast, retArr) {\r\n retArr.push('(');\r\n this.astGeneric(ast.left, retArr);\r\n retArr.push(ast.operator);\r\n this.astGeneric(ast.right, retArr);\r\n retArr.push(')');\r\n return retArr;\r\n }\r\n\r\n /**\r\n * @desc Parses the abstract syntax tree for *identifier* expression\r\n * @param {Object} idtNode - An ast Node\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astIdentifierExpression(idtNode, retArr) {\r\n if (idtNode.type !== 'Identifier') {\r\n throw this.astErrorOutput(\r\n 'IdentifierExpression - not an Identifier',\r\n idtNode\r\n );\r\n }\r\n\r\n switch (idtNode.name) {\r\n case 'Infinity':\r\n retArr.push('Infinity');\r\n break;\r\n default:\r\n if (this.constants && this.constants.hasOwnProperty(idtNode.name)) {\r\n retArr.push('constants_' + idtNode.name);\r\n } else {\r\n retArr.push('user_' + idtNode.name);\r\n }\r\n }\r\n\r\n return retArr;\r\n }\r\n\r\n /**\r\n * @desc Parses the abstract syntax tree for *for-loop* expression\r\n * @param {Object} forNode - An ast Node\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the parsed webgl string\r\n */\r\n astForStatement(forNode, retArr) {\r\n if (forNode.type !== 'ForStatement') {\r\n throw this.astErrorOutput('Invalid for statement', forNode);\r\n }\r\n\r\n const initArr = [];\r\n const testArr = [];\r\n const updateArr = [];\r\n const bodyArr = [];\r\n let isSafe = null;\r\n\r\n if (forNode.init) {\r\n this.pushState('in-for-loop-init');\r\n this.astGeneric(forNode.init, initArr);\r\n for (let i = 0; i < initArr.length; i++) {\r\n if (initArr[i].includes && initArr[i].includes(',')) {\r\n isSafe = false;\r\n }\r\n }\r\n this.popState('in-for-loop-init');\r\n } else {\r\n isSafe = false;\r\n }\r\n\r\n if (forNode.test) {\r\n this.astGeneric(forNode.test, testArr);\r\n } else {\r\n isSafe = false;\r\n }\r\n\r\n if (forNode.update) {\r\n this.astGeneric(forNode.update, updateArr);\r\n } else {\r\n isSafe = false;\r\n }\r\n\r\n if (forNode.body) {\r\n this.pushState('loop-body');\r\n this.astGeneric(forNode.body, bodyArr);\r\n this.popState('loop-body');\r\n }\r\n\r\n // have all parts, now make them safe\r\n if (isSafe === null) {\r\n isSafe = this.isSafe(forNode.init) && this.isSafe(forNode.test);\r\n }\r\n\r\n if (isSafe) {\r\n retArr.push(`for (${initArr.join('')};${testArr.join('')};${updateArr.join('')}){\\n`);\r\n retArr.push(bodyArr.join(''));\r\n retArr.push('}\\n');\r\n } else {\r\n const iVariableName = this.getInternalVariableName('safeI');\r\n if (initArr.length > 0) {\r\n retArr.push(initArr.join(''), ';\\n');\r\n }\r\n retArr.push(`for (let ${iVariableName}=0;${iVariableName}Instantiates properties to the CPU Kernel.
\r\n */\r\nexport class CPUKernel extends Kernel {\r\n static getFeatures() {\r\n return this.features;\r\n }\r\n static get features() {\r\n return Object.freeze({\r\n kernelMap: true,\r\n isIntegerDivisionAccurate: true\r\n });\r\n }\r\n static get isSupported() {\r\n return true;\r\n }\r\n static isContextMatch(context) {\r\n return false;\r\n }\r\n /**\r\n * @desc The current mode in which gpu.js is executing.\r\n */\r\n static get mode() {\r\n return 'cpu';\r\n }\r\n\r\n static nativeFunctionArguments() {\r\n return null;\r\n }\r\n\r\n static nativeFunctionReturnType() {\r\n return null;\r\n }\r\n\r\n static combineKernels(combinedKernel) {\r\n return combinedKernel;\r\n }\r\n\r\n constructor(source, settings) {\r\n super(source, settings);\r\n this.mergeSettings(source.settings || settings);\r\n\r\n this._imageData = null;\r\n this._colorData = null;\r\n this._kernelString = null;\r\n this.thread = {\r\n x: 0,\r\n y: 0,\r\n z: 0\r\n };\r\n this.translatedSources = null;\r\n }\r\n\r\n initCanvas() {\r\n if (typeof document !== 'undefined') {\r\n return document.createElement('canvas');\r\n } else if (typeof OffscreenCanvas !== 'undefined') {\r\n return new OffscreenCanvas(0, 0);\r\n }\r\n }\r\n\r\n initContext() {\r\n if (!this.canvas) return null;\r\n return this.canvas.getContext('2d');\r\n }\r\n\r\n initPlugins(settings) {\r\n return [];\r\n }\r\n\r\n /**\r\n * @desc Validate settings related to Kernel, such as dimensions size, and auto output support.\r\n * @param {IArguments} args\r\n */\r\n validateSettings(args) {\r\n if (!this.output || this.output.length === 0) {\r\n if (args.length !== 1) {\r\n throw new Error('Auto output only supported for kernels with only one input');\r\n }\r\n\r\n const argType = utils.getVariableType(args[0], this.strictIntegers);\r\n if (argType === 'Array') {\r\n this.output = utils.getDimensions(argType);\r\n } else if (argType === 'NumberTexture' || argType === 'ArrayTexture(4)') {\r\n this.output = args[0].output;\r\n } else {\r\n throw new Error('Auto output not supported for input type: ' + argType);\r\n }\r\n }\r\n\r\n if (this.graphical) {\r\n if (this.output.length !== 2) {\r\n throw new Error('Output must have 2 dimensions on graphical mode');\r\n }\r\n }\r\n\r\n this.checkOutput();\r\n }\r\n\r\n translateSource() {\r\n this.leadingReturnStatement = this.output.length > 1 ? 'resultX[x] = ' : 'result[x] = ';\r\n if (this.subKernels) {\r\n const followingReturnStatement = []\r\n for (let i = 0; i < this.subKernels.length; i++) {\r\n const {\r\n name\r\n } = this.subKernels[i];\r\n followingReturnStatement.push(this.output.length > 1 ? `resultX_${ name }[x] = subKernelResult_${ name };\\n` : `result_${ name }[x] = subKernelResult_${ name };\\n`);\r\n }\r\n this.followingReturnStatement = followingReturnStatement.join('');\r\n }\r\n const functionBuilder = FunctionBuilder.fromKernel(this, CPUFunctionNode);\r\n this.translatedSources = functionBuilder.getPrototypes('kernel');\r\n if (!this.graphical && !this.returnType) {\r\n this.returnType = functionBuilder.getKernelResultType();\r\n }\r\n }\r\n\r\n /**\r\n * @desc Builds the Kernel, by generating the kernel\r\n * string using thread dimensions, and arguments\r\n * supplied to the kernel.\r\n *\r\n *If the graphical flag is enabled, canvas is used.
\r\n */\r\n build() {\r\n this.setupConstants();\r\n this.setupArguments(arguments);\r\n this.validateSettings(arguments);\r\n this.translateSource();\r\n\r\n if (this.graphical) {\r\n const {\r\n canvas,\r\n output\r\n } = this;\r\n if (!canvas) {\r\n throw new Error('no canvas available for using graphical output');\r\n }\r\n const width = output[0];\r\n const height = output[1] || 1;\r\n canvas.width = width;\r\n canvas.height = height;\r\n this._imageData = this.context.createImageData(width, height);\r\n this._colorData = new Uint8ClampedArray(width * height * 4);\r\n }\r\n\r\n const kernelString = this.getKernelString();\r\n this.kernelString = kernelString;\r\n\r\n if (this.debug) {\r\n console.log('Function output:');\r\n console.log(kernelString);\r\n }\r\n\r\n try {\r\n this.run = new Function([], kernelString).bind(this)();\r\n } catch (e) {\r\n console.error('An error occurred compiling the javascript: ', e);\r\n }\r\n }\r\n\r\n color(r, g, b, a) {\r\n if (typeof a === 'undefined') {\r\n a = 1;\r\n }\r\n\r\n r = Math.floor(r * 255);\r\n g = Math.floor(g * 255);\r\n b = Math.floor(b * 255);\r\n a = Math.floor(a * 255);\r\n\r\n const width = this.output[0];\r\n const height = this.output[1];\r\n\r\n const x = this.thread.x;\r\n const y = height - this.thread.y - 1;\r\n\r\n const index = x + y * width;\r\n\r\n this._colorData[index * 4 + 0] = r;\r\n this._colorData[index * 4 + 1] = g;\r\n this._colorData[index * 4 + 2] = b;\r\n this._colorData[index * 4 + 3] = a;\r\n }\r\n\r\n /**\r\n * @desc Generates kernel string for this kernel program.\r\n *\r\n *If sub-kernels are supplied, they are also factored in.\r\n * This string can be saved by calling the `toString` method\r\n * and then can be reused later.
\r\n *\r\n * @returns {String} result\r\n *\r\n */\r\n getKernelString() {\r\n if (this._kernelString !== null) return this._kernelString;\r\n\r\n let kernelThreadString = null;\r\n let {\r\n translatedSources\r\n } = this;\r\n if (translatedSources.length > 1) {\r\n translatedSources = translatedSources.filter(fn => {\r\n if (/^function/.test(fn)) return fn;\r\n kernelThreadString = fn;\r\n return false;\r\n })\r\n } else {\r\n kernelThreadString = translatedSources.shift();\r\n }\r\n return this._kernelString = ` const LOOP_MAX = ${ this._getLoopMaxString() };\r\n ${ this.injectedNative || '' }\r\n const _this = this;\r\n ${ this._processConstants() }\r\n return (${ this.argumentNames.map(argumentName => 'user_' + argumentName).join(', ') }) => {\r\n ${ this._processArguments() }\r\n ${ this.graphical ? this._graphicalKernelBody(kernelThreadString) : this._resultKernelBody(kernelThreadString) }\r\n ${ translatedSources.length > 0 ? translatedSources.join('\\n') : '' }\r\n };`;\r\n }\r\n\r\n /**\r\n * @desc Returns the *pre-compiled* Kernel as a JS Object String, that can be reused.\r\n */\r\n toString() {\r\n return cpuKernelString(this);\r\n }\r\n\r\n /**\r\n * @desc Get the maximum loop size String.\r\n * @returns {String} result\r\n */\r\n _getLoopMaxString() {\r\n return (\r\n this.loopMaxIterations ?\r\n ` ${ parseInt(this.loopMaxIterations) };` :\r\n ' 1000;'\r\n );\r\n }\r\n\r\n _processConstants() {\r\n if (!this.constants) return '';\r\n\r\n const result = [];\r\n for (let p in this.constants) {\r\n const type = this.constantTypes[p];\r\n switch (type) {\r\n case 'HTMLImage':\r\n case 'HTMLVideo':\r\n result.push(` const constants_${p} = this._mediaTo2DArray(this.constants.${p});\\n`);\r\n break;\r\n case 'HTMLImageArray':\r\n result.push(` const constants_${p} = this._imageTo3DArray(this.constants.${p});\\n`);\r\n break;\r\n case 'Input':\r\n result.push(` const constants_${p} = this.constants.${p}.value;\\n`);\r\n break;\r\n default:\r\n result.push(` const constants_${p} = this.constants.${p};\\n`);\r\n }\r\n }\r\n return result.join('');\r\n }\r\n\r\n _processArguments() {\r\n const result = [];\r\n for (let i = 0; i < this.argumentTypes.length; i++) {\r\n const variableName = `user_${this.argumentNames[i]}`;\r\n switch (this.argumentTypes[i]) {\r\n case 'HTMLImage':\r\n case 'HTMLVideo':\r\n result.push(` ${variableName} = this._mediaTo2DArray(${variableName});\\n`);\r\n break;\r\n case 'HTMLImageArray':\r\n result.push(` ${variableName} = this._imageTo3DArray(${variableName});\\n`);\r\n break;\r\n case 'Input':\r\n result.push(` ${variableName} = ${variableName}.value;\\n`);\r\n break;\r\n case 'ArrayTexture(1)':\r\n case 'ArrayTexture(2)':\r\n case 'ArrayTexture(3)':\r\n case 'ArrayTexture(4)':\r\n case 'NumberTexture':\r\n case 'MemoryOptimizedNumberTexture':\r\n result.push(`\r\n if (${variableName}.toArray) {\r\n if (!_this.textureCache) {\r\n _this.textureCache = [];\r\n _this.arrayCache = [];\r\n }\r\n const textureIndex = _this.textureCache.indexOf(${variableName});\r\n if (textureIndex !== -1) {\r\n ${variableName} = _this.arrayCache[textureIndex];\r\n } else {\r\n _this.textureCache.push(${variableName});\r\n ${variableName} = ${variableName}.toArray();\r\n _this.arrayCache.push(${variableName});\r\n }\r\n }`);\r\n break;\r\n }\r\n }\r\n return result.join('');\r\n }\r\n\r\n _mediaTo2DArray(media) {\r\n const canvas = this.canvas;\r\n const width = media.width > 0 ? media.width : media.videoWidth;\r\n const height = media.height > 0 ? media.height : media.videoHeight;\r\n if (canvas.width < width) {\r\n canvas.width = width;\r\n }\r\n if (canvas.height < height) {\r\n canvas.height = height;\r\n }\r\n const ctx = this.context;\r\n ctx.drawImage(media, 0, 0, width, height);\r\n const pixelsData = ctx.getImageData(0, 0, width, height).data;\r\n const imageArray = new Array(height);\r\n let index = 0;\r\n for (let y = height - 1; y >= 0; y--) {\r\n const row = imageArray[y] = new Array(width);\r\n for (let x = 0; x < width; x++) {\r\n const pixel = new Float32Array(4);\r\n pixel[0] = pixelsData[index++] / 255; // r\r\n pixel[1] = pixelsData[index++] / 255; // g\r\n pixel[2] = pixelsData[index++] / 255; // b\r\n pixel[3] = pixelsData[index++] / 255; // a\r\n row[x] = pixel;\r\n }\r\n }\r\n return imageArray;\r\n }\r\n\r\n getPixels(flip) {\r\n const [width, height] = this.output;\r\n // cpu is not flipped by default\r\n return flip ? utils.flipPixels(this._imageData.data, width, height) : this._imageData.data.slice(0);\r\n }\r\n\r\n _imageTo3DArray(images) {\r\n const imagesArray = new Array(images.length);\r\n for (let i = 0; i < images.length; i++) {\r\n imagesArray[i] = this._mediaTo2DArray(images[i]);\r\n }\r\n return imagesArray;\r\n }\r\n\r\n _resultKernelBody(kernelString) {\r\n switch (this.output.length) {\r\n case 1:\r\n return this._resultKernel1DLoop(kernelString) + this._kernelOutput();\r\n case 2:\r\n return this._resultKernel2DLoop(kernelString) + this._kernelOutput();\r\n case 3:\r\n return this._resultKernel3DLoop(kernelString) + this._kernelOutput();\r\n default:\r\n throw new Error('unsupported size kernel');\r\n }\r\n }\r\n\r\n _graphicalKernelBody(kernelThreadString) {\r\n switch (this.output.length) {\r\n case 2:\r\n return this._graphicalKernel2DLoop(kernelThreadString) + this._graphicalOutput();\r\n default:\r\n throw new Error('unsupported size kernel');\r\n }\r\n }\r\n\r\n _graphicalOutput() {\r\n return `\r\n this._imageData.data.set(this._colorData);\r\n this.context.putImageData(this._imageData, 0, 0);\r\n return;`\r\n }\r\n\r\n _getKernelResultTypeConstructorString() {\r\n switch (this.returnType) {\r\n case 'LiteralInteger':\r\n case 'Number':\r\n case 'Integer':\r\n case 'Float':\r\n return 'Float32Array';\r\n case 'Array(2)':\r\n case 'Array(3)':\r\n case 'Array(4)':\r\n return 'Array';\r\n default:\r\n if (this.graphical) {\r\n return 'Float32Array';\r\n }\r\n throw new Error(`unhandled returnType ${ this.returnType }`);\r\n }\r\n }\r\n\r\n _resultKernel1DLoop(kernelString) {\r\n const {\r\n output\r\n } = this;\r\n const constructorString = this._getKernelResultTypeConstructorString();\r\n return ` const outputX = _this.output[0];\r\n const result = new ${constructorString}(outputX);\r\n ${ this._mapSubKernels(subKernel => `const result_${ subKernel.name } = new ${constructorString}(outputX);\\n`).join(' ') }\r\n ${ this._mapSubKernels(subKernel => `let subKernelResult_${ subKernel.name };\\n`).join(' ') }\r\n for (let x = 0; x < outputX; x++) {\r\n this.thread.x = x;\r\n this.thread.y = 0;\r\n this.thread.z = 0;\r\n ${ kernelString }\r\n }`;\r\n }\r\n\r\n _resultKernel2DLoop(kernelString) {\r\n const {\r\n output\r\n } = this;\r\n const constructorString = this._getKernelResultTypeConstructorString();\r\n return ` const outputX = _this.output[0];\r\n const outputY = _this.output[1];\r\n const result = new Array(outputY);\r\n ${ this._mapSubKernels(subKernel => `const result_${ subKernel.name } = new Array(outputY);\\n`).join(' ') }\r\n ${ this._mapSubKernels(subKernel => `let subKernelResult_${ subKernel.name };\\n`).join(' ') }\r\n for (let y = 0; y < outputY; y++) {\r\n this.thread.z = 0;\r\n this.thread.y = y;\r\n const resultX = result[y] = new ${constructorString}(outputX);\r\n ${ this._mapSubKernels(subKernel => `const resultX_${ subKernel.name } = result_${subKernel.name}[y] = new ${constructorString}(outputX);\\n`).join('') }\r\n for (let x = 0; x < outputX; x++) {\r\n this.thread.x = x;\r\n ${ kernelString }\r\n }\r\n }`;\r\n }\r\n\r\n _graphicalKernel2DLoop(kernelString) {\r\n const {\r\n output\r\n } = this;\r\n const constructorString = this._getKernelResultTypeConstructorString();\r\n return ` const outputX = _this.output[0];\r\n const outputY = _this.output[1];\r\n ${ this._mapSubKernels(subKernel => `const result_${ subKernel.name } = new Array(outputY);\\n`).join(' ') }\r\n ${ this._mapSubKernels(subKernel => `let subKernelResult_${ subKernel.name };\\n`).join(' ') }\r\n for (let y = 0; y < outputY; y++) {\r\n this.thread.z = 0;\r\n this.thread.y = y;\r\n ${ this._mapSubKernels(subKernel => `const resultX_${ subKernel.name } = result_${subKernel.name}[y] = new ${constructorString}(outputX);\\n`).join('') }\r\n for (let x = 0; x < outputX; x++) {\r\n this.thread.x = x;\r\n ${ kernelString }\r\n }\r\n }`;\r\n }\r\n\r\n _resultKernel3DLoop(kernelString) {\r\n const {\r\n output\r\n } = this;\r\n const constructorString = this._getKernelResultTypeConstructorString();\r\n return ` const outputX = _this.output[0];\r\n const outputY = _this.output[1];\r\n const outputZ = _this.output[2];\r\n const result = new Array(outputZ);\r\n ${ this._mapSubKernels(subKernel => `const result_${ subKernel.name } = new Array(outputZ);\\n`).join(' ') }\r\n ${ this._mapSubKernels(subKernel => `let subKernelResult_${ subKernel.name };\\n`).join(' ') }\r\n for (let z = 0; z < outputZ; z++) {\r\n this.thread.z = z;\r\n const resultY = result[z] = new Array(outputY);\r\n ${ this._mapSubKernels(subKernel => `const resultY_${ subKernel.name } = result_${subKernel.name}[z] = new Array(outputY);\\n`).join(' ') }\r\n for (let y = 0; y < outputY; y++) {\r\n this.thread.y = y;\r\n const resultX = resultY[y] = new ${constructorString}(outputX);\r\n ${ this._mapSubKernels(subKernel => `const resultX_${ subKernel.name } = resultY_${subKernel.name}[y] = new ${constructorString}(outputX);\\n`).join(' ') }\r\n for (let x = 0; x < outputX; x++) {\r\n this.thread.x = x;\r\n ${ kernelString }\r\n }\r\n }\r\n }`;\r\n }\r\n\r\n _kernelOutput() {\r\n if (!this.subKernels) {\r\n return '\\n return result;';\r\n }\r\n return `\\n return {\r\n result: result,\r\n ${ this.subKernels.map(subKernel => `${ subKernel.property }: result_${ subKernel.name }`).join(',\\n ') }\r\n };`;\r\n }\r\n\r\n _mapSubKernels(fn) {\r\n return this.subKernels === null ? [''] :\r\n this.subKernels.map(fn);\r\n }\r\n\r\n\r\n\r\n destroy(removeCanvasReference) {\r\n if (removeCanvasReference) {\r\n delete this.canvas;\r\n }\r\n }\r\n\r\n static destroyContext(context) {}\r\n\r\n toJSON() {\r\n const json = super.toJSON();\r\n json.functionNodes = FunctionBuilder.fromKernel(this, CPUFunctionNode).toJSON();\r\n return json;\r\n }\r\n\r\n setOutput(output) {\r\n super.setOutput(output);\r\n const [width, height] = this.output;\r\n if (this.graphical) {\r\n this._imageData = this.context.createImageData(width, height);\r\n this._colorData = new Uint8ClampedArray(width * height * 4);\r\n }\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { Texture } from '../../../texture';\r\n\r\nexport class GLTextureFloat extends Texture {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'ArrayTexture(1)';\r\n }\r\n renderRawOutput() {\r\n const { context: gl } = this;\r\n const framebuffer = gl.createFramebuffer();\r\n gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);\r\n gl.framebufferTexture2D(\r\n gl.FRAMEBUFFER,\r\n gl.COLOR_ATTACHMENT0,\r\n gl.TEXTURE_2D,\r\n this.texture,\r\n 0\r\n );\r\n const result = new Float32Array(this.size[0] * this.size[1] * 4);\r\n gl.readPixels(0, 0, this.size[0], this.size[1], gl.RGBA, gl.FLOAT, result);\r\n return result;\r\n }\r\n renderValues() {\r\n return this.renderRawOutput();\r\n }\r\n toArray() {\r\n return utils.erectFloat(this.renderValues(), this.output[0]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureFloat } from './float';\r\n\r\nexport class GLTextureArray2Float extends GLTextureFloat {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'ArrayTexture(2)';\r\n }\r\n toArray() {\r\n return utils.erectArray2(this.renderValues(), this.output[0], this.output[1]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureFloat } from './float';\r\n\r\nexport class GLTextureArray2Float2D extends GLTextureFloat {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'ArrayTexture(2)';\r\n }\r\n toArray() {\r\n return utils.erect2DArray2(this.renderValues(), this.output[0], this.output[1]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureFloat } from './float';\r\n\r\nexport class GLTextureArray2Float3D extends GLTextureFloat {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'ArrayTexture(2)';\r\n }\r\n toArray() {\r\n return utils.erect3DArray2(this.renderValues(), this.output[0], this.output[1], this.output[2]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureFloat } from './float';\r\n\r\nexport class GLTextureArray3Float extends GLTextureFloat {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'ArrayTexture(3)';\r\n }\r\n toArray() {\r\n return utils.erectArray3(this.renderValues(), this.output[0]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureFloat } from './float';\r\n\r\nexport class GLTextureArray3Float2D extends GLTextureFloat {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'ArrayTexture(3)';\r\n }\r\n toArray() {\r\n return utils.erect2DArray3(this.renderValues(), this.output[0], this.output[1]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureFloat } from './float';\r\n\r\nexport class GLTextureArray3Float3D extends GLTextureFloat {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'ArrayTexture(3)';\r\n }\r\n toArray() {\r\n return utils.erect3DArray3(this.renderValues(), this.output[0], this.output[1], this.output[2]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureFloat } from './float';\r\n\r\nexport class GLTextureArray4Float extends GLTextureFloat {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'ArrayTexture(4)';\r\n }\r\n toArray() {\r\n return utils.erectArray4(this.renderValues(), this.output[0]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureFloat } from './float';\r\n\r\nexport class GLTextureArray4Float2D extends GLTextureFloat {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'ArrayTexture(4)';\r\n }\r\n toArray() {\r\n return utils.erect2DArray4(this.renderValues(), this.output[0], this.output[1]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureFloat } from './float';\r\n\r\nexport class GLTextureArray4Float3D extends GLTextureFloat {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'ArrayTexture(4)';\r\n }\r\n toArray() {\r\n return utils.erect3DArray4(this.renderValues(), this.output[0], this.output[1], this.output[2]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureFloat } from './float';\r\n\r\nexport class GLTextureFloat2D extends GLTextureFloat {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'ArrayTexture(1)';\r\n }\r\n toArray() {\r\n return utils.erect2DFloat(this.renderValues(), this.output[0], this.output[1]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureFloat } from './float';\r\n\r\nexport class GLTextureFloat3D extends GLTextureFloat {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'ArrayTexture(1)';\r\n }\r\n toArray() {\r\n return utils.erect3DFloat(this.renderValues(), this.output[0], this.output[1], this.output[2]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureFloat } from './float';\r\n\r\nexport class GLTextureMemoryOptimized extends GLTextureFloat {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'MemoryOptimizedNumberTexture';\r\n }\r\n toArray() {\r\n return utils.erectMemoryOptimizedFloat(this.renderValues(), this.output[0]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureFloat } from './float';\r\n\r\nexport class GLTextureMemoryOptimized2D extends GLTextureFloat {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'MemoryOptimizedNumberTexture';\r\n }\r\n toArray() {\r\n return utils.erectMemoryOptimized2DFloat(this.renderValues(), this.output[0], this.output[1]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureFloat } from './float';\r\n\r\nexport class GLTextureMemoryOptimized3D extends GLTextureFloat {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'MemoryOptimizedNumberTexture';\r\n }\r\n toArray() {\r\n return utils.erectMemoryOptimized3DFloat(this.renderValues(), this.output[0], this.output[1], this.output[2]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { Texture } from '../../../texture';\r\n\r\nexport class GLTextureUnsigned extends Texture {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'NumberTexture';\r\n }\r\n renderRawOutput() {\r\n const { context: gl } = this;\r\n const framebuffer = gl.createFramebuffer();\r\n gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);\r\n gl.framebufferTexture2D(\r\n gl.FRAMEBUFFER,\r\n gl.COLOR_ATTACHMENT0,\r\n gl.TEXTURE_2D,\r\n this.texture,\r\n 0\r\n );\r\n const result = new Uint8Array(this.size[0] * this.size[1] * 4);\r\n gl.readPixels(0, 0, this.size[0], this.size[1], gl.RGBA, gl.UNSIGNED_BYTE, result);\r\n return result;\r\n }\r\n renderValues() {\r\n return new Float32Array(this.renderRawOutput().buffer);\r\n }\r\n toArray() {\r\n return utils.erectPackedFloat(this.renderValues(), this.output[0]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureUnsigned } from './unsigned';\r\n\r\nexport class GLTextureUnsigned2D extends GLTextureUnsigned {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'NumberTexture';\r\n }\r\n toArray() {\r\n return utils.erect2DPackedFloat(this.renderValues(), this.output[0], this.output[1]);\r\n }\r\n}\r\n","import { utils } from '../../../utils';\r\nimport { GLTextureUnsigned } from './unsigned';\r\n\r\nexport class GLTextureUnsigned3D extends GLTextureUnsigned {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'NumberTexture';\r\n }\r\n toArray() {\r\n return utils.erect3DPackedFloat(this.renderValues(), this.output[0], this.output[1], this.output[2]);\r\n }\r\n}\r\n","import { GLTextureUnsigned } from './unsigned';\r\n\r\nexport class GLTextureGraphical extends GLTextureUnsigned {\r\n constructor(settings) {\r\n super(settings);\r\n this.type = 'ArrayTexture(4)';\r\n }\r\n toArray() {\r\n return this.renderValues();\r\n }\r\n}\r\n","import { Kernel } from '../kernel';\r\nimport { utils } from '../../utils';\r\nimport { GLTextureArray2Float } from './texture/array-2-float';\r\nimport { GLTextureArray2Float2D } from './texture/array-2-float-2d';\r\nimport { GLTextureArray2Float3D } from './texture/array-2-float-3d';\r\nimport { GLTextureArray3Float } from './texture/array-3-float';\r\nimport { GLTextureArray3Float2D } from './texture/array-3-float-2d';\r\nimport { GLTextureArray3Float3D } from './texture/array-3-float-3d';\r\nimport { GLTextureArray4Float } from './texture/array-4-float';\r\nimport { GLTextureArray4Float2D } from './texture/array-4-float-2d';\r\nimport { GLTextureArray4Float3D } from './texture/array-4-float-3d';\r\nimport { GLTextureFloat } from './texture/float';\r\nimport { GLTextureFloat2D } from './texture/float-2d';\r\nimport { GLTextureFloat3D } from './texture/float-3d';\r\nimport { GLTextureMemoryOptimized } from './texture/memory-optimized';\r\nimport { GLTextureMemoryOptimized2D } from './texture/memory-optimized-2d';\r\nimport { GLTextureMemoryOptimized3D } from './texture/memory-optimized-3d';\r\nimport { GLTextureUnsigned } from './texture/unsigned';\r\nimport { GLTextureUnsigned2D } from './texture/unsigned-2d';\r\nimport { GLTextureUnsigned3D } from './texture/unsigned-3d';\r\nimport { GLTextureGraphical } from './texture/graphical';\r\n\r\n/**\r\n * @abstract\r\n * @extends Kernel\r\n */\r\nexport class GLKernel extends Kernel {\r\n static get mode() {\r\n return 'gpu';\r\n }\r\n\r\n static getIsFloatRead() {\r\n const kernelString = `function kernelFunction() {\r\n return 1;\r\n }`;\r\n const kernel = new this(kernelString, {\r\n context: this.testContext,\r\n canvas: this.testCanvas,\r\n validate: false,\r\n output: [1],\r\n precision: 'single',\r\n returnType: 'Number',\r\n tactic: 'speed',\r\n });\r\n kernel.build();\r\n kernel.run();\r\n const result = kernel.renderOutput();\r\n kernel.destroy(true);\r\n return result[0] === 1;\r\n }\r\n\r\n static getIsIntegerDivisionAccurate() {\r\n function kernelFunction(v1, v2) {\r\n return v1[this.thread.x] / v2[this.thread.x];\r\n }\r\n const kernel = new this(kernelFunction.toString(), {\r\n context: this.testContext,\r\n canvas: this.testCanvas,\r\n validate: false,\r\n output: [2],\r\n returnType: 'Number',\r\n precision: 'unsigned',\r\n tactic: 'speed',\r\n });\r\n const args = [\r\n [6, 6030401],\r\n [3, 3991]\r\n ];\r\n kernel.build.apply(kernel, args);\r\n kernel.run.apply(kernel, args);\r\n const result = kernel.renderOutput();\r\n kernel.destroy(true);\r\n // have we not got whole numbers for 6/3 or 6030401/3991\r\n // add more here if others see this problem\r\n return result[0] === 2 && result[1] === 1511;\r\n }\r\n\r\n /**\r\n * @abstract\r\n */\r\n static get testCanvas() {\r\n throw new Error(`\"testCanvas\" not defined on ${ this.name }`);\r\n }\r\n\r\n /**\r\n * @abstract\r\n */\r\n static get testContext() {\r\n throw new Error(`\"testContext\" not defined on ${ this.name }`);\r\n }\r\n\r\n /**\r\n * @type {IKernelFeatures}\r\n */\r\n static get features() {\r\n throw new Error(`\"features\" not defined on ${ this.name }`);\r\n }\r\n\r\n /**\r\n * @abstract\r\n */\r\n static setupFeatureChecks() {\r\n throw new Error(`\"setupFeatureChecks\" not defined on ${ this.name }`);\r\n }\r\n\r\n /**\r\n * @desc Fix division by factor of 3 FP accuracy bug\r\n * @param {Boolean} fix - should fix\r\n */\r\n setFixIntegerDivisionAccuracy(fix) {\r\n this.fixIntegerDivisionAccuracy = fix;\r\n return this;\r\n }\r\n\r\n /**\r\n * @desc Toggle output mode\r\n * @param {String} flag - 'single' or 'unsigned'\r\n */\r\n setPrecision(flag) {\r\n this.precision = flag;\r\n return this;\r\n }\r\n\r\n /**\r\n * @desc Toggle texture output mode\r\n * @param {Boolean} flag - true to enable floatTextures\r\n * @deprecated\r\n */\r\n setFloatTextures(flag) {\r\n utils.warnDeprecated('method', 'setFloatTextures', 'setOptimizeFloatMemory');\r\n this.floatTextures = flag;\r\n return this;\r\n }\r\n\r\n /**\r\n * A highly readable very forgiving micro-parser for a glsl function that gets argument types\r\n * @param {String} source\r\n * @returns {{argumentTypes: String[], argumentNames: String[]}}\r\n */\r\n static nativeFunctionArguments(source) {\r\n const argumentTypes = [];\r\n const argumentNames = [];\r\n const states = [];\r\n const isStartingVariableName = /^[a-zA-Z_]/;\r\n const isVariableChar = /[a-zA-Z_0-9]/;\r\n let i = 0;\r\n let argumentName = null;\r\n let argumentType = null;\r\n while (i < source.length) {\r\n const char = source[i];\r\n const nextChar = source[i + 1];\r\n const state = states.length > 0 ? states[states.length - 1] : null;\r\n\r\n // begin MULTI_LINE_COMMENT handling\r\n if (state === 'FUNCTION_ARGUMENTS' && char === '/' && nextChar === '*') {\r\n states.push('MULTI_LINE_COMMENT');\r\n i += 2;\r\n continue;\r\n } else if (state === 'MULTI_LINE_COMMENT' && char === '*' && nextChar === '/') {\r\n states.pop();\r\n i += 2;\r\n continue;\r\n }\r\n // end MULTI_LINE_COMMENT handling\r\n\r\n // begin COMMENT handling\r\n else if (state === 'FUNCTION_ARGUMENTS' && char === '/' && nextChar === '/') {\r\n states.push('COMMENT');\r\n i += 2;\r\n continue;\r\n } else if (state === 'COMMENT' && char === '\\n') {\r\n states.pop();\r\n i++;\r\n continue;\r\n }\r\n // end COMMENT handling\r\n\r\n // being FUNCTION_ARGUMENTS handling\r\n else if (state === null && char === '(') {\r\n states.push('FUNCTION_ARGUMENTS');\r\n i++;\r\n continue;\r\n } else if (state === 'FUNCTION_ARGUMENTS') {\r\n if (char === ')') {\r\n states.pop();\r\n break;\r\n }\r\n if (char === 'f' && nextChar === 'l' && source[i + 2] === 'o' && source[i + 3] === 'a' && source[i + 4] === 't' && source[i + 5] === ' ') {\r\n states.push('DECLARE_VARIABLE');\r\n argumentType = 'float';\r\n argumentName = '';\r\n i += 6;\r\n continue;\r\n } else if (char === 'i' && nextChar === 'n' && source[i + 2] === 't' && source[i + 3] === ' ') {\r\n states.push('DECLARE_VARIABLE');\r\n argumentType = 'int';\r\n argumentName = '';\r\n i += 4;\r\n continue;\r\n } else if (char === 'v' && nextChar === 'e' && source[i + 2] === 'c' && source[i + 3] === '2' && source[i + 4] === ' ') {\r\n states.push('DECLARE_VARIABLE');\r\n argumentType = 'vec2';\r\n argumentName = '';\r\n i += 5;\r\n continue;\r\n } else if (char === 'v' && nextChar === 'e' && source[i + 2] === 'c' && source[i + 3] === '3' && source[i + 4] === ' ') {\r\n states.push('DECLARE_VARIABLE');\r\n argumentType = 'vec3';\r\n argumentName = '';\r\n i += 5;\r\n continue;\r\n } else if (char === 'v' && nextChar === 'e' && source[i + 2] === 'c' && source[i + 3] === '4' && source[i + 4] === ' ') {\r\n states.push('DECLARE_VARIABLE');\r\n argumentType = 'vec4';\r\n argumentName = '';\r\n i += 5;\r\n continue;\r\n }\r\n }\r\n // end FUNCTION_ARGUMENTS handling\r\n\r\n // begin DECLARE_VARIABLE handling\r\n else if (state === 'DECLARE_VARIABLE') {\r\n if (argumentName === '') {\r\n if (char === ' ') {\r\n i++;\r\n continue;\r\n }\r\n if (!isStartingVariableName.test(char)) {\r\n throw new Error('variable name is not expected string');\r\n }\r\n }\r\n argumentName += char;\r\n if (!isVariableChar.test(nextChar)) {\r\n states.pop();\r\n argumentNames.push(argumentName);\r\n argumentTypes.push(typeMap[argumentType]);\r\n }\r\n }\r\n // end DECLARE_VARIABLE handling\r\n\r\n // Progress to next character\r\n i++;\r\n }\r\n if (states.length > 0) {\r\n throw new Error('GLSL function was not parsable');\r\n }\r\n return {\r\n argumentNames,\r\n argumentTypes,\r\n };\r\n }\r\n\r\n static nativeFunctionReturnType(source) {\r\n return typeMap[source.match(/int|float|vec[2-4]/)[0]];\r\n }\r\n\r\n static combineKernels(combinedKernel, lastKernel) {\r\n combinedKernel.apply(null, arguments);\r\n const {\r\n texSize,\r\n context,\r\n threadDim\r\n } = lastKernel.texSize;\r\n let result;\r\n if (lastKernel.precision === 'single') {\r\n const w = texSize[0];\r\n const h = Math.ceil(texSize[1] / 4);\r\n result = new Float32Array(w * h * 4 * 4);\r\n context.readPixels(0, 0, w, h * 4, context.RGBA, context.FLOAT, result);\r\n } else {\r\n const bytes = new Uint8Array(texSize[0] * texSize[1] * 4);\r\n context.readPixels(0, 0, texSize[0], texSize[1], context.RGBA, context.UNSIGNED_BYTE, bytes);\r\n result = new Float32Array(bytes.buffer);\r\n }\r\n\r\n result = result.subarray(0, threadDim[0] * threadDim[1] * threadDim[2]);\r\n\r\n if (lastKernel.output.length === 1) {\r\n return result;\r\n } else if (lastKernel.output.length === 2) {\r\n return utils.splitArray(result, lastKernel.output[0]);\r\n } else if (lastKernel.output.length === 3) {\r\n const cube = utils.splitArray(result, lastKernel.output[0] * lastKernel.output[1]);\r\n return cube.map(function(x) {\r\n return utils.splitArray(x, lastKernel.output[0]);\r\n });\r\n }\r\n }\r\n\r\n constructor(source, settings) {\r\n super(source, settings);\r\n this.transferValues = null;\r\n this.formatValues = null;\r\n this.TextureConstructor = null;\r\n this.renderOutput = null;\r\n this.renderRawOutput = null;\r\n this.texSize = null;\r\n this.translatedSource = null;\r\n this.renderStrategy = null;\r\n this.compiledFragmentShader = null;\r\n this.compiledVertexShader = null;\r\n }\r\n\r\n checkTextureSize() {\r\n const { features } = this.constructor;\r\n if (this.texSize[0] > features.maxTextureSize || this.texSize[1] > features.maxTextureSize) {\r\n throw new Error(`Texture size [${this.texSize[0]},${this.texSize[1]}] generated by kernel is larger than supported size [${features.maxTextureSize},${features.maxTextureSize}]`);\r\n }\r\n }\r\n\r\n translateSource() {\r\n throw new Error(`\"translateSource\" not defined on ${this.constructor.name}`);\r\n }\r\n\r\n /**\r\n * Picks a render strategy for the now finally parsed kernel\r\n * @param args\r\n * @return {null|KernelOutput}\r\n */\r\n pickRenderStrategy(args) {\r\n if (this.graphical) {\r\n this.renderRawOutput = this.readPackedPixelsToUint8Array;\r\n this.transferValues = (pixels) => pixels;\r\n this.TextureConstructor = GLTextureGraphical;\r\n return null;\r\n }\r\n if (this.precision === 'unsigned') {\r\n this.renderRawOutput = this.readPackedPixelsToUint8Array;\r\n this.transferValues = this.readPackedPixelsToFloat32Array;\r\n if (this.pipeline) {\r\n this.renderOutput = this.renderTexture;\r\n if (this.subKernels !== null) {\r\n this.renderKernels = this.renderKernelsToTextures;\r\n }\r\n switch (this.returnType) {\r\n case 'LiteralInteger':\r\n case 'Float':\r\n case 'Number':\r\n case 'Integer':\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureUnsigned3D;\r\n this.renderStrategy = renderStrategy.PackedPixelTo3DFloat;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureUnsigned2D;\r\n this.renderStrategy = renderStrategy.PackedPixelTo2DFloat;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureUnsigned;\r\n this.renderStrategy = renderStrategy.PackedPixelToFloat;\r\n return null;\r\n }\r\n break;\r\n case 'Array(2)':\r\n case 'Array(3)':\r\n case 'Array(4)':\r\n return this.requestFallback(args);\r\n }\r\n } else {\r\n if (this.subKernels !== null) {\r\n this.renderKernels = this.renderKernelsToArrays;\r\n }\r\n switch (this.returnType) {\r\n case 'LiteralInteger':\r\n case 'Float':\r\n case 'Number':\r\n case 'Integer':\r\n this.renderOutput = this.renderValues;\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureUnsigned3D;\r\n this.renderStrategy = renderStrategy.PackedPixelTo3DFloat;\r\n this.formatValues = utils.erect3DPackedFloat;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureUnsigned2D;\r\n this.renderStrategy = renderStrategy.PackedPixelTo2DFloat;\r\n this.formatValues = utils.erect2DPackedFloat;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureUnsigned;\r\n this.renderStrategy = renderStrategy.PackedPixelToFloat;\r\n this.formatValues = utils.erectPackedFloat;\r\n return null;\r\n }\r\n\r\n break;\r\n case 'Array(2)':\r\n case 'Array(3)':\r\n case 'Array(4)':\r\n return this.requestFallback(args);\r\n }\r\n }\r\n } else if (this.precision === 'single') {\r\n this.renderRawOutput = this.readFloatPixelsToFloat32Array;\r\n this.transferValues = this.readFloatPixelsToFloat32Array;\r\n if (this.pipeline) {\r\n this.renderStrategy = renderStrategy.FloatTexture;\r\n this.renderOutput = this.renderTexture;\r\n if (this.subKernels !== null) {\r\n this.renderKernels = this.renderKernelsToTextures;\r\n }\r\n switch (this.returnType) {\r\n case 'LiteralInteger':\r\n case 'Float':\r\n case 'Number':\r\n case 'Integer':\r\n if (this.optimizeFloatMemory) {\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureMemoryOptimized3D;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureMemoryOptimized2D;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureMemoryOptimized;\r\n return null;\r\n }\r\n } else {\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureFloat3D;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureFloat2D;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureFloat;\r\n return null;\r\n }\r\n }\r\n break;\r\n case 'Array(2)':\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureArray2Float3D;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureArray2Float2D;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureArray2Float;\r\n return null;\r\n }\r\n break;\r\n case 'Array(3)':\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureArray3Float3D;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureArray3Float2D;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureArray3Float;\r\n return null;\r\n }\r\n break;\r\n case 'Array(4)':\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureArray4Float3D;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureArray4Float2D;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureArray4Float;\r\n return null;\r\n }\r\n }\r\n }\r\n this.renderOutput = this.renderValues;\r\n if (this.subKernels !== null) {\r\n this.renderKernels = this.renderKernelsToArrays;\r\n }\r\n if (this.optimizeFloatMemory) {\r\n switch (this.returnType) {\r\n case 'LiteralInteger':\r\n case 'Float':\r\n case 'Number':\r\n case 'Integer':\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureMemoryOptimized3D;\r\n this.renderStrategy = renderStrategy.MemoryOptimizedFloatPixelToMemoryOptimized3DFloat;\r\n this.formatValues = utils.erectMemoryOptimized3DFloat;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureMemoryOptimized2D;\r\n this.renderStrategy = renderStrategy.MemoryOptimizedFloatPixelToMemoryOptimized2DFloat;\r\n this.formatValues = utils.erectMemoryOptimized2DFloat;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureMemoryOptimized;\r\n this.renderStrategy = renderStrategy.MemoryOptimizedFloatPixelToMemoryOptimizedFloat;\r\n this.formatValues = utils.erectMemoryOptimizedFloat;\r\n return null;\r\n }\r\n break;\r\n case 'Array(2)':\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureArray2Float3D;\r\n this.renderStrategy = renderStrategy.FloatPixelTo3DArray2;\r\n this.formatValues = utils.erect3DArray2;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureArray2Float2D;\r\n this.renderStrategy = renderStrategy.FloatPixelTo2DArray2;\r\n this.formatValues = utils.erect2DArray2;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureArray2Float;\r\n this.renderStrategy = renderStrategy.FloatPixelToArray2;\r\n this.formatValues = utils.erectArray2;\r\n return null;\r\n }\r\n break;\r\n case 'Array(3)':\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureArray3Float3D;\r\n this.renderStrategy = renderStrategy.FloatPixelTo3DArray3;\r\n this.formatValues = utils.erect3DArray3;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureArray3Float2D;\r\n this.renderStrategy = renderStrategy.FloatPixelTo2DArray3;\r\n this.formatValues = utils.erect2DArray3;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureArray3Float;\r\n this.renderStrategy = renderStrategy.FloatPixelToArray3;\r\n this.formatValues = utils.erectArray3;\r\n return null;\r\n }\r\n break;\r\n case 'Array(4)':\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureArray4Float3D;\r\n this.renderStrategy = renderStrategy.FloatPixelTo3DArray4;\r\n this.formatValues = utils.erect3DArray4;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureArray4Float2D;\r\n this.renderStrategy = renderStrategy.FloatPixelTo2DArray4;\r\n this.formatValues = utils.erect2DArray4;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureArray4Float;\r\n this.renderStrategy = renderStrategy.FloatPixelToArray4;\r\n this.formatValues = utils.erectArray4;\r\n return null;\r\n }\r\n }\r\n } else {\r\n switch (this.returnType) {\r\n case 'LiteralInteger':\r\n case 'Float':\r\n case 'Number':\r\n case 'Integer':\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureFloat3D;\r\n this.renderStrategy = renderStrategy.FloatPixelTo3DFloat;\r\n this.formatValues = utils.erect3DFloat;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureFloat2D;\r\n this.renderStrategy = renderStrategy.FloatPixelTo2DFloat;\r\n this.formatValues = utils.erect2DFloat;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureFloat;\r\n this.renderStrategy = renderStrategy.FloatPixelToFloat;\r\n this.formatValues = utils.erectFloat;\r\n return null;\r\n }\r\n break;\r\n case 'Array(2)':\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureArray2Float3D;\r\n this.renderStrategy = renderStrategy.FloatPixelTo3DArray2;\r\n this.formatValues = utils.erect3DArray2;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureArray2Float2D;\r\n this.renderStrategy = renderStrategy.FloatPixelTo2DArray2;\r\n this.formatValues = utils.erect2DArray2;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureArray2Float;\r\n this.renderStrategy = renderStrategy.FloatPixelToArray2;\r\n this.formatValues = utils.erectArray2;\r\n return null;\r\n }\r\n break;\r\n case 'Array(3)':\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureArray3Float3D;\r\n this.renderStrategy = renderStrategy.FloatPixelTo3DArray3;\r\n this.formatValues = utils.erect3DArray3;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureArray3Float2D;\r\n this.renderStrategy = renderStrategy.FloatPixelTo2DArray3;\r\n this.formatValues = utils.erect2DArray3;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureArray3Float;\r\n this.renderStrategy = renderStrategy.FloatPixelToArray3;\r\n this.formatValues = utils.erectArray3;\r\n return null;\r\n }\r\n break;\r\n case 'Array(4)':\r\n if (this.output[2] > 0) {\r\n this.TextureConstructor = GLTextureArray4Float3D;\r\n this.renderStrategy = renderStrategy.FloatPixelTo3DArray4;\r\n this.formatValues = utils.erect3DArray4;\r\n return null;\r\n } else if (this.output[1] > 0) {\r\n this.TextureConstructor = GLTextureArray4Float2D;\r\n this.renderStrategy = renderStrategy.FloatPixelTo2DArray4;\r\n this.formatValues = utils.erect2DArray4;\r\n return null;\r\n } else {\r\n this.TextureConstructor = GLTextureArray4Float;\r\n this.renderStrategy = renderStrategy.FloatPixelToArray4;\r\n this.formatValues = utils.erectArray4;\r\n return null;\r\n }\r\n }\r\n }\r\n } else {\r\n throw new Error(`unhandled precision of \"${this.precision}\"`);\r\n }\r\n\r\n throw new Error(`unhandled return type \"${this.returnType}\"`);\r\n }\r\n\r\n /**\r\n * @abstract\r\n * @returns String\r\n */\r\n getKernelString() {\r\n throw new Error(`abstract method call`);\r\n }\r\n\r\n getMainResultTexture() {\r\n switch (this.returnType) {\r\n case 'LiteralInteger':\r\n case 'Float':\r\n case 'Integer':\r\n case 'Number':\r\n return this.getMainResultNumberTexture();\r\n case 'Array(2)':\r\n return this.getMainResultArray2Texture();\r\n case 'Array(3)':\r\n return this.getMainResultArray3Texture();\r\n case 'Array(4)':\r\n return this.getMainResultArray4Texture();\r\n default:\r\n throw new Error(`unhandled returnType type ${ this.returnType }`);\r\n }\r\n }\r\n\r\n /**\r\n * @abstract\r\n * @returns String[]\r\n */\r\n getMainResultKernelNumberTexture() {\r\n throw new Error(`abstract method call`);\r\n }\r\n /**\r\n * @abstract\r\n * @returns String[]\r\n */\r\n getMainResultSubKernelNumberTexture() {\r\n throw new Error(`abstract method call`);\r\n }\r\n /**\r\n * @abstract\r\n * @returns String[]\r\n */\r\n getMainResultKernelArray2Texture() {\r\n throw new Error(`abstract method call`);\r\n }\r\n /**\r\n * @abstract\r\n * @returns String[]\r\n */\r\n getMainResultSubKernelArray2Texture() {\r\n throw new Error(`abstract method call`);\r\n }\r\n /**\r\n * @abstract\r\n * @returns String[]\r\n */\r\n getMainResultKernelArray3Texture() {\r\n throw new Error(`abstract method call`);\r\n }\r\n /**\r\n * @abstract\r\n * @returns String[]\r\n */\r\n getMainResultSubKernelArray3Texture() {\r\n throw new Error(`abstract method call`);\r\n }\r\n /**\r\n * @abstract\r\n * @returns String[]\r\n */\r\n getMainResultKernelArray4Texture() {\r\n throw new Error(`abstract method call`);\r\n }\r\n /**\r\n * @abstract\r\n * @returns String[]\r\n */\r\n getMainResultSubKernelArray4Texture() {\r\n throw new Error(`abstract method call`);\r\n }\r\n /**\r\n * @abstract\r\n * @returns String[]\r\n */\r\n getMainResultGraphical() {\r\n throw new Error(`abstract method call`);\r\n }\r\n /**\r\n * @abstract\r\n * @returns String[]\r\n */\r\n getMainResultMemoryOptimizedFloats() {\r\n throw new Error(`abstract method call`);\r\n }\r\n /**\r\n * @abstract\r\n * @returns String[]\r\n */\r\n getMainResultPackedPixels() {\r\n throw new Error(`abstract method call`);\r\n }\r\n\r\n getMainResultString() {\r\n if (this.graphical) {\r\n return this.getMainResultGraphical();\r\n } else if (this.precision === 'single') {\r\n if (this.optimizeFloatMemory) {\r\n return this.getMainResultMemoryOptimizedFloats();\r\n }\r\n return this.getMainResultTexture();\r\n } else {\r\n return this.getMainResultPackedPixels();\r\n }\r\n }\r\n\r\n getMainResultNumberTexture() {\r\n return utils.linesToString(this.getMainResultKernelNumberTexture()) +\r\n utils.linesToString(this.getMainResultSubKernelNumberTexture());\r\n }\r\n\r\n getMainResultArray2Texture() {\r\n return utils.linesToString(this.getMainResultKernelArray2Texture()) +\r\n utils.linesToString(this.getMainResultSubKernelArray2Texture());\r\n }\r\n\r\n getMainResultArray3Texture() {\r\n return utils.linesToString(this.getMainResultKernelArray3Texture()) +\r\n utils.linesToString(this.getMainResultSubKernelArray3Texture());\r\n }\r\n\r\n getMainResultArray4Texture() {\r\n return utils.linesToString(this.getMainResultKernelArray4Texture()) +\r\n utils.linesToString(this.getMainResultSubKernelArray4Texture());\r\n }\r\n\r\n /**\r\n *\r\n * @return {string}\r\n */\r\n getFloatTacticDeclaration() {\r\n switch (this.tactic) {\r\n case 'speed':\r\n return 'precision lowp float;\\n';\r\n case 'performance':\r\n return 'precision highp float;\\n';\r\n case 'balanced':\r\n default:\r\n return 'precision mediump float;\\n';\r\n }\r\n }\r\n\r\n /**\r\n *\r\n * @return {string}\r\n */\r\n getIntTacticDeclaration() {\r\n switch (this.tactic) {\r\n case 'speed':\r\n return 'precision lowp int;\\n';\r\n case 'performance':\r\n return 'precision highp int;\\n';\r\n case 'balanced':\r\n default:\r\n return 'precision mediump int;\\n';\r\n }\r\n }\r\n\r\n /**\r\n *\r\n * @return {string}\r\n */\r\n getSampler2DTacticDeclaration() {\r\n switch (this.tactic) {\r\n case 'speed':\r\n return 'precision lowp sampler2D;\\n';\r\n case 'performance':\r\n return 'precision highp sampler2D;\\n';\r\n case 'balanced':\r\n default:\r\n return 'precision mediump sampler2D;\\n';\r\n }\r\n }\r\n\r\n getSampler2DArrayTacticDeclaration() {\r\n switch (this.tactic) {\r\n case 'speed':\r\n return 'precision lowp sampler2DArray;\\n';\r\n case 'performance':\r\n return 'precision highp sampler2DArray;\\n';\r\n case 'balanced':\r\n default:\r\n return 'precision mediump sampler2DArray;\\n';\r\n }\r\n }\r\n\r\n renderTexture() {\r\n return new this.TextureConstructor({\r\n texture: this.outputTexture,\r\n size: this.texSize,\r\n dimensions: this.threadDim,\r\n output: this.output,\r\n context: this.context,\r\n });\r\n }\r\n readPackedPixelsToUint8Array() {\r\n if (this.precision !== 'unsigned') throw new Error('Requires this.precision to be \"unsigned\"');\r\n const {\r\n texSize,\r\n context: gl\r\n } = this;\r\n const result = new Uint8Array(texSize[0] * texSize[1] * 4);\r\n gl.readPixels(0, 0, texSize[0], texSize[1], gl.RGBA, gl.UNSIGNED_BYTE, result);\r\n return result;\r\n }\r\n\r\n readPackedPixelsToFloat32Array() {\r\n return new Float32Array(this.readPackedPixelsToUint8Array().buffer);\r\n }\r\n\r\n readFloatPixelsToFloat32Array() {\r\n if (this.precision !== 'single') throw new Error('Requires this.precision to be \"single\"');\r\n const {\r\n texSize,\r\n context: gl\r\n } = this;\r\n const w = texSize[0];\r\n const h = texSize[1];\r\n const result = new Float32Array(w * h * 4);\r\n gl.readPixels(0, 0, w, h, gl.RGBA, gl.FLOAT, result);\r\n return result;\r\n }\r\n\r\n readMemoryOptimizedFloatPixelsToFloat32Array() {\r\n if (this.precision !== 'single') throw new Error('Requires this.precision to be \"single\"');\r\n const {\r\n texSize,\r\n context: gl\r\n } = this;\r\n const w = texSize[0];\r\n const h = texSize[1];\r\n const result = new Float32Array(w * h * 4);\r\n gl.readPixels(0, 0, w, h, gl.RGBA, gl.FLOAT, result);\r\n return result;\r\n }\r\n\r\n /**\r\n *\r\n * @param {Boolean} [flip]\r\n * @return {Uint8Array}\r\n */\r\n getPixels(flip) {\r\n const {\r\n context: gl,\r\n output\r\n } = this;\r\n const [width, height] = output;\r\n const pixels = new Uint8Array(width * height * 4);\r\n gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);\r\n // flipped by default, so invert\r\n return new Uint8ClampedArray((flip ? pixels : utils.flipPixels(pixels, width, height)).buffer);\r\n }\r\n\r\n renderKernelsToArrays() {\r\n const result = {\r\n result: this.renderOutput(),\r\n };\r\n for (let i = 0; i < this.subKernels.length; i++) {\r\n result[this.subKernels[i].property] = new this.TextureConstructor({\r\n texture: this.subKernelOutputTextures[i],\r\n size: this.texSize,\r\n dimensions: this.threadDim,\r\n output: this.output,\r\n context: this.context,\r\n }).toArray();\r\n }\r\n return result;\r\n }\r\n\r\n renderKernelsToTextures() {\r\n const result = {\r\n result: this.renderOutput(),\r\n };\r\n for (let i = 0; i < this.subKernels.length; i++) {\r\n result[this.subKernels[i].property] = new this.TextureConstructor({\r\n texture: this.subKernelOutputTextures[i],\r\n size: this.texSize,\r\n dimensions: this.threadDim,\r\n output: this.output,\r\n context: this.context,\r\n });\r\n }\r\n return result;\r\n }\r\n\r\n setOutput(output) {\r\n super.setOutput(output);\r\n if (this.program) {\r\n this.threadDim = [this.output[0], this.output[1] || 1, this.output[2] || 1];\r\n this.texSize = utils.getKernelTextureSize({\r\n optimizeFloatMemory: this.optimizeFloatMemory,\r\n precision: this.precision,\r\n }, this.output);\r\n const { context: gl } = this;\r\n gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);\r\n this.updateMaxTexSize();\r\n this.framebuffer.width = this.texSize[0];\r\n this.framebuffer.height = this.texSize[1];\r\n this.context.viewport(0, 0, this.maxTexSize[0], this.maxTexSize[1]);\r\n this.canvas.width = this.maxTexSize[0];\r\n this.canvas.height = this.maxTexSize[1];\r\n this._setupOutputTexture();\r\n if (this.subKernels && this.subKernels.length > 0) {\r\n this._setupSubOutputTextures();\r\n }\r\n }\r\n return this;\r\n }\r\n renderValues() {\r\n return this.formatValues(\r\n this.transferValues(),\r\n this.output[0],\r\n this.output[1],\r\n this.output[2]\r\n );\r\n }\r\n}\r\n\r\nexport const renderStrategy = Object.freeze({\r\n PackedPixelToUint8Array: Symbol('PackedPixelToUint8Array'),\r\n PackedPixelToFloat: Symbol('PackedPixelToFloat'),\r\n PackedPixelTo2DFloat: Symbol('PackedPixelTo2DFloat'),\r\n PackedPixelTo3DFloat: Symbol('PackedPixelTo3DFloat'),\r\n PackedTexture: Symbol('PackedTexture'),\r\n FloatPixelToFloat32Array: Symbol('FloatPixelToFloat32Array'),\r\n FloatPixelToFloat: Symbol('FloatPixelToFloat'),\r\n FloatPixelTo2DFloat: Symbol('FloatPixelTo2DFloat'),\r\n FloatPixelTo3DFloat: Symbol('FloatPixelTo3DFloat'),\r\n FloatPixelToArray2: Symbol('FloatPixelToArray2'),\r\n FloatPixelTo2DArray2: Symbol('FloatPixelTo2DArray2'),\r\n FloatPixelTo3DArray2: Symbol('FloatPixelTo3DArray2'),\r\n FloatPixelToArray3: Symbol('FloatPixelToArray3'),\r\n FloatPixelTo2DArray3: Symbol('FloatPixelTo2DArray3'),\r\n FloatPixelTo3DArray3: Symbol('FloatPixelTo3DArray3'),\r\n FloatPixelToArray4: Symbol('FloatPixelToArray4'),\r\n FloatPixelTo2DArray4: Symbol('FloatPixelTo2DArray4'),\r\n FloatPixelTo3DArray4: Symbol('FloatPixelTo3DArray4'),\r\n FloatTexture: Symbol('FloatTexture'),\r\n MemoryOptimizedFloatPixelToMemoryOptimizedFloat: Symbol('MemoryOptimizedFloatPixelToFloat'),\r\n MemoryOptimizedFloatPixelToMemoryOptimized2DFloat: Symbol('MemoryOptimizedFloatPixelTo2DFloat'),\r\n MemoryOptimizedFloatPixelToMemoryOptimized3DFloat: Symbol('MemoryOptimizedFloatPixelTo3DFloat'),\r\n});\r\n\r\nconst typeMap = {\r\n int: 'Integer',\r\n float: 'Number',\r\n vec2: 'Array(2)',\r\n vec3: 'Array(3)',\r\n vec4: 'Array(4)',\r\n};\r\n","import { utils } from '../../utils';\r\nimport { FunctionNode } from '../function-node';\r\n// Closure capture for the ast function, prevent collision with existing AST functions\r\n// The prefixes to use\r\nconst jsMathPrefix = 'Math.';\r\nconst localPrefix = 'this.';\r\n\r\n/**\r\n * @desc [INTERNAL] Takes in a function node, and does all the AST voodoo required to toString its respective WebGL code\r\n * @extends FunctionNode\r\n * @returns the converted WebGL function string\r\n */\r\nexport class WebGLFunctionNode extends FunctionNode {\r\n constructor(source, settings) {\r\n super(source, settings);\r\n if (settings && settings.hasOwnProperty('fixIntegerDivisionAccuracy')) {\r\n this.fixIntegerDivisionAccuracy = settings.fixIntegerDivisionAccuracy;\r\n }\r\n }\r\n\r\n /**\r\n * @desc Parses the abstract syntax tree for to its *named function*\r\n * @param {Object} ast - the AST object to parse\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astFunction(ast, retArr) {\r\n // Setup function return type and name\r\n if (this.isRootKernel) {\r\n retArr.push('void');\r\n } else {\r\n // looking up return type, this is a little expensive, and can be avoided if returnType is set\r\n let lastReturn = null;\r\n if (!this.returnType) {\r\n const lastReturn = this.findLastReturn();\r\n if (lastReturn) {\r\n this.returnType = this.getType(ast.body);\r\n if (this.returnType === 'LiteralInteger') {\r\n this.returnType = 'Number';\r\n }\r\n }\r\n }\r\n\r\n const { returnType } = this;\r\n if (!returnType) {\r\n retArr.push('void');\r\n } else {\r\n const type = typeMap[returnType];\r\n if (!type) {\r\n throw new Error(`unknown type ${returnType}`);\r\n }\r\n retArr.push(type);\r\n }\r\n }\r\n retArr.push(' ');\r\n retArr.push(this.name);\r\n retArr.push('(');\r\n\r\n if (!this.isRootKernel) {\r\n // Arguments handling\r\n for (let i = 0; i < this.argumentNames.length; ++i) {\r\n const argumentName = this.argumentNames[i];\r\n\r\n if (i > 0) {\r\n retArr.push(', ');\r\n }\r\n let argumentType = this.argumentTypes[this.argumentNames.indexOf(argumentName)];\r\n // The type is too loose ended, here we descide to solidify a type, lets go with float\r\n if (!argumentType) {\r\n throw this.astErrorOutput(`Unknown argument ${argumentName} type`, ast);\r\n }\r\n if (argumentType === 'LiteralInteger') {\r\n this.argumentTypes[i] = argumentType = 'Number';\r\n }\r\n const type = typeMap[argumentType];\r\n if (!type) {\r\n throw this.astErrorOutput('Unexpected expression', ast);\r\n }\r\n retArr.push(type);\r\n retArr.push(' ');\r\n retArr.push('user_');\r\n retArr.push(argumentName);\r\n }\r\n }\r\n\r\n // Function opening\r\n retArr.push(') {\\n');\r\n\r\n // Body statement iteration\r\n for (let i = 0; i < ast.body.body.length; ++i) {\r\n this.astGeneric(ast.body.body[i], retArr);\r\n retArr.push('\\n');\r\n }\r\n\r\n // Function closing\r\n retArr.push('}\\n');\r\n return retArr;\r\n }\r\n\r\n /**\r\n * @desc Parses the abstract syntax tree for to *return* statement\r\n * @param {Object} ast - the AST object to parse\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astReturnStatement(ast, retArr) {\r\n if (!ast.argument) throw this.astErrorOutput('Unexpected return statement', ast);\r\n this.pushState('skip-literal-correction');\r\n const type = this.getType(ast.argument);\r\n this.popState('skip-literal-correction');\r\n\r\n const result = [];\r\n\r\n if (!this.returnType) {\r\n if (type === 'LiteralInteger' || type === 'Integer') {\r\n this.returnType = 'Number';\r\n } else {\r\n this.returnType = type;\r\n }\r\n }\r\n\r\n switch (this.returnType) {\r\n case 'LiteralInteger':\r\n case 'Number':\r\n case 'Float':\r\n switch (type) {\r\n case 'Integer':\r\n result.push('float(');\r\n this.astGeneric(ast.argument, result);\r\n result.push(')');\r\n break;\r\n case 'LiteralInteger':\r\n this.castLiteralToFloat(ast.argument, result);\r\n\r\n // Running astGeneric forces the LiteralInteger to pick a type, and here, if we are returning a float, yet\r\n // the LiteralInteger has picked to be an integer because of constraints on it we cast it to float.\r\n if (this.getType(ast) === 'Integer') {\r\n result.unshift('float(');\r\n result.push(')');\r\n }\r\n break;\r\n default:\r\n this.astGeneric(ast.argument, result);\r\n }\r\n break;\r\n case 'Integer':\r\n switch (type) {\r\n case 'Float':\r\n case 'Number':\r\n this.castValueToInteger(ast.argument, result);\r\n break;\r\n case 'LiteralInteger':\r\n this.castLiteralToInteger(ast.argument, result);\r\n break;\r\n default:\r\n this.astGeneric(ast.argument, result);\r\n }\r\n break;\r\n case 'Array(4)':\r\n case 'Array(3)':\r\n case 'Array(2)':\r\n case 'Input':\r\n this.astGeneric(ast.argument, result);\r\n break;\r\n default:\r\n throw this.astErrorOutput(`unhandled return type ${this.returnType}`, ast);\r\n }\r\n\r\n if (this.isRootKernel) {\r\n retArr.push(`kernelResult = ${ result.join('') };`);\r\n retArr.push('return;');\r\n } else if (this.isSubKernel) {\r\n retArr.push(`subKernelResult_${ this.name } = ${ result.join('') };`);\r\n retArr.push(`return subKernelResult_${ this.name };`);\r\n } else {\r\n retArr.push(`return ${ result.join('') };`);\r\n }\r\n return retArr;\r\n }\r\n\r\n /**\r\n * @desc Parses the abstract syntax tree for *literal value*\r\n *\r\n * @param {Object} ast - the AST object to parse\r\n * @param {Array} retArr - return array string\r\n *\r\n * @returns {Array} the append retArr\r\n */\r\n astLiteral(ast, retArr) {\r\n // Reject non numeric literals\r\n if (isNaN(ast.value)) {\r\n throw this.astErrorOutput(\r\n 'Non-numeric literal not supported : ' + ast.value,\r\n ast\r\n );\r\n }\r\n\r\n const key = `${ast.start},${ast.end}`;\r\n if (Number.isInteger(ast.value)) {\r\n if (this.isState('in-for-loop-init') || this.isState('casting-to-integer') || this.isState('building-integer')) {\r\n this.literalTypes[key] = 'Integer';\r\n retArr.push(`${ast.value}`);\r\n } else if (this.isState('casting-to-float') || this.isState('building-float')) {\r\n this.literalTypes[key] = 'Number';\r\n retArr.push(`${ast.value}.0`);\r\n } else {\r\n this.literalTypes[key] = 'Number';\r\n retArr.push(`${ast.value}.0`);\r\n }\r\n } else if (this.isState('casting-to-integer') || this.isState('building-integer')) {\r\n this.literalTypes[key] = 'Integer';\r\n retArr.push(Math.round(ast.value));\r\n } else {\r\n this.literalTypes[key] = 'Number';\r\n retArr.push(`${ast.value}`);\r\n }\r\n return retArr;\r\n }\r\n\r\n /**\r\n * @desc Parses the abstract syntax tree for *binary* expression\r\n * @param {Object} ast - the AST object to parse\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astBinaryExpression(ast, retArr) {\r\n if (this.checkAndUpconvertOperator(ast, retArr)) {\r\n return retArr;\r\n }\r\n\r\n if (this.fixIntegerDivisionAccuracy && ast.operator === '/') {\r\n retArr.push('div_with_int_check(');\r\n this.pushState('building-float');\r\n switch (this.getType(ast.left)) {\r\n case 'Integer':\r\n this.castValueToFloat(ast.left, retArr);\r\n break;\r\n case 'LiteralInteger':\r\n this.castLiteralToFloat(ast.left, retArr);\r\n break;\r\n default:\r\n this.astGeneric(ast.left, retArr);\r\n }\r\n retArr.push(', ');\r\n switch (this.getType(ast.right)) {\r\n case 'Integer':\r\n this.castValueToFloat(ast.right, retArr);\r\n break;\r\n case 'LiteralInteger':\r\n this.castLiteralToFloat(ast.right, retArr);\r\n break;\r\n default:\r\n this.astGeneric(ast.right, retArr);\r\n }\r\n this.popState('building-float');\r\n retArr.push(')');\r\n return retArr;\r\n }\r\n\r\n retArr.push('(');\r\n const leftType = this.getType(ast.left) || 'Number';\r\n const rightType = this.getType(ast.right) || 'Number';\r\n if (!leftType || !rightType) {\r\n throw this.astErrorOutput(`Unhandled binary expression`, ast);\r\n }\r\n const key = leftType + ' & ' + rightType;\r\n switch (key) {\r\n case 'Integer & Integer':\r\n this.pushState('building-integer');\r\n this.astGeneric(ast.left, retArr);\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.astGeneric(ast.right, retArr);\r\n this.popState('building-integer');\r\n break;\r\n case 'Number & Float':\r\n case 'Float & Number':\r\n case 'Float & Float':\r\n case 'Number & Number':\r\n this.pushState('building-float');\r\n this.astGeneric(ast.left, retArr);\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.astGeneric(ast.right, retArr);\r\n this.popState('building-float');\r\n break;\r\n case 'LiteralInteger & LiteralInteger':\r\n if (this.isState('casting-to-integer') || this.isState('building-integer')) {\r\n this.pushState('building-integer');\r\n this.astGeneric(ast.left, retArr);\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.astGeneric(ast.right, retArr);\r\n this.popState('building-integer');\r\n } else {\r\n this.pushState('building-float');\r\n this.castLiteralToFloat(ast.left, retArr);\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.castLiteralToFloat(ast.right, retArr);\r\n this.popState('building-float');\r\n }\r\n break;\r\n\r\n case 'Integer & Float':\r\n case 'Integer & Number':\r\n if (ast.operator === '>' || ast.operator === '<' && ast.right.type === 'Literal') {\r\n // if right value is actually a float, don't loose that information, cast left to right rather than the usual right to left\r\n if (!Number.isInteger(ast.right.value)) {\r\n this.pushState('building-float');\r\n this.castValueToFloat(ast.left, retArr);\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.astGeneric(ast.right, retArr);\r\n this.popState('building-float');\r\n break;\r\n }\r\n }\r\n this.pushState('building-integer');\r\n this.astGeneric(ast.left, retArr);\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.pushState('casting-to-integer');\r\n if (ast.right.type === 'Literal') {\r\n const literalResult = [];\r\n this.astGeneric(ast.right, literalResult);\r\n const literalType = this.getType(ast.right);\r\n if (literalType === 'Integer') {\r\n retArr.push(literalResult.join(''));\r\n } else {\r\n throw this.astErrorOutput(`Unhandled binary expression with literal`, ast);\r\n }\r\n } else {\r\n retArr.push('int(');\r\n this.astGeneric(ast.right, retArr);\r\n retArr.push(')');\r\n }\r\n this.popState('casting-to-integer');\r\n this.popState('building-integer');\r\n break;\r\n case 'Integer & LiteralInteger':\r\n this.pushState('building-integer');\r\n this.astGeneric(ast.left, retArr);\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.castLiteralToInteger(ast.right, retArr);\r\n this.popState('building-integer');\r\n break;\r\n\r\n case 'Number & Integer':\r\n this.pushState('building-float');\r\n this.astGeneric(ast.left, retArr);\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.castValueToFloat(ast.right, retArr);\r\n this.popState('building-float');\r\n break;\r\n case 'Float & LiteralInteger':\r\n case 'Number & LiteralInteger':\r\n if (this.isState('in-for-loop-test')) {\r\n this.pushState('building-integer');\r\n retArr.push('int(');\r\n this.astGeneric(ast.left, retArr);\r\n retArr.push(')');\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.castLiteralToInteger(ast.right, retArr);\r\n this.popState('building-integer');\r\n } else {\r\n this.pushState('building-float');\r\n this.astGeneric(ast.left, retArr);\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.castLiteralToFloat(ast.right, retArr);\r\n this.popState('building-float');\r\n }\r\n break;\r\n case 'LiteralInteger & Float':\r\n case 'LiteralInteger & Number':\r\n if (this.isState('in-for-loop-test') || this.isState('in-for-loop-init') || this.isState('casting-to-integer')) {\r\n this.pushState('building-integer');\r\n this.castLiteralToInteger(ast.left, retArr);\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.castValueToInteger(ast.right, retArr);\r\n this.popState('building-integer');\r\n } else {\r\n this.pushState('building-float');\r\n this.astGeneric(ast.left, retArr);\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.pushState('casting-to-float');\r\n this.astGeneric(ast.right, retArr);\r\n this.popState('casting-to-float');\r\n this.popState('building-float');\r\n }\r\n break;\r\n case 'LiteralInteger & Integer':\r\n this.pushState('building-integer');\r\n this.castLiteralToInteger(ast.left, retArr);\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.astGeneric(ast.right, retArr);\r\n this.popState('building-integer');\r\n break;\r\n\r\n case 'Boolean & Boolean':\r\n this.pushState('building-boolean');\r\n this.astGeneric(ast.left, retArr);\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.astGeneric(ast.right, retArr);\r\n this.popState('building-boolean');\r\n break;\r\n\r\n case 'Float & Integer':\r\n this.pushState('building-float');\r\n this.astGeneric(ast.left, retArr);\r\n retArr.push(operatorMap[ast.operator] || ast.operator);\r\n this.castValueToFloat(ast.right, retArr);\r\n this.popState('building-float');\r\n break;\r\n\r\n default:\r\n throw this.astErrorOutput(`Unhandled binary expression between ${key}`, ast);\r\n }\r\n retArr.push(')');\r\n\r\n return retArr;\r\n }\r\n\r\n checkAndUpconvertOperator(ast, retArr) {\r\n const bitwiseResult = this.checkAndUpconvertBitwiseOperators(ast, retArr);\r\n if (bitwiseResult) {\r\n return bitwiseResult;\r\n }\r\n const upconvertableOperators = {\r\n '%': 'mod',\r\n '**': 'pow',\r\n };\r\n const foundOperator = upconvertableOperators[ast.operator];\r\n if (!foundOperator) return null;\r\n retArr.push(foundOperator);\r\n retArr.push('(');\r\n switch (this.getType(ast.left)) {\r\n case 'Integer':\r\n this.castValueToFloat(ast.left, retArr);\r\n break;\r\n case 'LiteralInteger':\r\n this.castLiteralToFloat(ast.left, retArr);\r\n break;\r\n default:\r\n this.astGeneric(ast.left, retArr);\r\n }\r\n retArr.push(',');\r\n switch (this.getType(ast.right)) {\r\n case 'Integer':\r\n this.castValueToFloat(ast.right, retArr);\r\n break;\r\n case 'LiteralInteger':\r\n this.castLiteralToFloat(ast.right, retArr);\r\n break;\r\n default:\r\n this.astGeneric(ast.right, retArr);\r\n }\r\n retArr.push(')');\r\n return retArr;\r\n }\r\n\r\n checkAndUpconvertBitwiseOperators(ast, retArr) {\r\n const upconvertableOperators = {\r\n '&': 'bitwiseAnd',\r\n '|': 'bitwiseOr',\r\n '^': 'bitwiseXOR',\r\n '<<': 'bitwiseZeroFillLeftShift',\r\n '>>': 'bitwiseSignedRightShift',\r\n '>>>': 'bitwiseZeroFillRightShift',\r\n };\r\n const foundOperator = upconvertableOperators[ast.operator];\r\n if (!foundOperator) return null;\r\n retArr.push(foundOperator);\r\n retArr.push('(');\r\n const leftType = this.getType(ast.left);\r\n switch (leftType) {\r\n case 'Number':\r\n case 'Float':\r\n this.castValueToInteger(ast.left, retArr);\r\n break;\r\n case 'LiteralInteger':\r\n this.castLiteralToInteger(ast.left, retArr);\r\n break;\r\n default:\r\n this.astGeneric(ast.left, retArr);\r\n }\r\n retArr.push(',');\r\n const rightType = this.getType(ast.right);\r\n switch (rightType) {\r\n case 'Number':\r\n case 'Float':\r\n this.castValueToInteger(ast.right, retArr);\r\n break;\r\n case 'LiteralInteger':\r\n this.castLiteralToInteger(ast.right, retArr);\r\n break;\r\n default:\r\n this.astGeneric(ast.right, retArr);\r\n }\r\n retArr.push(')');\r\n return retArr;\r\n }\r\n\r\n checkAndUpconvertBitwiseUnary(ast, retArr) {\r\n const upconvertableOperators = {\r\n '~': 'bitwiseNot',\r\n };\r\n const foundOperator = upconvertableOperators[ast.operator];\r\n if (!foundOperator) return null;\r\n retArr.push(foundOperator);\r\n retArr.push('(');\r\n switch (this.getType(ast.argument)) {\r\n case 'Number':\r\n case 'Float':\r\n this.castValueToInteger(ast.argument, retArr);\r\n break;\r\n case 'LiteralInteger':\r\n this.castLiteralToInteger(ast.argument, retArr);\r\n break;\r\n default:\r\n this.astGeneric(ast.argument, retArr);\r\n }\r\n retArr.push(')');\r\n return retArr;\r\n }\r\n\r\n /**\r\n *\r\n * @param {Object} ast\r\n * @param {Array} retArr\r\n * @return {String[]}\r\n */\r\n castLiteralToInteger(ast, retArr) {\r\n this.pushState('casting-to-integer');\r\n this.astGeneric(ast, retArr);\r\n this.popState('casting-to-integer');\r\n return retArr;\r\n }\r\n\r\n /**\r\n *\r\n * @param {Object} ast\r\n * @param {Array} retArr\r\n * @return {String[]}\r\n */\r\n castLiteralToFloat(ast, retArr) {\r\n this.pushState('casting-to-float');\r\n this.astGeneric(ast, retArr);\r\n this.popState('casting-to-float');\r\n return retArr;\r\n }\r\n\r\n /**\r\n *\r\n * @param {Object} ast\r\n * @param {Array} retArr\r\n * @return {String[]}\r\n */\r\n castValueToInteger(ast, retArr) {\r\n this.pushState('casting-to-integer');\r\n retArr.push('int(');\r\n this.astGeneric(ast, retArr);\r\n retArr.push(')');\r\n this.popState('casting-to-integer');\r\n return retArr;\r\n }\r\n\r\n /**\r\n *\r\n * @param {Object} ast\r\n * @param {Array} retArr\r\n * @return {String[]}\r\n */\r\n castValueToFloat(ast, retArr) {\r\n this.pushState('casting-to-float');\r\n retArr.push('float(');\r\n this.astGeneric(ast, retArr);\r\n retArr.push(')');\r\n this.popState('casting-to-float');\r\n return retArr;\r\n }\r\n\r\n /**\r\n * @desc Parses the abstract syntax tree for *identifier* expression\r\n * @param {Object} idtNode - An ast Node\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the append retArr\r\n */\r\n astIdentifierExpression(idtNode, retArr) {\r\n if (idtNode.type !== 'Identifier') {\r\n throw this.astErrorOutput('IdentifierExpression - not an Identifier', idtNode);\r\n }\r\n\r\n const type = this.getType(idtNode);\r\n\r\n if (idtNode.name === 'Infinity') {\r\n // https://stackoverflow.com/a/47543127/1324039\r\n retArr.push('3.402823466e+38');\r\n } else if (type === 'Boolean') {\r\n if (this.argumentNames.indexOf(idtNode.name) > -1) {\r\n retArr.push(`bool(user_${idtNode.name})`);\r\n } else {\r\n retArr.push(`user_${idtNode.name}`);\r\n }\r\n } else {\r\n retArr.push(`user_${idtNode.name}`);\r\n }\r\n\r\n return retArr;\r\n }\r\n\r\n /**\r\n * @desc Parses the abstract syntax tree for *for-loop* expression\r\n * @param {Object} forNode - An ast Node\r\n * @param {Array} retArr - return array string\r\n * @returns {Array} the parsed webgl string\r\n */\r\n astForStatement(forNode, retArr) {\r\n if (forNode.type !== 'ForStatement') {\r\n throw this.astErrorOutput('Invalid for statement', forNode);\r\n }\r\n\r\n const initArr = [];\r\n const testArr = [];\r\n const updateArr = [];\r\n const bodyArr = [];\r\n let isSafe = null;\r\n\r\n if (forNode.init) {\r\n this.pushState('in-for-loop-init');\r\n this.astGeneric(forNode.init, initArr);\r\n const { declarations } = forNode.init;\r\n for (let i = 0; i < declarations.length; i++) {\r\n if (declarations[i].init && declarations[i].init.type !== 'Literal') {\r\n isSafe = false;\r\n }\r\n }\r\n if (isSafe) {\r\n for (let i = 0; i < initArr.length; i++) {\r\n if (initArr[i].includes && initArr[i].includes(',')) {\r\n isSafe = false;\r\n }\r\n }\r\n }\r\n this.popState('in-for-loop-init');\r\n } else {\r\n isSafe = false;\r\n }\r\n\r\n if (forNode.test) {\r\n this.pushState('in-for-loop-test');\r\n this.astGeneric(forNode.test, testArr);\r\n this.popState('in-for-loop-test');\r\n } else {\r\n isSafe = false;\r\n }\r\n\r\n if (forNode.update) {\r\n this.astGeneric(forNode.update, updateArr);\r\n } else {\r\n isSafe = false;\r\n }\r\n\r\n if (forNode.body) {\r\n this.pushState('loop-body');\r\n this.astGeneric(forNode.body, bodyArr);\r\n this.popState('loop-body');\r\n }\r\n\r\n // have all parts, now make them safe\r\n if (isSafe === null) {\r\n isSafe = this.isSafe(forNode.init) && this.isSafe(forNode.test);\r\n }\r\n\r\n if (isSafe) {\r\n retArr.push(`for (${initArr.join('')};${testArr.join('')};${updateArr.join('')}){\\n`);\r\n retArr.push(bodyArr.join(''));\r\n retArr.push('}\\n');\r\n } else {\r\n const iVariableName = this.getInternalVariableName('safeI');\r\n if (initArr.length > 0) {\r\n retArr.push(initArr.join(''), ';\\n');\r\n }\r\n retArr.push(`for (int ${iVariableName}=0;${iVariableName}This handles all the raw state, converted state, etc. Of a single function.
- */ -class CPUFunctionNode extends FunctionNode { - /** - * @desc Parses the abstract syntax tree for to its *named function* - * @param {Object} ast - the AST object to parse - * @param {Array} retArr - return array string - * @returns {Array} the append retArr - */ - astFunction(ast, retArr) { - - // Setup function return type and name - if (!this.isRootKernel) { - retArr.push('function'); - retArr.push(' '); - retArr.push(this.name); - retArr.push('('); - - // Arguments handling - 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); - } - - // Function opening - retArr.push(') {\n'); - } - - // Body statement iteration - for (let i = 0; i < ast.body.body.length; ++i) { - this.astGeneric(ast.body.body[i], retArr); - retArr.push('\n'); - } - - if (!this.isRootKernel) { - // Function closing - retArr.push('}\n'); - } - return retArr; - } - - /** - * @desc Parses the abstract syntax tree for to *return* statement - * @param {Object} ast - the AST object to parse - * @param {Array} retArr - return array string - * @returns {Array} the append 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; - } - - /** - * @desc Parses the abstract syntax tree for *literal value* - * @param {Object} ast - the AST object to parse - * @param {Array} retArr - return array string - * @returns {Array} the append retArr - */ - astLiteral(ast, retArr) { - - // Reject non numeric literals - if (isNaN(ast.value)) { - throw this.astErrorOutput( - 'Non-numeric literal not supported : ' + ast.value, - ast - ); - } - - retArr.push(ast.value); - - return retArr; - } - - /** - * @desc Parses the abstract syntax tree for *binary* expression - * @param {Object} ast - the AST object to parse - * @param {Array} retArr - return array string - * @returns {Array} the append retArr - */ - astBinaryExpression(ast, retArr) { - retArr.push('('); - this.astGeneric(ast.left, retArr); - retArr.push(ast.operator); - this.astGeneric(ast.right, retArr); - retArr.push(')'); - return retArr; - } - - /** - * @desc Parses the abstract syntax tree for *identifier* expression - * @param {Object} idtNode - An ast Node - * @param {Array} retArr - return array string - * @returns {Array} the append 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; - } - - /** - * @desc Parses the abstract syntax tree for *for-loop* expression - * @param {Object} forNode - An ast Node - * @param {Array} retArr - return array string - * @returns {Array} the parsed webgl string - */ - 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'); - } - - // have all parts, now make them safe - 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}This handles all the raw state, converted state, etc. Of a single function.
+ */ +export class CPUFunctionNode extends FunctionNode { + /** + * @desc Parses the abstract syntax tree for to its *named function* + * @param {Object} ast - the AST object to parse + * @param {Array} retArr - return array string + * @returns {Array} the append retArr + */ + astFunction(ast, retArr) { + + // Setup function return type and name + if (!this.isRootKernel) { + retArr.push('function'); + retArr.push(' '); + retArr.push(this.name); + retArr.push('('); + + // Arguments handling + 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); + } + + // Function opening + retArr.push(') {\n'); + } + + // Body statement iteration + for (let i = 0; i < ast.body.body.length; ++i) { + this.astGeneric(ast.body.body[i], retArr); + retArr.push('\n'); + } + + if (!this.isRootKernel) { + // Function closing + retArr.push('}\n'); + } + return retArr; + } + + /** + * @desc Parses the abstract syntax tree for to *return* statement + * @param {Object} ast - the AST object to parse + * @param {Array} retArr - return array string + * @returns {Array} the append 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; + } + + /** + * @desc Parses the abstract syntax tree for *literal value* + * @param {Object} ast - the AST object to parse + * @param {Array} retArr - return array string + * @returns {Array} the append retArr + */ + astLiteral(ast, retArr) { + + // Reject non numeric literals + if (isNaN(ast.value)) { + throw this.astErrorOutput( + 'Non-numeric literal not supported : ' + ast.value, + ast + ); + } + + retArr.push(ast.value); + + return retArr; + } + + /** + * @desc Parses the abstract syntax tree for *binary* expression + * @param {Object} ast - the AST object to parse + * @param {Array} retArr - return array string + * @returns {Array} the append retArr + */ + astBinaryExpression(ast, retArr) { + retArr.push('('); + this.astGeneric(ast.left, retArr); + retArr.push(ast.operator); + this.astGeneric(ast.right, retArr); + retArr.push(')'); + return retArr; + } + + /** + * @desc Parses the abstract syntax tree for *identifier* expression + * @param {Object} idtNode - An ast Node + * @param {Array} retArr - return array string + * @returns {Array} the append 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; + } + + /** + * @desc Parses the abstract syntax tree for *for-loop* expression + * @param {Object} forNode - An ast Node + * @param {Array} retArr - return array string + * @returns {Array} the parsed webgl string + */ + 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'); + } + + // have all parts, now make them safe + 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}Instantiates properties to the CPU Kernel.
- */ -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; - } - /** - * @desc The current mode in which gpu.js is executing. - */ - static get mode() { - return 'cpu'; - } - - static nativeFunctionArguments() { - return null; - } - - static nativeFunctionReturnType() { - return null; - } - - static combineKernels(combinedKernel) { - return combinedKernel; - } - - constructor(source, settings) { - super(source, settings); - this.mergeSettings(source.settings || settings); - - this._imageData = null; - this._colorData = null; - this._kernelString = null; - 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 []; - } - - /** - * @desc Validate settings related to Kernel, such as dimensions size, and auto output support. - * @param {IArguments} args - */ - 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(); - } - } - - /** - * @desc Builds the Kernel, by generating the kernel - * string using thread dimensions, and arguments - * supplied to the kernel. - * - *If the graphical flag is enabled, canvas is used.
- */ - build() { - 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); - } - } - - 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; - } - - /** - * @desc Generates kernel string for this kernel program. - * - *If sub-kernels are supplied, they are also factored in. - * This string can be saved by calling the `toString` method - * and then can be reused later.
- * - * @returns {String} result - * - */ - 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._processConstants() } - return (${ this.argumentNames.map(argumentName => 'user_' + argumentName).join(', ') }) => { - ${ this._processArguments() } - ${ this.graphical ? this._graphicalKernelBody(kernelThreadString) : this._resultKernelBody(kernelThreadString) } - ${ translatedSources.length > 0 ? translatedSources.join('\n') : '' } - };`; - } - - /** - * @desc Returns the *pre-compiled* Kernel as a JS Object String, that can be reused. - */ - toString() { - return cpuKernelString(this); - } - - /** - * @desc Get the maximum loop size String. - * @returns {String} result - */ - _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 'HTMLImage': - 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(''); - } - - _processArguments() { - const result = []; - for (let i = 0; i < this.argumentTypes.length; i++) { - const variableName = `user_${this.argumentNames[i]}`; - switch (this.argumentTypes[i]) { - case 'HTMLImage': - case 'HTMLVideo': - result.push(` ${variableName} = this._mediaTo2DArray(${variableName});\n`); - break; - case 'HTMLImageArray': - result.push(` ${variableName} = this._imageTo3DArray(${variableName});\n`); - break; - case 'Input': - result.push(` ${variableName} = ${variableName}.value;\n`); - break; - case 'ArrayTexture(1)': - case 'ArrayTexture(2)': - case 'ArrayTexture(3)': - case 'ArrayTexture(4)': - case 'NumberTexture': - case 'MemoryOptimizedNumberTexture': - result.push(` - if (${variableName}.toArray) { - if (!_this.textureCache) { - _this.textureCache = []; - _this.arrayCache = []; - } - const textureIndex = _this.textureCache.indexOf(${variableName}); - if (textureIndex !== -1) { - ${variableName} = _this.arrayCache[textureIndex]; - } else { - _this.textureCache.push(${variableName}); - ${variableName} = ${variableName}.toArray(); - _this.arrayCache.push(${variableName}); - } - }`); - break; - } - } - return result.join(''); - } - - _mediaTo2DArray(media) { - const canvas = this.canvas; - const width = media.width > 0 ? media.width : media.videoWidth; - const height = media.height > 0 ? media.height : media.videoHeight; - if (canvas.width < width) { - canvas.width = width; - } - if (canvas.height < height) { - canvas.height = height; - } - const ctx = this.context; - ctx.drawImage(media, 0, 0, width, height); - const pixelsData = ctx.getImageData(0, 0, width, height).data; - const imageArray = new Array(height); - let index = 0; - for (let y = height - 1; y >= 0; y--) { - const row = imageArray[y] = new Array(width); - for (let x = 0; x < width; x++) { - const pixel = new Float32Array(4); - pixel[0] = pixelsData[index++] / 255; // r - pixel[1] = pixelsData[index++] / 255; // g - pixel[2] = pixelsData[index++] / 255; // b - pixel[3] = pixelsData[index++] / 255; // a - row[x] = pixel; - } - } - return imageArray; - } - - getPixels(flip) { - const [width, height] = this.output; - // cpu is not flipped by default - return flip ? utils.flipPixels(this._imageData.data, width, height) : this._imageData.data.slice(0); - } - - _imageTo3DArray(images) { - const imagesArray = new Array(images.length); - for (let i = 0; i < images.length; i++) { - imagesArray[i] = this._mediaTo2DArray(images[i]); - } - return imagesArray; - } - - _resultKernelBody(kernelString) { - switch (this.output.length) { - case 1: - return this._resultKernel1DLoop(kernelString) + this._kernelOutput(); - case 2: - return this._resultKernel2DLoop(kernelString) + this._kernelOutput(); - case 3: - return this._resultKernel3DLoop(kernelString) + this._kernelOutput(); - default: - throw new Error('unsupported size kernel'); - } - } - - _graphicalKernelBody(kernelThreadString) { - switch (this.output.length) { - case 2: - return this._graphicalKernel2DLoop(kernelThreadString) + this._graphicalOutput(); - default: - throw new Error('unsupported size kernel'); - } - } - - _graphicalOutput() { - return ` - this._imageData.data.set(this._colorData); - this.context.putImageData(this._imageData, 0, 0); - return;` - } - - _getKernelResultTypeConstructorString() { - switch (this.returnType) { - case 'LiteralInteger': - case 'Number': - case 'Integer': - case 'Float': - return 'Float32Array'; - case 'Array(2)': - case 'Array(3)': - case 'Array(4)': - return 'Array'; - default: - if (this.graphical) { - return 'Float32Array'; - } - throw new Error(`unhandled returnType ${ this.returnType }`); - } - } - - _resultKernel1DLoop(kernelString) { - const { - output - } = this; - const constructorString = this._getKernelResultTypeConstructorString(); - return ` const outputX = _this.output[0]; - const result = new ${constructorString}(outputX); - ${ this._mapSubKernels(subKernel => `const result_${ subKernel.name } = new ${constructorString}(outputX);\n`).join(' ') } - ${ this._mapSubKernels(subKernel => `let subKernelResult_${ subKernel.name };\n`).join(' ') } - for (let x = 0; x < outputX; x++) { - this.thread.x = x; - this.thread.y = 0; - this.thread.z = 0; - ${ kernelString } - }`; - } - - _resultKernel2DLoop(kernelString) { - const { - output - } = this; - const constructorString = this._getKernelResultTypeConstructorString(); - return ` const outputX = _this.output[0]; - const outputY = _this.output[1]; - const result = new Array(outputY); - ${ this._mapSubKernels(subKernel => `const result_${ subKernel.name } = new Array(outputY);\n`).join(' ') } - ${ this._mapSubKernels(subKernel => `let subKernelResult_${ subKernel.name };\n`).join(' ') } - for (let y = 0; y < outputY; y++) { - this.thread.z = 0; - this.thread.y = y; - const resultX = result[y] = new ${constructorString}(outputX); - ${ this._mapSubKernels(subKernel => `const resultX_${ subKernel.name } = result_${subKernel.name}[y] = new ${constructorString}(outputX);\n`).join('') } - for (let x = 0; x < outputX; x++) { - this.thread.x = x; - ${ kernelString } - } - }`; - } - - _graphicalKernel2DLoop(kernelString) { - const { - output - } = this; - const constructorString = this._getKernelResultTypeConstructorString(); - return ` const outputX = _this.output[0]; - const outputY = _this.output[1]; - ${ this._mapSubKernels(subKernel => `const result_${ subKernel.name } = new Array(outputY);\n`).join(' ') } - ${ this._mapSubKernels(subKernel => `let subKernelResult_${ subKernel.name };\n`).join(' ') } - for (let y = 0; y < outputY; y++) { - this.thread.z = 0; - this.thread.y = y; - ${ this._mapSubKernels(subKernel => `const resultX_${ subKernel.name } = result_${subKernel.name}[y] = new ${constructorString}(outputX);\n`).join('') } - for (let x = 0; x < outputX; x++) { - this.thread.x = x; - ${ kernelString } - } - }`; - } - - _resultKernel3DLoop(kernelString) { - const { - output - } = this; - const constructorString = this._getKernelResultTypeConstructorString(); - return ` const outputX = _this.output[0]; - const outputY = _this.output[1]; - const outputZ = _this.output[2]; - const result = new Array(outputZ); - ${ this._mapSubKernels(subKernel => `const result_${ subKernel.name } = new Array(outputZ);\n`).join(' ') } - ${ this._mapSubKernels(subKernel => `let subKernelResult_${ subKernel.name };\n`).join(' ') } - for (let z = 0; z < outputZ; z++) { - this.thread.z = z; - const resultY = result[z] = new Array(outputY); - ${ this._mapSubKernels(subKernel => `const resultY_${ subKernel.name } = result_${subKernel.name}[z] = new Array(outputY);\n`).join(' ') } - for (let y = 0; y < outputY; y++) { - this.thread.y = y; - const resultX = resultY[y] = new ${constructorString}(outputX); - ${ this._mapSubKernels(subKernel => `const resultX_${ subKernel.name } = resultY_${subKernel.name}[y] = new ${constructorString}(outputX);\n`).join(' ') } - for (let x = 0; x < outputX; x++) { - this.thread.x = x; - ${ kernelString } - } - } - }`; - } - - _kernelOutput() { - if (!this.subKernels) { - return '\n return result;'; - } - return `\n return { - result: result, - ${ this.subKernels.map(subKernel => `${ subKernel.property }: result_${ subKernel.name }`).join(',\n ') } - };`; - } - - _mapSubKernels(fn) { - return this.subKernels === null ? [''] : - this.subKernels.map(fn); - } - - - - destroy(removeCanvasReference) { - if (removeCanvasReference) { - delete this.canvas; - } - } - - static destroyContext(context) {} - - toJSON() { - const json = super.toJSON(); - json.functionNodes = FunctionBuilder.fromKernel(this, CPUFunctionNode).toJSON(); - return json; - } - - setOutput(output) { - super.setOutput(output); - const [width, height] = this.output; - if (this.graphical) { - this._imageData = this.context.createImageData(width, height); - this._colorData = new Uint8ClampedArray(width * height * 4); - } - } -} - -module.exports = { - CPUKernel -}; \ No newline at end of file +import { Kernel } from '../kernel'; +import { FunctionBuilder } from '../function-builder'; +import { CPUFunctionNode } from './function-node'; +import { utils } from '../../utils'; +import { cpuKernelString } from './kernel-string'; + +/** + * @desc Kernel Implementation for CPU. + *Instantiates properties to the CPU Kernel.
+ */ +export 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; + } + /** + * @desc The current mode in which gpu.js is executing. + */ + static get mode() { + return 'cpu'; + } + + static nativeFunctionArguments() { + return null; + } + + static nativeFunctionReturnType() { + return null; + } + + static combineKernels(combinedKernel) { + return combinedKernel; + } + + constructor(source, settings) { + super(source, settings); + this.mergeSettings(source.settings || settings); + + this._imageData = null; + this._colorData = null; + this._kernelString = null; + 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 []; + } + + /** + * @desc Validate settings related to Kernel, such as dimensions size, and auto output support. + * @param {IArguments} args + */ + 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(); + } + } + + /** + * @desc Builds the Kernel, by generating the kernel + * string using thread dimensions, and arguments + * supplied to the kernel. + * + *If the graphical flag is enabled, canvas is used.
+ */ + build() { + 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); + } + } + + 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; + } + + /** + * @desc Generates kernel string for this kernel program. + * + *If sub-kernels are supplied, they are also factored in. + * This string can be saved by calling the `toString` method + * and then can be reused later.
+ * + * @returns {String} result + * + */ + 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._processConstants() } + return (${ this.argumentNames.map(argumentName => 'user_' + argumentName).join(', ') }) => { + ${ this._processArguments() } + ${ this.graphical ? this._graphicalKernelBody(kernelThreadString) : this._resultKernelBody(kernelThreadString) } + ${ translatedSources.length > 0 ? translatedSources.join('\n') : '' } + };`; + } + + /** + * @desc Returns the *pre-compiled* Kernel as a JS Object String, that can be reused. + */ + toString() { + return cpuKernelString(this); + } + + /** + * @desc Get the maximum loop size String. + * @returns {String} result + */ + _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 'HTMLImage': + 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(''); + } + + _processArguments() { + const result = []; + for (let i = 0; i < this.argumentTypes.length; i++) { + const variableName = `user_${this.argumentNames[i]}`; + switch (this.argumentTypes[i]) { + case 'HTMLImage': + case 'HTMLVideo': + result.push(` ${variableName} = this._mediaTo2DArray(${variableName});\n`); + break; + case 'HTMLImageArray': + result.push(` ${variableName} = this._imageTo3DArray(${variableName});\n`); + break; + case 'Input': + result.push(` ${variableName} = ${variableName}.value;\n`); + break; + case 'ArrayTexture(1)': + case 'ArrayTexture(2)': + case 'ArrayTexture(3)': + case 'ArrayTexture(4)': + case 'NumberTexture': + case 'MemoryOptimizedNumberTexture': + result.push(` + if (${variableName}.toArray) { + if (!_this.textureCache) { + _this.textureCache = []; + _this.arrayCache = []; + } + const textureIndex = _this.textureCache.indexOf(${variableName}); + if (textureIndex !== -1) { + ${variableName} = _this.arrayCache[textureIndex]; + } else { + _this.textureCache.push(${variableName}); + ${variableName} = ${variableName}.toArray(); + _this.arrayCache.push(${variableName}); + } + }`); + break; + } + } + return result.join(''); + } + + _mediaTo2DArray(media) { + const canvas = this.canvas; + const width = media.width > 0 ? media.width : media.videoWidth; + const height = media.height > 0 ? media.height : media.videoHeight; + if (canvas.width < width) { + canvas.width = width; + } + if (canvas.height < height) { + canvas.height = height; + } + const ctx = this.context; + ctx.drawImage(media, 0, 0, width, height); + const pixelsData = ctx.getImageData(0, 0, width, height).data; + const imageArray = new Array(height); + let index = 0; + for (let y = height - 1; y >= 0; y--) { + const row = imageArray[y] = new Array(width); + for (let x = 0; x < width; x++) { + const pixel = new Float32Array(4); + pixel[0] = pixelsData[index++] / 255; // r + pixel[1] = pixelsData[index++] / 255; // g + pixel[2] = pixelsData[index++] / 255; // b + pixel[3] = pixelsData[index++] / 255; // a + row[x] = pixel; + } + } + return imageArray; + } + + getPixels(flip) { + const [width, height] = this.output; + // cpu is not flipped by default + return flip ? utils.flipPixels(this._imageData.data, width, height) : this._imageData.data.slice(0); + } + + _imageTo3DArray(images) { + const imagesArray = new Array(images.length); + for (let i = 0; i < images.length; i++) { + imagesArray[i] = this._mediaTo2DArray(images[i]); + } + return imagesArray; + } + + _resultKernelBody(kernelString) { + switch (this.output.length) { + case 1: + return this._resultKernel1DLoop(kernelString) + this._kernelOutput(); + case 2: + return this._resultKernel2DLoop(kernelString) + this._kernelOutput(); + case 3: + return this._resultKernel3DLoop(kernelString) + this._kernelOutput(); + default: + throw new Error('unsupported size kernel'); + } + } + + _graphicalKernelBody(kernelThreadString) { + switch (this.output.length) { + case 2: + return this._graphicalKernel2DLoop(kernelThreadString) + this._graphicalOutput(); + default: + throw new Error('unsupported size kernel'); + } + } + + _graphicalOutput() { + return ` + this._imageData.data.set(this._colorData); + this.context.putImageData(this._imageData, 0, 0); + return;` + } + + _getKernelResultTypeConstructorString() { + switch (this.returnType) { + case 'LiteralInteger': + case 'Number': + case 'Integer': + case 'Float': + return 'Float32Array'; + case 'Array(2)': + case 'Array(3)': + case 'Array(4)': + return 'Array'; + default: + if (this.graphical) { + return 'Float32Array'; + } + throw new Error(`unhandled returnType ${ this.returnType }`); + } + } + + _resultKernel1DLoop(kernelString) { + const { + output + } = this; + const constructorString = this._getKernelResultTypeConstructorString(); + return ` const outputX = _this.output[0]; + const result = new ${constructorString}(outputX); + ${ this._mapSubKernels(subKernel => `const result_${ subKernel.name } = new ${constructorString}(outputX);\n`).join(' ') } + ${ this._mapSubKernels(subKernel => `let subKernelResult_${ subKernel.name };\n`).join(' ') } + for (let x = 0; x < outputX; x++) { + this.thread.x = x; + this.thread.y = 0; + this.thread.z = 0; + ${ kernelString } + }`; + } + + _resultKernel2DLoop(kernelString) { + const { + output + } = this; + const constructorString = this._getKernelResultTypeConstructorString(); + return ` const outputX = _this.output[0]; + const outputY = _this.output[1]; + const result = new Array(outputY); + ${ this._mapSubKernels(subKernel => `const result_${ subKernel.name } = new Array(outputY);\n`).join(' ') } + ${ this._mapSubKernels(subKernel => `let subKernelResult_${ subKernel.name };\n`).join(' ') } + for (let y = 0; y < outputY; y++) { + this.thread.z = 0; + this.thread.y = y; + const resultX = result[y] = new ${constructorString}(outputX); + ${ this._mapSubKernels(subKernel => `const resultX_${ subKernel.name } = result_${subKernel.name}[y] = new ${constructorString}(outputX);\n`).join('') } + for (let x = 0; x < outputX; x++) { + this.thread.x = x; + ${ kernelString } + } + }`; + } + + _graphicalKernel2DLoop(kernelString) { + const { + output + } = this; + const constructorString = this._getKernelResultTypeConstructorString(); + return ` const outputX = _this.output[0]; + const outputY = _this.output[1]; + ${ this._mapSubKernels(subKernel => `const result_${ subKernel.name } = new Array(outputY);\n`).join(' ') } + ${ this._mapSubKernels(subKernel => `let subKernelResult_${ subKernel.name };\n`).join(' ') } + for (let y = 0; y < outputY; y++) { + this.thread.z = 0; + this.thread.y = y; + ${ this._mapSubKernels(subKernel => `const resultX_${ subKernel.name } = result_${subKernel.name}[y] = new ${constructorString}(outputX);\n`).join('') } + for (let x = 0; x < outputX; x++) { + this.thread.x = x; + ${ kernelString } + } + }`; + } + + _resultKernel3DLoop(kernelString) { + const { + output + } = this; + const constructorString = this._getKernelResultTypeConstructorString(); + return ` const outputX = _this.output[0]; + const outputY = _this.output[1]; + const outputZ = _this.output[2]; + const result = new Array(outputZ); + ${ this._mapSubKernels(subKernel => `const result_${ subKernel.name } = new Array(outputZ);\n`).join(' ') } + ${ this._mapSubKernels(subKernel => `let subKernelResult_${ subKernel.name };\n`).join(' ') } + for (let z = 0; z < outputZ; z++) { + this.thread.z = z; + const resultY = result[z] = new Array(outputY); + ${ this._mapSubKernels(subKernel => `const resultY_${ subKernel.name } = result_${subKernel.name}[z] = new Array(outputY);\n`).join(' ') } + for (let y = 0; y < outputY; y++) { + this.thread.y = y; + const resultX = resultY[y] = new ${constructorString}(outputX); + ${ this._mapSubKernels(subKernel => `const resultX_${ subKernel.name } = resultY_${subKernel.name}[y] = new ${constructorString}(outputX);\n`).join(' ') } + for (let x = 0; x < outputX; x++) { + this.thread.x = x; + ${ kernelString } + } + } + }`; + } + + _kernelOutput() { + if (!this.subKernels) { + return '\n return result;'; + } + return `\n return { + result: result, + ${ this.subKernels.map(subKernel => `${ subKernel.property }: result_${ subKernel.name }`).join(',\n ') } + };`; + } + + _mapSubKernels(fn) { + return this.subKernels === null ? [''] : + this.subKernels.map(fn); + } + + + + destroy(removeCanvasReference) { + if (removeCanvasReference) { + delete this.canvas; + } + } + + static destroyContext(context) {} + + toJSON() { + const json = super.toJSON(); + json.functionNodes = FunctionBuilder.fromKernel(this, CPUFunctionNode).toJSON(); + return json; + } + + setOutput(output) { + super.setOutput(output); + const [width, height] = this.output; + if (this.graphical) { + this._imageData = this.context.createImageData(width, height); + this._colorData = new Uint8ClampedArray(width * height * 4); + } + } +} diff --git a/src/backend/function-builder.js b/src/backend/function-builder.js index 89bb3c07..610e7705 100644 --- a/src/backend/function-builder.js +++ b/src/backend/function-builder.js @@ -1,621 +1,664 @@ -/** - * @desc This handles all the raw state, converted state, etc. of a single function. - * [INTERNAL] A collection of functionNodes. - * @class - */ -class FunctionBuilder { - /** - * - * @param {Kernel} kernel - * @param {FunctionNode} FunctionNode - * @param {object} [extraNodeOptions] - * @returns {FunctionBuilder} - * @static - */ - static fromKernel(kernel, FunctionNode, extraNodeOptions) { - const { - kernelArguments, - kernelConstants, - argumentNames, - argumentSizes, - argumentBitRatios, - constants, - constantBitRatios, - debug, - loopMaxIterations, - nativeFunctions, - output, - optimizeFloatMemory, - precision, - plugins, - source, - subKernels, - functions, - leadingReturnStatement, - followingReturnStatement, - dynamicArguments, - dynamicOutput, - warnVarUsage, - } = kernel; - - const argumentTypes = new Array(kernelArguments.length); - const constantTypes = {}; - - for (let i = 0; i < kernelArguments.length; i++) { - argumentTypes[i] = kernelArguments[i].type; - } - - for (let i = 0; i < kernelConstants.length; i++) { - const kernelConstant = kernelConstants[i]; - constantTypes[kernelConstant.name] = kernelConstant.type; - } - - const needsArgumentType = (functionName, index) => { - return functionBuilder.needsArgumentType(functionName, index); - }; - - const assignArgumentType = (functionName, index, type) => { - functionBuilder.assignArgumentType(functionName, index, type); - }; - - const lookupReturnType = (functionName, ast, requestingNode) => { - return functionBuilder.lookupReturnType(functionName, ast, requestingNode); - }; - - const lookupFunctionArgumentTypes = (functionName) => { - return functionBuilder.lookupFunctionArgumentTypes(functionName); - }; - - const lookupFunctionArgumentName = (functionName, argumentIndex) => { - return functionBuilder.lookupFunctionArgumentName(functionName, argumentIndex); - }; - - const lookupFunctionArgumentBitRatio = (functionName, argumentName) => { - return functionBuilder.lookupFunctionArgumentBitRatio(functionName, argumentName); - }; - - const triggerImplyArgumentType = (functionName, i, argumentType, requestingNode) => { - functionBuilder.assignArgumentType(functionName, i, argumentType, requestingNode); - }; - - const triggerImplyArgumentBitRatio = (functionName, argumentName, calleeFunctionName, argumentIndex) => { - functionBuilder.assignArgumentBitRatio(functionName, argumentName, calleeFunctionName, argumentIndex); - }; - - const onFunctionCall = (functionName, calleeFunctionName, args) => { - functionBuilder.trackFunctionCall(functionName, calleeFunctionName, args); - }; - - const onNestedFunction = (ast, returnType) => { - const argumentNames = []; - for (let i = 0; i < ast.params.length; i++) { - argumentNames.push(ast.params[i].name); - } - const nestedFunction = new FunctionNode(null, Object.assign({}, nodeOptions, { - returnType: null, - ast, - name: ast.id.name, - argumentNames, - lookupReturnType, - lookupFunctionArgumentTypes, - lookupFunctionArgumentName, - lookupFunctionArgumentBitRatio, - needsArgumentType, - assignArgumentType, - triggerImplyArgumentType, - triggerImplyArgumentBitRatio, - onFunctionCall, - warnVarUsage, - })); - nestedFunction.traceFunctionAST(ast); - functionBuilder.addFunctionNode(nestedFunction); - }; - - const nodeOptions = Object.assign({ - isRootKernel: false, - onNestedFunction, - lookupReturnType, - lookupFunctionArgumentTypes, - lookupFunctionArgumentName, - lookupFunctionArgumentBitRatio, - needsArgumentType, - assignArgumentType, - triggerImplyArgumentType, - triggerImplyArgumentBitRatio, - onFunctionCall, - optimizeFloatMemory, - precision, - constants, - constantTypes, - constantBitRatios, - debug, - loopMaxIterations, - output, - plugins, - dynamicArguments, - dynamicOutput, - }, extraNodeOptions || {}); - - const rootNodeOptions = Object.assign({}, nodeOptions, { - isRootKernel: true, - name: 'kernel', - argumentNames, - argumentTypes, - argumentSizes, - argumentBitRatios, - leadingReturnStatement, - followingReturnStatement, - }); - - if (typeof source === 'object' && source.functionNodes) { - return new FunctionBuilder().fromJSON(source.functionNodes, FunctionNode); - } - - const rootNode = new FunctionNode(source, rootNodeOptions); - - let functionNodes = null; - if (functions) { - functionNodes = functions.map((fn) => new FunctionNode(fn.source, { - returnType: fn.returnType, - argumentTypes: fn.argumentTypes, - output, - plugins, - constants, - constantTypes, - constantBitRatios, - optimizeFloatMemory, - precision, - lookupReturnType, - lookupFunctionArgumentTypes, - lookupFunctionArgumentName, - lookupFunctionArgumentBitRatio, - needsArgumentType, - assignArgumentType, - triggerImplyArgumentType, - triggerImplyArgumentBitRatio, - onFunctionCall, - onNestedFunction, - })); - } - - let subKernelNodes = null; - if (subKernels) { - subKernelNodes = subKernels.map((subKernel) => { - const { name, source } = subKernel; - return new FunctionNode(source, Object.assign({}, nodeOptions, { - name, - isSubKernel: true, - isRootKernel: false, - })); - }); - } - - const functionBuilder = new FunctionBuilder({ - kernel, - rootNode, - functionNodes, - nativeFunctions, - subKernelNodes - }); - - return functionBuilder; - } - - /** - * - * @param {IFunctionBuilderSettings} [settings] - */ - constructor(settings) { - settings = settings || {}; - this.kernel = settings.kernel; - this.rootNode = settings.rootNode; - this.functionNodes = settings.functionNodes || []; - this.subKernelNodes = settings.subKernelNodes || []; - this.nativeFunctions = settings.nativeFunctions || []; - this.functionMap = {}; - this.nativeFunctionNames = []; - this.lookupChain = []; - this.argumentChain = []; - this.functionNodeDependencies = {}; - this.functionCalls = {}; - - if (this.rootNode) { - this.functionMap['kernel'] = this.rootNode; - } - - if (this.functionNodes) { - for (let i = 0; i < this.functionNodes.length; i++) { - this.functionMap[this.functionNodes[i].name] = this.functionNodes[i]; - } - } - - if (this.subKernelNodes) { - for (let i = 0; i < this.subKernelNodes.length; i++) { - this.functionMap[this.subKernelNodes[i].name] = this.subKernelNodes[i]; - } - } - - if (this.nativeFunctions) { - for (let i = 0; i < this.nativeFunctions.length; i++) { - const nativeFunction = this.nativeFunctions[i]; - this.nativeFunctionNames.push(nativeFunction.name); - } - } - } - - /** - * @desc Add the function node directly - * - * @param {FunctionNode} functionNode - functionNode to add - * - */ - addFunctionNode(functionNode) { - if (!functionNode.name) throw new Error('functionNode.name needs set'); - this.functionMap[functionNode.name] = functionNode; - if (functionNode.isRootKernel) { - this.rootNode = functionNode; - } - } - - /** - * @desc Trace all the depending functions being called, from a single function - * - * This allow for 'unneeded' functions to be automatically optimized out. - * Note that the 0-index, is the starting function trace. - * - * @param {String} functionName - Function name to trace from, default to 'kernel' - * @param {String[]} [retList] - Returning list of function names that is traced. Including itself. - * - * @returns {String[]} Returning list of function names that is traced. Including itself. - */ - traceFunctionCalls(functionName, retList) { - functionName = functionName || 'kernel'; - retList = retList || []; - - if (this.nativeFunctionNames.indexOf(functionName) > -1) { - if (retList.indexOf(functionName) === -1) { - retList.push(functionName); - } - return retList; - } - - const functionNode = this.functionMap[functionName]; - if (functionNode) { - // Check if function already exists - const functionIndex = retList.indexOf(functionName); - if (functionIndex === -1) { - retList.push(functionName); - functionNode.toString(); //ensure JS trace is done - for (let i = 0; i < functionNode.calledFunctions.length; ++i) { - this.traceFunctionCalls(functionNode.calledFunctions[i], retList); - } - } else { - /** - * https://github.com/gpujs/gpu.js/issues/207 - * if dependent function is already in the list, because a function depends on it, and because it has - * already been traced, we know that we must move the dependent function to the end of the the retList. - * */ - const dependantFunctionName = retList.splice(functionIndex, 1)[0]; - retList.push(dependantFunctionName); - } - } - - return retList; - } - - /** - * @desc Return the string for a function - * @param {String} functionName - Function name to trace from. If null, it returns the WHOLE builder stack - * @returns {String} The full string, of all the various functions. Trace optimized if functionName given - */ - getPrototypeString(functionName) { - return this.getPrototypes(functionName).join('\n'); - } - - /** - * @desc Return the string for a function - * @param {String} [functionName] - Function name to trace from. If null, it returns the WHOLE builder stack - * @returns {Array} The full string, of all the various functions. Trace optimized if functionName given - */ - getPrototypes(functionName) { - if (this.rootNode) { - this.rootNode.toString(); - } - if (functionName) { - return this.getPrototypesFromFunctionNames(this.traceFunctionCalls(functionName, []).reverse()); - } - return this.getPrototypesFromFunctionNames(Object.keys(this.functionMap)); - } - - /** - * @desc Get string from function names - * @param {String[]} functionList - List of function to build string - * @returns {String} The string, of all the various functions. Trace optimized if functionName given - */ - getStringFromFunctionNames(functionList) { - const ret = []; - for (let i = 0; i < functionList.length; ++i) { - const node = this.functionMap[functionList[i]]; - if (node) { - ret.push(this.functionMap[functionList[i]].toString()); - } - } - return ret.join('\n'); - } - - /** - * @desc Return string of all functions converted - * @param {String[]} functionList - List of function names to build the string. - * @returns {Array} Prototypes of all functions converted - */ - getPrototypesFromFunctionNames(functionList) { - const ret = []; - for (let i = 0; i < functionList.length; ++i) { - const functionName = functionList[i]; - const functionIndex = this.nativeFunctionNames.indexOf(functionName); - if (functionIndex > -1) { - ret.push(this.nativeFunctions[functionIndex].source); - continue; - } - const node = this.functionMap[functionName]; - if (node) { - ret.push(node.toString()); - } - } - return ret; - } - - toJSON() { - return this.traceFunctionCalls(this.rootNode.name).reverse().map(name => { - const nativeIndex = this.nativeFunctions.indexOf(name); - if (nativeIndex > -1) { - return { - name, - source: this.nativeFunctions[nativeIndex].source - }; - } else if (this.functionMap[name]) { - return this.functionMap[name].toJSON(); - } else { - throw new Error(`function ${ name } not found`); - } - }); - } - - fromJSON(jsonFunctionNodes, FunctionNode) { - this.functionMap = {}; - for (let i = 0; i < jsonFunctionNodes.length; i++) { - const jsonFunctionNode = jsonFunctionNodes[i]; - this.functionMap[jsonFunctionNode.settings.name] = new FunctionNode(jsonFunctionNode.ast, jsonFunctionNode.settings); - } - return this; - } - - /** - * @desc Get string for a particular function name - * @param {String} functionName - Function name to trace from. If null, it returns the WHOLE builder stack - * @returns {String} settings - The string, of all the various functions. Trace optimized if functionName given - */ - getString(functionName) { - if (functionName) { - return this.getStringFromFunctionNames(this.traceFunctionCalls(functionName).reverse()); - } - return this.getStringFromFunctionNames(Object.keys(this.functionMap)); - } - - lookupReturnType(functionName, ast, requestingNode) { - if (ast.type !== 'CallExpression') { - throw new Error(`expected ast type of "CallExpression", but is ${ ast.type }`); - } - if (this._isNativeFunction(functionName)) { - return this._lookupNativeFunctionReturnType(functionName); - } else if (this._isFunction(functionName)) { - const node = this._getFunction(functionName); - if (node.returnType) { - return node.returnType; - } else { - for (let i = 0; i < this.lookupChain.length; i++) { - // detect circlical logic - if (this.lookupChain[i].ast === ast) { - // detect if arguments have not resolved, preventing a return type - // if so, go ahead and resolve them, so we can resolve the return type - if (node.argumentTypes.length === 0 && ast.arguments.length > 0) { - const args = ast.arguments; - for (let j = 0; j < args.length; j++) { - this.lookupChain.push({ - name: requestingNode.name, - ast: args[i], - requestingNode - }); - node.argumentTypes[j] = requestingNode.getType(args[j]); - this.lookupChain.pop(); - } - return node.returnType = node.getType(node.getJsAST()); - } - - throw new Error('circlical logic detected!'); - } - } - // get ready for a ride! - this.lookupChain.push({ - name: requestingNode.name, - ast, - requestingNode - }); - const type = node.getType(node.getJsAST()); - this.lookupChain.pop(); - return node.returnType = type; - } - } - - return null; - } - - /** - * - * @param {String} functionName - * @return {FunctionNode} - * @private - */ - _getFunction(functionName) { - if (!this._isFunction(functionName)) { - new Error(`Function ${functionName} not found`); - } - return this.functionMap[functionName]; - } - - _isFunction(functionName) { - return Boolean(this.functionMap[functionName]); - } - - _getNativeFunction(functionName) { - for (let i = 0; i < this.nativeFunctions.length; i++) { - if (this.nativeFunctions[i].name === functionName) return this.nativeFunctions[i]; - } - return null; - } - - _isNativeFunction(functionName) { - return Boolean(this._getNativeFunction(functionName)); - } - - _lookupNativeFunctionReturnType(functionName) { - let nativeFunction = this._getNativeFunction(functionName); - if (nativeFunction) { - return nativeFunction.returnType; - } - throw new Error(`Native function ${ functionName } not found`); - } - - lookupFunctionArgumentTypes(functionName) { - if (this._isNativeFunction(functionName)) { - return this._getNativeFunction(functionName).argumentTypes; - } else if (this._isFunction(functionName)) { - return this._getFunction(functionName).argumentTypes; - } - return null; - } - - lookupFunctionArgumentName(functionName, argumentIndex) { - return this._getFunction(functionName).argumentNames[argumentIndex]; - } - - /** - * - * @param {string} functionName - * @param {string} argumentName - * @return {number} - */ - lookupFunctionArgumentBitRatio(functionName, argumentName) { - if (!this._isFunction(functionName)) { - throw new Error('function not found'); - } - if (this.rootNode.name === functionName) { - const i = this.rootNode.argumentNames.indexOf(argumentName); - if (i !== -1) { - return this.rootNode.argumentBitRatios[i]; - } - } - const node = this._getFunction(functionName); - const i = node.argumentNames.indexOf(argumentName); - if (i === -1) { - throw new Error('argument not found'); - } - const bitRatio = node.argumentBitRatios[i]; - if (typeof bitRatio !== 'number') { - throw new Error('argument bit ratio not found'); - } - return bitRatio; - } - - needsArgumentType(functionName, i) { - if (!this._isFunction(functionName)) return false; - const fnNode = this._getFunction(functionName); - return !fnNode.argumentTypes[i]; - } - - assignArgumentType(functionName, i, argumentType, requestingNode) { - if (!this._isFunction(functionName)) return; - const fnNode = this._getFunction(functionName); - if (!fnNode.argumentTypes[i]) { - fnNode.argumentTypes[i] = argumentType; - } - } - - /** - * @param {string} functionName - * @param {string} argumentName - * @param {string} calleeFunctionName - * @param {number} argumentIndex - * @return {number|null} - */ - assignArgumentBitRatio(functionName, argumentName, calleeFunctionName, argumentIndex) { - const node = this._getFunction(functionName); - if (this._isNativeFunction(calleeFunctionName)) return null; - const calleeNode = this._getFunction(calleeFunctionName); - const i = node.argumentNames.indexOf(argumentName); - if (i === -1) { - throw new Error(`Argument ${argumentName} not found in arguments from function ${functionName}`); - } - const bitRatio = node.argumentBitRatios[i]; - if (typeof bitRatio !== 'number') { - throw new Error(`Bit ratio for argument ${argumentName} not found in function ${functionName}`); - } - if (!calleeNode.argumentBitRatios) { - calleeNode.argumentBitRatios = new Array(calleeNode.argumentNames.length); - } - const calleeBitRatio = calleeNode.argumentBitRatios[i]; - if (typeof calleeBitRatio === 'number') { - if (calleeBitRatio !== bitRatio) { - throw new Error(`Incompatible bit ratio found at function ${functionName} at argument ${argumentName}`); - } - return calleeBitRatio; - } - calleeNode.argumentBitRatios[i] = bitRatio; - return bitRatio; - } - - trackFunctionCall(functionName, calleeFunctionName, args) { - if (!this.functionNodeDependencies[functionName]) { - this.functionNodeDependencies[functionName] = new Set(); - this.functionCalls[functionName] = []; - } - this.functionNodeDependencies[functionName].add(calleeFunctionName); - this.functionCalls[functionName].push(args); - } - - getKernelResultType() { - return this.rootNode.returnType || this.rootNode.getType(this.rootNode.ast); - } - - getSubKernelResultType(index) { - const subKernelNode = this.subKernelNodes[index]; - let called = false; - for (let functionCallIndex = 0; functionCallIndex < this.rootNode.functionCalls.length; functionCallIndex++) { - const functionCall = this.rootNode.functionCalls[functionCallIndex]; - if (functionCall.ast.callee.name === subKernelNode.name) { - called = true; - } - } - if (!called) { - throw new Error(`SubKernel ${ subKernelNode.name } never called by kernel`); - } - return subKernelNode.returnType || subKernelNode.getType(subKernelNode.getJsAST()); - } - - getReturnTypes() { - const result = { - [this.rootNode.name]: this.rootNode.getType(this.rootNode.ast), - }; - const list = this.traceFunctionCalls(this.rootNode.name); - for (let i = 0; i < list.length; i++) { - const functionName = list[i]; - const functionNode = this.functionMap[functionName]; - result[functionName] = functionNode.getType(functionNode.ast); - } - return result; - } -} - -module.exports = { - FunctionBuilder -}; \ No newline at end of file +/** + * @desc This handles all the raw state, converted state, etc. of a single function. + * [INTERNAL] A collection of functionNodes. + * @class + */ +export class FunctionBuilder { + /** + * + * @param {Kernel} kernel + * @param {FunctionNode} FunctionNode + * @param {object} [extraNodeOptions] + * @returns {FunctionBuilder} + * @static + */ + static fromKernel(kernel, FunctionNode, extraNodeOptions) { + const { + kernelArguments, + kernelConstants, + argumentNames, + argumentSizes, + argumentBitRatios, + constants, + constantBitRatios, + debug, + loopMaxIterations, + nativeFunctions, + output, + optimizeFloatMemory, + precision, + plugins, + source, + subKernels, + functions, + leadingReturnStatement, + followingReturnStatement, + dynamicArguments, + dynamicOutput, + warnVarUsage, + } = kernel; + + const argumentTypes = new Array(kernelArguments.length); + const constantTypes = {}; + + for (let i = 0; i < kernelArguments.length; i++) { + argumentTypes[i] = kernelArguments[i].type; + } + + for (let i = 0; i < kernelConstants.length; i++) { + const kernelConstant = kernelConstants[i] + constantTypes[kernelConstant.name] = kernelConstant.type; + } + + const needsArgumentType = (functionName, index) => { + return functionBuilder.needsArgumentType(functionName, index); + }; + + const assignArgumentType = (functionName, index, type) => { + functionBuilder.assignArgumentType(functionName, index, type); + }; + + const lookupReturnType = (functionName, ast, requestingNode) => { + return functionBuilder.lookupReturnType(functionName, ast, requestingNode); + }; + + const lookupFunctionArgumentTypes = (functionName) => { + return functionBuilder.lookupFunctionArgumentTypes(functionName); + }; + + const lookupFunctionArgumentName = (functionName, argumentIndex) => { + return functionBuilder.lookupFunctionArgumentName(functionName, argumentIndex); + }; + + const lookupFunctionArgumentBitRatio = (functionName, argumentName) => { + return functionBuilder.lookupFunctionArgumentBitRatio(functionName, argumentName); + }; + + const triggerImplyArgumentType = (functionName, i, argumentType, requestingNode) => { + functionBuilder.assignArgumentType(functionName, i, argumentType, requestingNode); + }; + + const triggerTrackArgumentSynonym = (functionName, argumentName, calleeFunctionName, argumentIndex) => { + functionBuilder.trackArgumentSynonym(functionName, argumentName, calleeFunctionName, argumentIndex); + }; + + const lookupArgumentSynonym = (originFunctionName, functionName, argumentName) => { + return functionBuilder.lookupArgumentSynonym(originFunctionName, functionName, argumentName); + }; + + const onFunctionCall = (functionName, calleeFunctionName, args) => { + functionBuilder.trackFunctionCall(functionName, calleeFunctionName, args); + }; + + const onNestedFunction = (ast, returnType) => { + const argumentNames = []; + for (let i = 0; i < ast.params.length; i++) { + argumentNames.push(ast.params[i].name); + } + const nestedFunction = new FunctionNode(null, Object.assign({}, nodeOptions, { + returnType: null, + ast, + name: ast.id.name, + argumentNames, + lookupReturnType, + lookupFunctionArgumentTypes, + lookupFunctionArgumentName, + lookupFunctionArgumentBitRatio, + needsArgumentType, + assignArgumentType, + triggerImplyArgumentType, + triggerTrackArgumentSynonym, + lookupArgumentSynonym, + onFunctionCall, + warnVarUsage, + })); + nestedFunction.traceFunctionAST(ast); + functionBuilder.addFunctionNode(nestedFunction); + }; + + const nodeOptions = Object.assign({ + isRootKernel: false, + onNestedFunction, + lookupReturnType, + lookupFunctionArgumentTypes, + lookupFunctionArgumentName, + lookupFunctionArgumentBitRatio, + needsArgumentType, + assignArgumentType, + triggerImplyArgumentType, + triggerTrackArgumentSynonym, + lookupArgumentSynonym, + onFunctionCall, + optimizeFloatMemory, + precision, + constants, + constantTypes, + constantBitRatios, + debug, + loopMaxIterations, + output, + plugins, + dynamicArguments, + dynamicOutput, + }, extraNodeOptions || {}); + + const rootNodeOptions = Object.assign({}, nodeOptions, { + isRootKernel: true, + name: 'kernel', + argumentNames, + argumentTypes, + argumentSizes, + argumentBitRatios, + leadingReturnStatement, + followingReturnStatement, + }); + + if (typeof source === 'object' && source.functionNodes) { + return new FunctionBuilder().fromJSON(source.functionNodes, FunctionNode); + } + + const rootNode = new FunctionNode(source, rootNodeOptions); + + let functionNodes = null; + if (functions) { + functionNodes = functions.map((fn) => new FunctionNode(fn.source, { + returnType: fn.returnType, + argumentTypes: fn.argumentTypes, + output, + plugins, + constants, + constantTypes, + constantBitRatios, + optimizeFloatMemory, + precision, + lookupReturnType, + lookupFunctionArgumentTypes, + lookupFunctionArgumentName, + lookupFunctionArgumentBitRatio, + needsArgumentType, + assignArgumentType, + triggerImplyArgumentType, + triggerTrackArgumentSynonym, + lookupArgumentSynonym, + onFunctionCall, + })); + } + + let subKernelNodes = null; + if (subKernels) { + subKernelNodes = subKernels.map((subKernel) => { + const { name, source } = subKernel; + return new FunctionNode(source, Object.assign({}, nodeOptions, { + name, + isSubKernel: true, + isRootKernel: false, + })); + }); + } + + const functionBuilder = new FunctionBuilder({ + kernel, + rootNode, + functionNodes, + nativeFunctions, + subKernelNodes + }); + + return functionBuilder; + } + + /** + * + * @param {IFunctionBuilderSettings} [settings] + */ + constructor(settings) { + settings = settings || {}; + this.kernel = settings.kernel; + this.rootNode = settings.rootNode; + this.functionNodes = settings.functionNodes || []; + this.subKernelNodes = settings.subKernelNodes || []; + this.nativeFunctions = settings.nativeFunctions || []; + this.functionMap = {}; + this.nativeFunctionNames = []; + this.lookupChain = []; + this.argumentChain = []; + this.functionNodeDependencies = {}; + this.functionCalls = {}; + + if (this.rootNode) { + this.functionMap['kernel'] = this.rootNode; + } + + if (this.functionNodes) { + for (let i = 0; i < this.functionNodes.length; i++) { + this.functionMap[this.functionNodes[i].name] = this.functionNodes[i]; + } + } + + if (this.subKernelNodes) { + for (let i = 0; i < this.subKernelNodes.length; i++) { + this.functionMap[this.subKernelNodes[i].name] = this.subKernelNodes[i]; + } + } + + if (this.nativeFunctions) { + for (let i = 0; i < this.nativeFunctions.length; i++) { + const nativeFunction = this.nativeFunctions[i]; + this.nativeFunctionNames.push(nativeFunction.name); + } + } + } + + /** + * @desc Add the function node directly + * + * @param {FunctionNode} functionNode - functionNode to add + * + */ + addFunctionNode(functionNode) { + if (!functionNode.name) throw new Error('functionNode.name needs set'); + this.functionMap[functionNode.name] = functionNode; + if (functionNode.isRootKernel) { + this.rootNode = functionNode; + } + } + + /** + * @desc Trace all the depending functions being called, from a single function + * + * This allow for 'unneeded' functions to be automatically optimized out. + * Note that the 0-index, is the starting function trace. + * + * @param {String} functionName - Function name to trace from, default to 'kernel' + * @param {String[]} [retList] - Returning list of function names that is traced. Including itself. + * + * @returns {String[]} Returning list of function names that is traced. Including itself. + */ + traceFunctionCalls(functionName, retList) { + functionName = functionName || 'kernel'; + retList = retList || []; + + if (this.nativeFunctionNames.indexOf(functionName) > -1) { + if (retList.indexOf(functionName) === -1) { + retList.push(functionName); + } + return retList; + } + + const functionNode = this.functionMap[functionName]; + if (functionNode) { + // Check if function already exists + const functionIndex = retList.indexOf(functionName); + if (functionIndex === -1) { + retList.push(functionName); + functionNode.toString(); //ensure JS trace is done + for (let i = 0; i < functionNode.calledFunctions.length; ++i) { + this.traceFunctionCalls(functionNode.calledFunctions[i], retList); + } + } else { + /** + * https://github.com/gpujs/gpu.js/issues/207 + * if dependent function is already in the list, because a function depends on it, and because it has + * already been traced, we know that we must move the dependent function to the end of the the retList. + * */ + const dependantFunctionName = retList.splice(functionIndex, 1)[0]; + retList.push(dependantFunctionName); + } + } + + return retList; + } + + /** + * @desc Return the string for a function + * @param {String} functionName - Function name to trace from. If null, it returns the WHOLE builder stack + * @returns {String} The full string, of all the various functions. Trace optimized if functionName given + */ + getPrototypeString(functionName) { + return this.getPrototypes(functionName).join('\n'); + } + + /** + * @desc Return the string for a function + * @param {String} [functionName] - Function name to trace from. If null, it returns the WHOLE builder stack + * @returns {Array} The full string, of all the various functions. Trace optimized if functionName given + */ + getPrototypes(functionName) { + if (this.rootNode) { + this.rootNode.toString(); + } + if (functionName) { + return this.getPrototypesFromFunctionNames(this.traceFunctionCalls(functionName, []).reverse()); + } + return this.getPrototypesFromFunctionNames(Object.keys(this.functionMap)); + } + + /** + * @desc Get string from function names + * @param {String[]} functionList - List of function to build string + * @returns {String} The string, of all the various functions. Trace optimized if functionName given + */ + getStringFromFunctionNames(functionList) { + const ret = []; + for (let i = 0; i < functionList.length; ++i) { + const node = this.functionMap[functionList[i]]; + if (node) { + ret.push(this.functionMap[functionList[i]].toString()); + } + } + return ret.join('\n'); + } + + /** + * @desc Return string of all functions converted + * @param {String[]} functionList - List of function names to build the string. + * @returns {Array} Prototypes of all functions converted + */ + getPrototypesFromFunctionNames(functionList) { + const ret = []; + for (let i = 0; i < functionList.length; ++i) { + const functionName = functionList[i]; + const functionIndex = this.nativeFunctionNames.indexOf(functionName); + if (functionIndex > -1) { + ret.push(this.nativeFunctions[functionIndex].source); + continue; + } + const node = this.functionMap[functionName]; + if (node) { + ret.push(node.toString()); + } + } + return ret; + } + + toJSON() { + return this.traceFunctionCalls(this.rootNode.name).reverse().map(name => { + const nativeIndex = this.nativeFunctions.indexOf(name); + if (nativeIndex > -1) { + return { + name, + source: this.nativeFunctions[nativeIndex].source + }; + } else if (this.functionMap[name]) { + return this.functionMap[name].toJSON(); + } else { + throw new Error(`function ${ name } not found`); + } + }); + } + + fromJSON(jsonFunctionNodes, FunctionNode) { + this.functionMap = {}; + for (let i = 0; i < jsonFunctionNodes.length; i++) { + const jsonFunctionNode = jsonFunctionNodes[i]; + this.functionMap[jsonFunctionNode.settings.name] = new FunctionNode(jsonFunctionNode.ast, jsonFunctionNode.settings); + } + return this; + } + + /** + * @desc Get string for a particular function name + * @param {String} functionName - Function name to trace from. If null, it returns the WHOLE builder stack + * @returns {String} settings - The string, of all the various functions. Trace optimized if functionName given + */ + getString(functionName) { + if (functionName) { + return this.getStringFromFunctionNames(this.traceFunctionCalls(functionName).reverse()); + } + return this.getStringFromFunctionNames(Object.keys(this.functionMap)); + } + + lookupReturnType(functionName, ast, requestingNode) { + if (ast.type !== 'CallExpression') { + throw new Error(`expected ast type of "CallExpression", but is ${ ast.type }`); + } + if (this._isNativeFunction(functionName)) { + return this._lookupNativeFunctionReturnType(functionName); + } else if (this._isFunction(functionName)) { + const node = this._getFunction(functionName); + if (node.returnType) { + return node.returnType; + } else { + for (let i = 0; i < this.lookupChain.length; i++) { + // detect circlical logic + if (this.lookupChain[i].ast === ast) { + // detect if arguments have not resolved, preventing a return type + // if so, go ahead and resolve them, so we can resolve the return type + if (node.argumentTypes.length === 0 && ast.arguments.length > 0) { + const args = ast.arguments; + for (let j = 0; j < args.length; j++) { + this.lookupChain.push({ + name: requestingNode.name, + ast: args[i], + requestingNode + }); + node.argumentTypes[j] = requestingNode.getType(args[j]); + this.lookupChain.pop(); + } + return node.returnType = node.getType(node.getJsAST()); + } + + throw new Error('circlical logic detected!'); + } + } + // get ready for a ride! + this.lookupChain.push({ + name: requestingNode.name, + ast, + requestingNode + }); + const type = node.getType(node.getJsAST()); + this.lookupChain.pop(); + return node.returnType = type; + } + } + + // function not found, maybe native? + return null; + + /** + * first iteration + * kernel.outputs = Array + * kernel.targets = Array + * kernel.returns = null + * kernel.calls.calcErrorOutput = [kernel.output, kernel.targets] + * kernel.calls.calcDeltas = [calcErrorOutput.returns, kernel.output] + * calcErrorOutput.output = null + * calcErrorOutput.targets = null + * calcErrorOutput.returns = null + * calcDeltasSigmoid.error = null + * calcDeltasSigmoid.output = Number + * calcDeltasSigmoid.returns = null + * + * resolvable are: + * calcErrorOutput.output + * calcErrorOutput.targets + * calcErrorOutput.returns + * + * second iteration + * kernel.outputs = Array + * kernel.targets = Array + * kernel.returns = null + * kernel.calls.calcErrorOutput = [kernel.output, kernel.targets] + * kernel.calls.calcDeltas = [calcErrorOutput.returns, kernel.output] + * calcErrorOutput.output = Number + * calcErrorOutput.targets = Array + * calcErrorOutput.returns = Number + * calcDeltasSigmoid.error = null + * calcDeltasSigmoid.output = Number + * calcDeltasSigmoid.returns = null + * + * resolvable are: + * calcDeltasSigmoid.error + * calcDeltasSigmoid.returns + * kernel.returns + * + * third iteration + * kernel.outputs = Array + * kernel.targets = Array + * kernel.returns = Number + * kernel.calls.calcErrorOutput = [kernel.output, kernel.targets] + * kernel.calls.calcDeltas = [calcErrorOutput.returns, kernel.output] + * calcErrorOutput.output = Number + * calcErrorOutput.targets = Array + * calcErrorOutput.returns = Number + * calcDeltasSigmoid.error = Number + * calcDeltasSigmoid.output = Number + * calcDeltasSigmoid.returns = Number + * + * + */ + } + + _getFunction(functionName) { + if (!this._isFunction(functionName)) { + new Error(`Function ${functionName} not found`); + } + return this.functionMap[functionName]; + } + + _isFunction(functionName) { + return Boolean(this.functionMap[functionName]); + } + + _getNativeFunction(functionName) { + for (let i = 0; i < this.nativeFunctions.length; i++) { + if (this.nativeFunctions[i].name === functionName) return this.nativeFunctions[i]; + } + return null; + } + + _isNativeFunction(functionName) { + return Boolean(this._getNativeFunction(functionName)); + } + + _lookupNativeFunctionReturnType(functionName) { + let nativeFunction = this._getNativeFunction(functionName); + if (nativeFunction) { + return nativeFunction.returnType; + } + throw new Error(`Native function ${ functionName } not found`); + } + + lookupFunctionArgumentTypes(functionName) { + if (this._isNativeFunction(functionName)) { + return this._getNativeFunction(functionName).argumentTypes; + } else if (this._isFunction(functionName)) { + return this._getFunction(functionName).argumentTypes; + } + return null; + } + + lookupFunctionArgumentName(functionName, argumentIndex) { + return this._getFunction(functionName).argumentNames[argumentIndex]; + } + + lookupFunctionArgumentBitRatio(functionName, argumentName) { + if (!this._isFunction(functionName)) { + throw new Error('function not found'); + } + if (this.rootNode.name === functionName) { + const i = this.rootNode.argumentNames.indexOf(argumentName); + if (i !== -1) { + return this.rootNode.argumentBitRatios[i]; + } else { + throw new Error('argument bit ratio not found'); + } + } else { + const node = this._getFunction(functionName); + const argumentSynonym = node.argumentSynonym[node.synonymIndex]; + if (!argumentSynonym) { + throw new Error('argument synonym not found'); + } + return this.lookupFunctionArgumentBitRatio(argumentSynonym.functionName, argumentSynonym.argumentName); + } + } + + needsArgumentType(functionName, i) { + if (!this._isFunction(functionName)) return false; + const fnNode = this._getFunction(functionName); + return !fnNode.argumentTypes[i]; + } + + assignArgumentType(functionName, i, argumentType, requestingNode) { + if (!this._isFunction(functionName)) return; + const fnNode = this._getFunction(functionName); + if (!fnNode.argumentTypes[i]) { + fnNode.argumentTypes[i] = argumentType; + } + } + + trackArgumentSynonym(functionName, argumentName, calleeFunctionName, argumentIndex) { + if (!this._isFunction(calleeFunctionName)) return; + const node = this._getFunction(calleeFunctionName); + if (!node.argumentSynonym) { + node.argumentSynonym = {}; + } + const calleeArgumentName = node.argumentNames[argumentIndex]; + if (!node.argumentSynonym[calleeArgumentName]) { + node.argumentSynonym[calleeArgumentName] = {}; + } + node.synonymIndex++; + node.argumentSynonym[node.synonymIndex] = { + functionName, + argumentName, + calleeArgumentName, + calleeFunctionName, + }; + } + + lookupArgumentSynonym(originFunctionName, functionName, argumentName) { + if (originFunctionName === functionName) return argumentName; + if (!this._isFunction(functionName)) return null; + const node = this._getFunction(functionName); + const argumentSynonym = node.argumentSynonym[node.synonymUseIndex]; + if (!argumentSynonym) return null; + if (argumentSynonym.calleeArgumentName !== argumentName) return null; + node.synonymUseIndex++; + if (originFunctionName !== functionName) { + return this.lookupArgumentSynonym(originFunctionName, argumentSynonym.functionName, argumentSynonym.argumentName); + } + return argumentSynonym.argumentName; + } + + trackFunctionCall(functionName, calleeFunctionName, args) { + if (!this.functionNodeDependencies[functionName]) { + this.functionNodeDependencies[functionName] = new Set(); + this.functionCalls[functionName] = []; + } + this.functionNodeDependencies[functionName].add(calleeFunctionName); + this.functionCalls[functionName].push(args); + } + + getKernelResultType() { + return this.rootNode.returnType || this.rootNode.getType(this.rootNode.ast); + } + + getSubKernelResultType(index) { + const subKernelNode = this.subKernelNodes[index]; + let called = false; + for (let functionCallIndex = 0; functionCallIndex < this.rootNode.functionCalls.length; functionCallIndex++) { + const functionCall = this.rootNode.functionCalls[functionCallIndex]; + if (functionCall.ast.callee.name === subKernelNode.name) { + called = true; + } + } + if (!called) { + throw new Error(`SubKernel ${ subKernelNode.name } never called by kernel`); + } + return subKernelNode.returnType || subKernelNode.getType(subKernelNode.getJsAST()); + } + + getReturnTypes() { + const result = { + [this.rootNode.name]: this.rootNode.getType(this.rootNode.ast), + }; + const list = this.traceFunctionCalls(this.rootNode.name); + for (let i = 0; i < list.length; i++) { + const functionName = list[i]; + const functionNode = this.functionMap[functionName]; + result[functionName] = functionNode.getType(functionNode.ast); + } + return result; + } +} diff --git a/src/backend/function-node.js b/src/backend/function-node.js index 8ffd1da9..14b39132 100644 --- a/src/backend/function-node.js +++ b/src/backend/function-node.js @@ -1,1509 +1,1510 @@ -const acorn = require('acorn'); -const { utils } = require('../utils'); -const { FunctionTracer } = require('./function-tracer'); - -/** - * - * @desc Represents a single function, inside JS, webGL, or openGL. - *This handles all the raw state, converted state, etc. Of a single function.
- */ -class FunctionNode { - /** - * - * @param {string|object} source - * @param {IFunctionSettings} [settings] - */ - constructor(source, settings) { - if (!source && !settings.ast) { - throw new Error('source parameter is missing'); - } - settings = settings || {}; - this.source = source; - this.ast = null; - this.name = typeof source === 'string' ? settings.isRootKernel ? - 'kernel' : - (settings.name || utils.getFunctionNameFromString(source)) : null; - this.calledFunctions = []; - this.constants = {}; - this.constantTypes = {}; - this.constantBitRatios = {}; - this.isRootKernel = false; - this.isSubKernel = false; - this.debug = null; - this.declarations = null; - this.functions = null; - this.identifiers = null; - this.contexts = null; - this.functionCalls = null; - this.states = []; - this.needsArgumentType = null; - this.assignArgumentType = null; - this.lookupReturnType = null; - this.lookupFunctionArgumentTypes = null; - this.lookupFunctionArgumentBitRatio = null; - this.triggerImplyArgumentType = null; - this.triggerImplyArgumentBitRatio = null; - this.onNestedFunction = null; - this.onFunctionCall = null; - this.optimizeFloatMemory = null; - this.precision = null; - this.loopMaxIterations = null; - this.argumentNames = (typeof this.source === 'string' ? utils.getArgumentNamesFromString(this.source) : null); - this.argumentTypes = []; - this.argumentSizes = []; - this.argumentBitRatios = null; - this.returnType = null; - this.output = []; - this.plugins = null; - this.leadingReturnStatement = null; - this.followingReturnStatement = null; - this.dynamicOutput = null; - this.dynamicArguments = null; - this.strictTypingChecking = false; - this.fixIntegerDivisionAccuracy = null; - this.warnVarUsage = true; - - if (settings) { - for (const p in settings) { - if (!settings.hasOwnProperty(p)) continue; - if (!this.hasOwnProperty(p)) continue; - this[p] = settings[p]; - } - } - - this.literalTypes = {}; - - this.validate(); - this._string = null; - this._internalVariableNames = {}; - } - - validate() { - if (typeof this.source !== 'string' && !this.ast) { - throw new Error('this.source not a string'); - } - - if (!this.ast && !utils.isFunctionString(this.source)) { - throw new Error('this.source not a function string'); - } - - if (!this.name) { - throw new Error('this.name could not be set'); - } - - if (this.argumentTypes.length > 0 && this.argumentTypes.length !== this.argumentNames.length) { - throw new Error(`argumentTypes count of ${ this.argumentTypes.length } exceeds ${ this.argumentNames.length }`); - } - - if (this.output.length < 1) { - throw new Error('this.output is not big enough'); - } - } - - /** - * @param {String} name - * @returns {boolean} - */ - isIdentifierConstant(name) { - if (!this.constants) return false; - return this.constants.hasOwnProperty(name); - } - - isInput(argumentName) { - return this.argumentTypes[this.argumentNames.indexOf(argumentName)] === 'Input'; - } - - pushState(state) { - this.states.push(state); - } - - popState(state) { - if (this.state !== state) { - throw new Error(`Cannot popState ${ state } when in ${ this.state }`); - } - this.states.pop(); - } - - isState(state) { - return this.state === state; - } - - get state() { - return this.states[this.states.length - 1]; - } - - /** - * @function - * @name astMemberExpressionUnroll - * @desc Parses the abstract syntax tree for binary expression. - * - *Utility function for astCallExpression.
- * - * @param {Object} ast - the AST object to parse - * - * @returns {String} the function namespace call, unrolled - */ - astMemberExpressionUnroll(ast) { - if (ast.type === 'Identifier') { - return ast.name; - } else if (ast.type === 'ThisExpression') { - return 'this'; - } - - if (ast.type === 'MemberExpression') { - if (ast.object && ast.property) { - //babel sniffing - if (ast.object.hasOwnProperty('name') && ast.object.name[0] === '_') { - return this.astMemberExpressionUnroll(ast.property); - } - - return ( - this.astMemberExpressionUnroll(ast.object) + - '.' + - this.astMemberExpressionUnroll(ast.property) - ); - } - } - - //babel sniffing - if (ast.hasOwnProperty('expressions')) { - const firstExpression = ast.expressions[0]; - if (firstExpression.type === 'Literal' && firstExpression.value === 0 && ast.expressions.length === 2) { - return this.astMemberExpressionUnroll(ast.expressions[1]); - } - } - - // Failure, unknown expression - throw this.astErrorOutput('Unknown astMemberExpressionUnroll', ast); - } - - /** - * @desc Parses the class function JS, and returns its Abstract Syntax Tree object. - * This is used internally to convert to shader code - * - * @param {Object} [inParser] - Parser to use, assumes in scope 'parser' if null or undefined - * - * @returns {Object} The function AST Object, note that result is cached under this.ast; - */ - getJsAST(inParser) { - if (this.ast) { - return this.ast; - } - if (typeof this.source === 'object') { - this.traceFunctionAST(this.source); - return this.ast = this.source; - } - - inParser = inParser || acorn; - if (inParser === null) { - throw new Error('Missing JS to AST parser'); - } - - const ast = Object.freeze(inParser.parse(`const parser_${ this.name } = ${ this.source };`, { - locations: true - })); - // take out the function object, outside the var declarations - const functionAST = ast.body[0].declarations[0].init; - this.traceFunctionAST(functionAST); - - if (!ast) { - throw new Error('Failed to parse JS code'); - } - - return this.ast = functionAST; - } - - traceFunctionAST(ast) { - const { contexts, declarations, functions, identifiers, functionCalls } = new FunctionTracer(ast); - this.contexts = contexts; - this.identifiers = identifiers; - this.functionCalls = functionCalls; - this.declarations = []; - this.functions = functions; - for (let i = 0; i < declarations.length; i++) { - const declaration = declarations[i]; - const { ast, context, name, origin, forceInteger, assignable } = declaration; - const { init } = ast; - const dependencies = this.getDependencies(init); - let valueType = null; - - if (forceInteger) { - valueType = 'Integer'; - } else { - if (init) { - const realType = this.getType(init); - switch (realType) { - case 'Integer': - case 'Float': - case 'Number': - if (init.type === 'MemberExpression') { - valueType = realType; - } else { - valueType = 'Number'; - } - break; - case 'LiteralInteger': - valueType = 'Number'; - break; - default: - valueType = realType; - } - } - } - this.declarations.push({ - valueType, - dependencies, - isSafe: this.isSafeDependencies(dependencies), - ast, - name, - context, - origin, - assignable, - }); - } - - for (let i = 0; i < functions.length; i++) { - this.onNestedFunction(functions[i]); - } - } - - getDeclaration(ast) { - for (let i = 0; i < this.identifiers.length; i++) { - const identifier = this.identifiers[i]; - if (ast === identifier.ast && identifier.context.hasOwnProperty(ast.name)) { - for (let j = 0; j < this.declarations.length; j++) { - const declaration = this.declarations[j]; - if (declaration.name === ast.name && declaration.context[ast.name] === identifier.context[ast.name]) { - return declaration; - } - } - } - } - return null; - } - - /** - * @desc Return the type of parameter sent to subKernel/Kernel. - * @param {Object} ast - Identifier - * @returns {String} Type of the parameter - */ - getVariableType(ast) { - if (ast.type !== 'Identifier') { - throw new Error(`ast of ${ast.type} not "Identifier"`); - } - let type = null; - const argumentIndex = this.argumentNames.indexOf(ast.name); - if (argumentIndex === -1) { - const declaration = this.getDeclaration(ast); - if (declaration) { - return declaration.valueType; - } - } else { - const argumentType = this.argumentTypes[argumentIndex]; - if (argumentType) { - type = argumentType; - } - } - if (!type && this.strictTypingChecking) { - throw new Error(`Declaration of ${name} not found`); - } - return type; - } - - /** - * Generally used to lookup the value type returned from a member expressions - * @param {String} type - * @return {String} - */ - getLookupType(type) { - if (!typeLookupMap.hasOwnProperty(type)) { - throw new Error(`unknown typeLookupMap ${ type }`); - } - return typeLookupMap[type]; - } - - getConstantType(constantName) { - if (this.constantTypes[constantName]) { - const type = this.constantTypes[constantName]; - if (type === 'Float') { - return 'Number'; - } else { - return type; - } - } - throw new Error(`Type for constant "${ constantName }" not declared`); - } - - toString() { - if (this._string) return this._string; - return this._string = this.astGeneric(this.getJsAST(), []).join('').trim(); - } - - toJSON() { - const settings = { - source: this.source, - name: this.name, - constants: this.constants, - constantTypes: this.constantTypes, - isRootKernel: this.isRootKernel, - isSubKernel: this.isSubKernel, - debug: this.debug, - output: this.output, - loopMaxIterations: this.loopMaxIterations, - argumentNames: this.argumentNames, - argumentTypes: this.argumentTypes, - argumentSizes: this.argumentSizes, - returnType: this.returnType, - leadingReturnStatement: this.leadingReturnStatement, - followingReturnStatement: this.followingReturnStatement, - }; - - return { - ast: this.ast, - settings - }; - } - - /** - * Recursively looks up type for ast expression until it's found - * @param ast - * @returns {String|null} - */ - getType(ast) { - if (Array.isArray(ast)) { - return this.getType(ast[ast.length - 1]); - } - switch (ast.type) { - case 'BlockStatement': - return this.getType(ast.body); - case 'ArrayExpression': - return `Array(${ ast.elements.length })`; - case 'Literal': - const literalKey = `${ast.start},${ast.end}`; - if (this.literalTypes[literalKey]) { - return this.literalTypes[literalKey]; - } - if (Number.isInteger(ast.value)) { - return 'LiteralInteger'; - } else if (ast.value === true || ast.value === false) { - return 'Boolean'; - } else { - return 'Number'; - } - case 'AssignmentExpression': - return this.getType(ast.left); - case 'CallExpression': - if (this.isAstMathFunction(ast)) { - return 'Number'; - } - if (!ast.callee || !ast.callee.name) { - if (ast.callee.type === 'SequenceExpression' && ast.callee.expressions[ast.callee.expressions.length - 1].property.name) { - const functionName = ast.callee.expressions[ast.callee.expressions.length - 1].property.name; - this.inferArgumentTypesIfNeeded(functionName, ast.arguments); - return this.lookupReturnType(functionName, ast, this); - } - throw this.astErrorOutput('Unknown call expression', ast); - } - if (ast.callee && ast.callee.name) { - const functionName = ast.callee.name; - this.inferArgumentTypesIfNeeded(functionName, ast.arguments); - return this.lookupReturnType(functionName, ast, this); - } - throw this.astErrorOutput(`Unhandled getType Type "${ ast.type }"`, ast); - case 'BinaryExpression': - // modulos is Number - switch (ast.operator) { - case '%': - case '/': - if (this.fixIntegerDivisionAccuracy) { - return 'Number'; - } else { - break; - } - case '>': - case '<': - return 'Boolean'; - case '&': - case '|': - case '^': - case '<<': - case '>>': - case '>>>': - return 'Integer'; - } - const type = this.getType(ast.left); - if (this.isState('skip-literal-correction')) return type; - if (type === 'LiteralInteger') { - const rightType = this.getType(ast.right); - if (rightType === 'LiteralInteger') { - if (ast.left.value % 1 === 0) { - return 'Integer'; - } else { - return 'Float'; - } - } - return rightType; - } - return typeLookupMap[type] || type; - case 'UpdateExpression': - return this.getType(ast.argument); - case 'UnaryExpression': - if (ast.operator === '~') { - return 'Integer'; - } - return this.getType(ast.argument); - case 'VariableDeclaration': { - const declarations = ast.declarations; - let lastType; - for (let i = 0; i < declarations.length; i++) { - const declaration = declarations[i]; - lastType = this.getType(declaration); - } - if (!lastType) { - throw this.astErrorOutput(`Unable to find type for declaration`, ast); - } - return lastType; - } - case 'VariableDeclarator': - const declaration = this.getDeclaration(ast.id); - if (!declaration) { - throw this.astErrorOutput(`Unable to find declarator`, ast); - } - - if (!declaration.valueType) { - throw this.astErrorOutput(`Unable to find declarator valueType`, ast); - } - - return declaration.valueType; - case 'Identifier': - if (ast.name === 'Infinity') { - return 'Number'; - } - if (this.isAstVariable(ast)) { - const signature = this.getVariableSignature(ast); - if (signature === 'value') { - const type = this.getVariableType(ast); - if (!type) { - throw this.astErrorOutput(`Unable to find identifier valueType`, ast); - } - return type; - } - } - const origin = this.findIdentifierOrigin(ast); - if (origin && origin.init) { - return this.getType(origin.init); - } - return null; - case 'ReturnStatement': - return this.getType(ast.argument); - case 'MemberExpression': - if (this.isAstMathFunction(ast)) { - switch (ast.property.name) { - case 'ceil': - return 'Integer'; - case 'floor': - return 'Integer'; - case 'round': - return 'Integer'; - } - return 'Number'; - } - if (this.isAstVariable(ast)) { - const variableSignature = this.getVariableSignature(ast); - switch (variableSignature) { - case 'value[]': - return this.getLookupType(this.getVariableType(ast.object)); - case 'value[][]': - return this.getLookupType(this.getVariableType(ast.object.object)); - case 'value[][][]': - return this.getLookupType(this.getVariableType(ast.object.object.object)); - case 'value[][][][]': - return this.getLookupType(this.getVariableType(ast.object.object.object.object)); - case 'value.thread.value': - case 'this.thread.value': - return 'Integer'; - case 'this.output.value': - return this.dynamicOutput ? 'Integer' : 'LiteralInteger'; - case 'this.constants.value': - return this.getConstantType(ast.property.name); - case 'this.constants.value[]': - return this.getLookupType(this.getConstantType(ast.object.property.name)); - case 'this.constants.value[][]': - return this.getLookupType(this.getConstantType(ast.object.object.property.name)); - case 'this.constants.value[][][]': - return this.getLookupType(this.getConstantType(ast.object.object.object.property.name)); - case 'this.constants.value[][][][]': - return this.getLookupType(this.getConstantType(ast.object.object.object.object.property.name)); - case 'fn()[]': - return this.getLookupType(this.getType(ast.object)); - case 'fn()[][]': - return this.getLookupType(this.getType(ast.object)); - case 'fn()[][][]': - return this.getLookupType(this.getType(ast.object)); - case 'value.value': - if (this.isAstMathVariable(ast)) { - return 'Number'; - } - switch (ast.property.name) { - case 'r': - return this.getLookupType(this.getVariableType(ast.object)); - case 'g': - return this.getLookupType(this.getVariableType(ast.object)); - case 'b': - return this.getLookupType(this.getVariableType(ast.object)); - case 'a': - return this.getLookupType(this.getVariableType(ast.object)); - } - case '[][]': - return 'Number'; - } - throw this.astErrorOutput('Unhandled getType MemberExpression', ast); - } - throw this.astErrorOutput('Unhandled getType MemberExpression', ast); - case 'ConditionalExpression': - return this.getType(ast.consequent); - case 'FunctionDeclaration': - case 'FunctionExpression': - const lastReturn = this.findLastReturn(ast.body); - if (lastReturn) { - return this.getType(lastReturn); - } - return null; - case 'IfStatement': - return this.getType(ast.consequent); - default: - throw this.astErrorOutput(`Unhandled getType Type "${ ast.type }"`, ast); - } - } - - inferArgumentTypesIfNeeded(functionName, args) { - // ensure arguments are filled in, so when we lookup return type, we already can infer it - for (let i = 0; i < args.length; i++) { - if (!this.needsArgumentType(functionName, i)) continue; - const type = this.getType(args[i]); - if (!type) { - throw this.astErrorOutput(`Unable to infer argument ${i}`, args[i]); - } - this.assignArgumentType(functionName, i, type); - } - } - - isAstMathVariable(ast) { - const mathProperties = [ - 'E', - 'PI', - 'SQRT2', - 'SQRT1_2', - 'LN2', - 'LN10', - 'LOG2E', - 'LOG10E', - ]; - return ast.type === 'MemberExpression' && - ast.object && ast.object.type === 'Identifier' && - ast.object.name === 'Math' && - ast.property && - ast.property.type === 'Identifier' && - mathProperties.indexOf(ast.property.name) > -1; - } - - isAstMathFunction(ast) { - const mathFunctions = [ - 'abs', - 'acos', - 'asin', - 'atan', - 'atan2', - 'ceil', - 'cos', - 'exp', - 'floor', - 'log', - 'log2', - 'max', - 'min', - 'pow', - 'random', - 'round', - 'sign', - 'sin', - 'sqrt', - 'tan', - ]; - return ast.type === 'CallExpression' && - ast.callee && - ast.callee.type === 'MemberExpression' && - ast.callee.object && - ast.callee.object.type === 'Identifier' && - ast.callee.object.name === 'Math' && - ast.callee.property && - ast.callee.property.type === 'Identifier' && - mathFunctions.indexOf(ast.callee.property.name) > -1; - } - - isAstVariable(ast) { - return ast.type === 'Identifier' || ast.type === 'MemberExpression'; - } - - isSafe(ast) { - return this.isSafeDependencies(this.getDependencies(ast)); - } - - isSafeDependencies(dependencies) { - return dependencies && dependencies.every ? dependencies.every(dependency => dependency.isSafe) : true; - } - - /** - * - * @param ast - * @param dependencies - * @param isNotSafe - * @return {Array} - */ - getDependencies(ast, dependencies, isNotSafe) { - if (!dependencies) { - dependencies = []; - } - if (!ast) return null; - if (Array.isArray(ast)) { - for (let i = 0; i < ast.length; i++) { - this.getDependencies(ast[i], dependencies, isNotSafe); - } - return dependencies; - } - switch (ast.type) { - case 'AssignmentExpression': - this.getDependencies(ast.left, dependencies, isNotSafe); - this.getDependencies(ast.right, dependencies, isNotSafe); - return dependencies; - case 'ConditionalExpression': - this.getDependencies(ast.test, dependencies, isNotSafe); - this.getDependencies(ast.alternate, dependencies, isNotSafe); - this.getDependencies(ast.consequent, dependencies, isNotSafe); - return dependencies; - case 'Literal': - dependencies.push({ - origin: 'literal', - value: ast.value, - isSafe: isNotSafe === true ? false : ast.value > -Infinity && ast.value < Infinity && !isNaN(ast.value) - }); - break; - case 'VariableDeclarator': - return this.getDependencies(ast.init, dependencies, isNotSafe); - case 'Identifier': - const declaration = this.getDeclaration(ast); - if (declaration) { - dependencies.push({ - name: ast.name, - origin: 'declaration', - isSafe: isNotSafe ? false : this.isSafeDependencies(declaration.dependencies), - }); - } else if (this.argumentNames.indexOf(ast.name) > -1) { - dependencies.push({ - name: ast.name, - origin: 'argument', - isSafe: false, - }); - } else if (this.strictTypingChecking) { - throw new Error(`Cannot find identifier origin "${ast.name}"`); - } - break; - case 'FunctionDeclaration': - return this.getDependencies(ast.body.body[ast.body.body.length - 1], dependencies, isNotSafe); - case 'ReturnStatement': - return this.getDependencies(ast.argument, dependencies); - case 'BinaryExpression': - isNotSafe = (ast.operator === '/' || ast.operator === '*'); - this.getDependencies(ast.left, dependencies, isNotSafe); - this.getDependencies(ast.right, dependencies, isNotSafe); - return dependencies; - case 'UnaryExpression': - case 'UpdateExpression': - return this.getDependencies(ast.argument, dependencies, isNotSafe); - case 'VariableDeclaration': - return this.getDependencies(ast.declarations, dependencies, isNotSafe); - case 'ArrayExpression': - dependencies.push({ - origin: 'declaration', - isSafe: true, - }); - return dependencies; - case 'CallExpression': - dependencies.push({ - origin: 'function', - isSafe: true, - }); - return dependencies; - case 'MemberExpression': - const details = this.getMemberExpressionDetails(ast); - switch (details.signature) { - case 'value[]': - this.getDependencies(ast.object, dependencies, isNotSafe); - break; - case 'value[][]': - this.getDependencies(ast.object.object, dependencies, isNotSafe); - break; - case 'value[][][]': - this.getDependencies(ast.object.object.object, dependencies, isNotSafe); - break; - case 'this.output.value': - if (this.dynamicOutput) { - dependencies.push({ - name: details.name, - origin: 'output', - isSafe: false, - }); - } - break; - } - if (details) { - if (details.property) { - this.getDependencies(details.property, dependencies, isNotSafe); - } - if (details.xProperty) { - this.getDependencies(details.xProperty, dependencies, isNotSafe); - } - if (details.yProperty) { - this.getDependencies(details.yProperty, dependencies, isNotSafe); - } - if (details.zProperty) { - this.getDependencies(details.zProperty, dependencies, isNotSafe); - } - return dependencies; - } - default: - throw this.astErrorOutput(`Unhandled type ${ ast.type } in getDependencies`, ast); - } - return dependencies; - } - - getVariableSignature(ast) { - if (!this.isAstVariable(ast)) { - throw new Error(`ast of type "${ ast.type }" is not a variable signature`); - } - if (ast.type === 'Identifier') { - return 'value'; - } - const signature = []; - while (true) { - if (!ast) break; - if (ast.computed) { - signature.push('[]'); - } else if (ast.type === 'ThisExpression') { - signature.unshift('this'); - } else if (ast.property && ast.property.name) { - if ( - ast.property.name === 'x' || - ast.property.name === 'y' || - ast.property.name === 'z' - ) { - signature.unshift('.value'); - } else if ( - ast.property.name === 'constants' || - ast.property.name === 'thread' || - ast.property.name === 'output' - ) { - signature.unshift('.' + ast.property.name); - } else { - signature.unshift('.value'); - } - } else if (ast.name) { - signature.unshift('value'); - } else if (ast.callee && ast.callee.name) { - signature.unshift('fn()'); - } else if (ast.elements) { - signature.unshift('[]'); - } else { - signature.unshift('unknown'); - } - ast = ast.object; - } - - const signatureString = signature.join(''); - const allowedExpressions = [ - 'value', - 'value[]', - 'value[][]', - 'value[][][]', - 'value[][][][]', - 'value.value', - 'value.thread.value', - 'this.thread.value', - 'this.output.value', - 'this.constants.value', - 'this.constants.value[]', - 'this.constants.value[][]', - 'this.constants.value[][][]', - 'this.constants.value[][][][]', - 'fn()[]', - 'fn()[][]', - 'fn()[][][]', - '[][]', - ]; - if (allowedExpressions.indexOf(signatureString) > -1) { - return signatureString; - } - return null; - } - - build() { - return this.toString().length > 0; - } - - /** - * @desc Parses the abstract syntax tree for generically to its respective function - * @param {Object} ast - the AST object to parse - * @param {Array} retArr - return array string - * @returns {Array} the parsed string array - */ - astGeneric(ast, retArr) { - if (ast === null) { - throw this.astErrorOutput('NULL ast', ast); - } else { - if (Array.isArray(ast)) { - for (let i = 0; i < ast.length; i++) { - this.astGeneric(ast[i], retArr); - } - return retArr; - } - - switch (ast.type) { - case 'FunctionDeclaration': - return this.astFunctionDeclaration(ast, retArr); - case 'FunctionExpression': - return this.astFunctionExpression(ast, retArr); - case 'ReturnStatement': - return this.astReturnStatement(ast, retArr); - case 'Literal': - return this.astLiteral(ast, retArr); - case 'BinaryExpression': - return this.astBinaryExpression(ast, retArr); - case 'Identifier': - return this.astIdentifierExpression(ast, retArr); - case 'AssignmentExpression': - return this.astAssignmentExpression(ast, retArr); - case 'ExpressionStatement': - return this.astExpressionStatement(ast, retArr); - case 'EmptyStatement': - return this.astEmptyStatement(ast, retArr); - case 'BlockStatement': - return this.astBlockStatement(ast, retArr); - case 'IfStatement': - return this.astIfStatement(ast, retArr); - case 'SwitchStatement': - return this.astSwitchStatement(ast, retArr); - case 'BreakStatement': - return this.astBreakStatement(ast, retArr); - case 'ContinueStatement': - return this.astContinueStatement(ast, retArr); - case 'ForStatement': - return this.astForStatement(ast, retArr); - case 'WhileStatement': - return this.astWhileStatement(ast, retArr); - case 'DoWhileStatement': - return this.astDoWhileStatement(ast, retArr); - case 'VariableDeclaration': - return this.astVariableDeclaration(ast, retArr); - case 'VariableDeclarator': - return this.astVariableDeclarator(ast, retArr); - case 'ThisExpression': - return this.astThisExpression(ast, retArr); - case 'SequenceExpression': - return this.astSequenceExpression(ast, retArr); - case 'UnaryExpression': - return this.astUnaryExpression(ast, retArr); - case 'UpdateExpression': - return this.astUpdateExpression(ast, retArr); - case 'LogicalExpression': - return this.astLogicalExpression(ast, retArr); - case 'MemberExpression': - return this.astMemberExpression(ast, retArr); - case 'CallExpression': - return this.astCallExpression(ast, retArr); - case 'ArrayExpression': - return this.astArrayExpression(ast, retArr); - case 'DebuggerStatement': - return this.astDebuggerStatement(ast, retArr); - case 'ConditionalExpression': - return this.astConditionalExpression(ast, retArr); - } - - throw this.astErrorOutput('Unknown ast type : ' + ast.type, ast); - } - } - /** - * @desc To throw the AST error, with its location. - * @param {string} error - the error message output - * @param {Object} ast - the AST object where the error is - */ - astErrorOutput(error, ast) { - if (typeof this.source !== 'string') { - return new Error(error); - } - - const debugString = utils.getAstString(this.source, ast); - const leadingSource = this.source.substr(ast.start); - const splitLines = leadingSource.split(/\n/); - const lineBefore = splitLines.length > 0 ? splitLines[splitLines.length - 1] : 0; - return new Error(`${error} on line ${ splitLines.length }, position ${ lineBefore.length }:\n ${ debugString }`); - } - - astDebuggerStatement(arrNode, retArr) { - return retArr; - } - - astConditionalExpression(ast, retArr) { - if (ast.type !== 'ConditionalExpression') { - throw this.astErrorOutput('Not a conditional expression', ast); - } - retArr.push('('); - this.astGeneric(ast.test, retArr); - retArr.push('?'); - this.astGeneric(ast.consequent, retArr); - retArr.push(':'); - this.astGeneric(ast.alternate, retArr); - retArr.push(')'); - return retArr; - } - - /** - * @abstract - * @param {Object} ast - * @param {String[]} retArr - * @returns {String[]} - */ - astFunction(ast, retArr) { - throw new Error(`"astFunction" not defined on ${ this.constructor.name }`); - } - - /** - * @desc Parses the abstract syntax tree for to its *named function declaration* - * @param {Object} ast - the AST object to parse - * @param {Array} retArr - return array string - * @returns {Array} the append retArr - */ - astFunctionDeclaration(ast, retArr) { - if (this.isChildFunction(ast)) { - return retArr; - } - return this.astFunction(ast, retArr); - } - astFunctionExpression(ast, retArr) { - if (this.isChildFunction(ast)) { - return retArr; - } - return this.astFunction(ast, retArr); - } - isChildFunction(ast) { - for (let i = 0; i < this.functions.length; i++) { - if (this.functions[i] === ast) { - return true; - } - } - return false; - } - astReturnStatement(ast, retArr) { - return retArr; - } - astLiteral(ast, retArr) { - this.literalTypes[`${ast.start},${ast.end}`] = 'Number'; - return retArr; - } - astBinaryExpression(ast, retArr) { - return retArr; - } - astIdentifierExpression(ast, retArr) { - return retArr; - } - astAssignmentExpression(ast, retArr) { - return retArr; - } - /** - * @desc Parses the abstract syntax tree for *generic expression* statement - * @param {Object} esNode - An ast Node - * @param {Array} retArr - return array string - * @returns {Array} the append retArr - */ - astExpressionStatement(esNode, retArr) { - this.astGeneric(esNode.expression, retArr); - retArr.push(';'); - return retArr; - } - /** - * @desc Parses the abstract syntax tree for an *Empty* Statement - * @param {Object} eNode - An ast Node - * @param {Array} retArr - return array string - * @returns {Array} the append retArr - */ - astEmptyStatement(eNode, retArr) { - return retArr; - } - astBlockStatement(ast, retArr) { - return retArr; - } - astIfStatement(ast, retArr) { - return retArr; - } - astSwitchStatement(ast, retArr) { - return retArr; - } - /** - * @desc Parses the abstract syntax tree for *Break* Statement - * @param {Object} brNode - An ast Node - * @param {Array} retArr - return array string - * @returns {Array} the append retArr - */ - astBreakStatement(brNode, retArr) { - retArr.push('break;'); - return retArr; - } - /** - * @desc Parses the abstract syntax tree for *Continue* Statement - * @param {Object} crNode - An ast Node - * @param {Array} retArr - return array string - * @returns {Array} the append retArr - */ - astContinueStatement(crNode, retArr) { - retArr.push('continue;\n'); - return retArr; - } - astForStatement(ast, retArr) { - return retArr; - } - astWhileStatement(ast, retArr) { - return retArr; - } - astDoWhileStatement(ast, retArr) { - return retArr; - } - /** - * @desc Parses the abstract syntax tree for *Variable Declaration* - * @param {Object} varDecNode - An ast Node - * @param {Array} retArr - return array string - * @returns {Array} the append retArr - */ - astVariableDeclaration(varDecNode, retArr) { - const declarations = varDecNode.declarations; - if (!declarations || !declarations[0] || !declarations[0].init) { - throw this.astErrorOutput('Unexpected expression', varDecNode); - } - const result = []; - const firstDeclaration = declarations[0]; - const init = firstDeclaration.init; - let type = this.isState('in-for-loop-init') ? 'Integer' : this.getType(init); - if (type === 'LiteralInteger') { - // We had the choice to go either float or int, choosing float - type = 'Number'; - } - const markupType = typeMap[type]; - if (!markupType) { - throw this.astErrorOutput(`Markup type ${ markupType } not handled`, varDecNode); - } - let dependencies = this.getDependencies(firstDeclaration.init); - throw new Error('remove me'); - this.declarations[firstDeclaration.id.name] = Object.freeze({ - type, - dependencies, - isSafe: dependencies.every(dependency => dependency.isSafe) - }); - const initResult = [`${type} user_${firstDeclaration.id.name}=`]; - this.astGeneric(init, initResult); - result.push(initResult.join('')); - - // first declaration is done, now any added ones setup - for (let i = 1; i < declarations.length; i++) { - const declaration = declarations[i]; - dependencies = this.getDependencies(declaration); - throw new Error('Remove me'); - this.declarations[declaration.id.name] = Object.freeze({ - type, - dependencies, - isSafe: false - }); - this.astGeneric(declaration, result); - } - - retArr.push(retArr, result.join(',')); - retArr.push(';'); - return retArr; - } - /** - * @desc Parses the abstract syntax tree for *Variable Declarator* - * @param {Object} iVarDecNode - An ast Node - * @param {Array} retArr - return array string - * @returns {Array} the append retArr - */ - astVariableDeclarator(iVarDecNode, retArr) { - this.astGeneric(iVarDecNode.id, retArr); - if (iVarDecNode.init !== null) { - retArr.push('='); - this.astGeneric(iVarDecNode.init, retArr); - } - return retArr; - } - astThisExpression(ast, retArr) { - return retArr; - } - astSequenceExpression(sNode, retArr) { - for (let i = 0; i < sNode.expressions.length; i++) { - if (i > 0) { - retArr.push(','); - } - this.astGeneric(sNode.expressions, retArr); - } - return retArr; - } - /** - * @desc Parses the abstract syntax tree for *Unary* Expression - * @param {Object} uNode - An ast Node - * @param {Array} retArr - return array string - * @returns {Array} the append retArr - */ - astUnaryExpression(uNode, retArr) { - const unaryResult = this.checkAndUpconvertBitwiseUnary(uNode, retArr); - if (unaryResult) { - return retArr; - } - - if (uNode.prefix) { - retArr.push(uNode.operator); - this.astGeneric(uNode.argument, retArr); - } else { - this.astGeneric(uNode.argument, retArr); - retArr.push(uNode.operator); - } - - return retArr; - } - - checkAndUpconvertBitwiseUnary(uNode, retArr) {} - - /** - * @desc Parses the abstract syntax tree for *Update* Expression - * @param {Object} uNode - An ast Node - * @param {Array} retArr - return array string - * @returns {Array} the append retArr - */ - astUpdateExpression(uNode, retArr) { - if (uNode.prefix) { - retArr.push(uNode.operator); - this.astGeneric(uNode.argument, retArr); - } else { - this.astGeneric(uNode.argument, retArr); - retArr.push(uNode.operator); - } - - return retArr; - } - /** - * @desc Parses the abstract syntax tree for *Logical* Expression - * @param {Object} logNode - An ast Node - * @param {Array} retArr - return array string - * @returns {Array} the append retArr - */ - astLogicalExpression(logNode, retArr) { - retArr.push('('); - this.astGeneric(logNode.left, retArr); - retArr.push(logNode.operator); - this.astGeneric(logNode.right, retArr); - retArr.push(')'); - return retArr; - } - astMemberExpression(ast, retArr) { - return retArr; - } - astCallExpression(ast, retArr) { - return retArr; - } - astArrayExpression(ast, retArr) { - return retArr; - } - - /** - * - * @param ast - * @return {IFunctionNodeMemberExpressionDetails} - */ - getMemberExpressionDetails(ast) { - if (ast.type !== 'MemberExpression') { - throw this.astErrorOutput(`Expression ${ ast.type } not a MemberExpression`, ast); - } - let name = null; - let type = null; - const variableSignature = this.getVariableSignature(ast); - switch (variableSignature) { - case 'value': - return null; - case 'value.thread.value': - case 'this.thread.value': - case 'this.output.value': - return { - signature: variableSignature, - type: 'Integer', - name: ast.property.name - }; - case 'value[]': - if (typeof ast.object.name !== 'string') { - throw this.astErrorOutput('Unexpected expression', ast); - } - name = ast.object.name; - return { - name, - origin: 'user', - signature: variableSignature, - type: this.getVariableType(ast.object), - xProperty: ast.property - }; - case 'value[][]': - if (typeof ast.object.object.name !== 'string') { - throw this.astErrorOutput('Unexpected expression', ast); - } - name = ast.object.object.name; - return { - name, - origin: 'user', - signature: variableSignature, - type: this.getVariableType(ast.object.object), - yProperty: ast.object.property, - xProperty: ast.property, - }; - case 'value[][][]': - if (typeof ast.object.object.object.name !== 'string') { - throw this.astErrorOutput('Unexpected expression', ast); - } - name = ast.object.object.object.name; - return { - name, - origin: 'user', - signature: variableSignature, - type: this.getVariableType(ast.object.object.object), - zProperty: ast.object.object.property, - yProperty: ast.object.property, - xProperty: ast.property, - }; - case 'value[][][][]': - if (typeof ast.object.object.object.object.name !== 'string') { - throw this.astErrorOutput('Unexpected expression', ast); - } - name = ast.object.object.object.object.name; - return { - name, - origin: 'user', - signature: variableSignature, - type: this.getVariableType(ast.object.object.object.object), - zProperty: ast.object.object.property, - yProperty: ast.object.property, - xProperty: ast.property, - }; - case 'value.value': - if (typeof ast.property.name !== 'string') { - throw this.astErrorOutput('Unexpected expression', ast); - } - if (this.isAstMathVariable(ast)) { - name = ast.property.name; - return { - name, - origin: 'Math', - type: 'Number', - signature: variableSignature, - }; - } - switch (ast.property.name) { - case 'r': - case 'g': - case 'b': - case 'a': - name = ast.object.name; - return { - name, - property: ast.property.name, - origin: 'user', - signature: variableSignature, - type: 'Number' - }; - default: - throw this.astErrorOutput('Unexpected expression', ast); - } - case 'this.constants.value': - if (typeof ast.property.name !== 'string') { - throw this.astErrorOutput('Unexpected expression', ast); - } - name = ast.property.name; - type = this.getConstantType(name); - if (!type) { - throw this.astErrorOutput('Constant has no type', ast); - } - return { - name, - type, - origin: 'constants', - signature: variableSignature, - }; - case 'this.constants.value[]': - if (typeof ast.object.property.name !== 'string') { - throw this.astErrorOutput('Unexpected expression', ast); - } - name = ast.object.property.name; - type = this.getConstantType(name); - if (!type) { - throw this.astErrorOutput('Constant has no type', ast); - } - return { - name, - type, - origin: 'constants', - signature: variableSignature, - xProperty: ast.property, - }; - case 'this.constants.value[][]': { - if (typeof ast.object.object.property.name !== 'string') { - throw this.astErrorOutput('Unexpected expression', ast); - } - name = ast.object.object.property.name; - type = this.getConstantType(name); - if (!type) { - throw this.astErrorOutput('Constant has no type', ast); - } - return { - name, - type, - origin: 'constants', - signature: variableSignature, - yProperty: ast.object.property, - xProperty: ast.property, - }; - } - case 'this.constants.value[][][]': { - if (typeof ast.object.object.object.property.name !== 'string') { - throw this.astErrorOutput('Unexpected expression', ast); - } - name = ast.object.object.object.property.name; - type = this.getConstantType(name); - if (!type) { - throw this.astErrorOutput('Constant has no type', ast); - } - return { - name, - type, - origin: 'constants', - signature: variableSignature, - zProperty: ast.object.object.property, - yProperty: ast.object.property, - xProperty: ast.property, - }; - } - case 'fn()[]': - case '[][]': - return { - signature: variableSignature, - property: ast.property, - }; - default: - throw this.astErrorOutput('Unexpected expression', ast); - } - } - - findIdentifierOrigin(astToFind) { - const stack = [this.ast]; - - while (stack.length > 0) { - const atNode = stack[0]; - if (atNode.type === 'VariableDeclarator' && atNode.id && atNode.id.name && atNode.id.name === astToFind.name) { - return atNode; - } - stack.shift(); - if (atNode.argument) { - stack.push(atNode.argument); - } else if (atNode.body) { - stack.push(atNode.body); - } else if (atNode.declarations) { - stack.push(atNode.declarations); - } else if (Array.isArray(atNode)) { - for (let i = 0; i < atNode.length; i++) { - stack.push(atNode[i]); - } - } - } - return null; - } - - findLastReturn(ast) { - const stack = [ast || this.ast]; - - while (stack.length > 0) { - const atNode = stack.pop(); - if (atNode.type === 'ReturnStatement') { - return atNode; - } - if (atNode.type === 'FunctionDeclaration') { - continue; - } - if (atNode.argument) { - stack.push(atNode.argument); - } else if (atNode.body) { - stack.push(atNode.body); - } else if (atNode.declarations) { - stack.push(atNode.declarations); - } else if (Array.isArray(atNode)) { - for (let i = 0; i < atNode.length; i++) { - stack.push(atNode[i]); - } - } else if (atNode.consequent) { - stack.push(atNode.consequent); - } else if (atNode.cases) { - stack.push(atNode.cases); - } - } - return null; - } - - getInternalVariableName(name) { - if (!this._internalVariableNames.hasOwnProperty(name)) { - this._internalVariableNames[name] = 0; - } - this._internalVariableNames[name]++; - if (this._internalVariableNames[name] === 1) { - return name; - } - return name + this._internalVariableNames[name]; - } - - varWarn() { - console.warn('var declarations are deprecated, weird things happen when falling back to CPU because var scope differs in javascript than in most languages. Use const or let'); - } -} - -const typeLookupMap = { - 'Number': 'Number', - 'Float': 'Float', - 'Integer': 'Integer', - 'Array': 'Number', - 'Array(2)': 'Number', - 'Array(3)': 'Number', - 'Array(4)': 'Number', - 'Array2D': 'Number', - 'Array3D': 'Number', - 'Input': 'Number', - 'HTMLImage': 'Array(4)', - 'HTMLVideo': 'Array(4)', - 'HTMLImageArray': 'Array(4)', - 'NumberTexture': 'Number', - 'MemoryOptimizedNumberTexture': 'Number', - 'Array1D(2)': 'Array(2)', - 'Array1D(3)': 'Array(3)', - 'Array1D(4)': 'Array(4)', - 'Array2D(2)': 'Array(2)', - 'Array2D(3)': 'Array(3)', - 'Array2D(4)': 'Array(4)', - 'Array3D(2)': 'Array(2)', - 'Array3D(3)': 'Array(3)', - 'Array3D(4)': 'Array(4)', - 'ArrayTexture(1)': 'Number', - 'ArrayTexture(2)': 'Array(2)', - 'ArrayTexture(3)': 'Array(3)', - 'ArrayTexture(4)': 'Array(4)', -}; - -module.exports = { - FunctionNode -}; \ No newline at end of file +import { parse } from 'acorn'; +import { FunctionTracer } from './function-tracer'; +import { + getArgumentNamesFromString, + getAstString, + getFunctionNameFromString, + isFunctionString, +} from '../common'; + +/** + * + * @desc Represents a single function, inside JS, webGL, or openGL. + *This handles all the raw state, converted state, etc. Of a single function.
+ */ +export class FunctionNode { + /** + * + * @param {string|object} source + * @param {IFunctionSettings} [settings] + */ + constructor(source, settings) { + if (!source && !settings.ast) { + throw new Error('source parameter is missing'); + } + settings = settings || {}; + this.source = source; + this.ast = null; + this.name = typeof source === 'string' ? settings.isRootKernel ? + 'kernel' : + (settings.name || getFunctionNameFromString(source)) : null; + this.calledFunctions = []; + this.constants = {}; + this.constantTypes = {}; + this.constantBitRatios = {}; + this.isRootKernel = false; + this.isSubKernel = false; + this.debug = null; + this.declarations = null; + this.functions = null; + this.identifiers = null; + this.contexts = null; + this.functionCalls = null; + this.states = []; + this.needsArgumentType = null; + this.assignArgumentType = null; + this.lookupReturnType = null; + this.lookupFunctionArgumentTypes = null; + this.lookupFunctionArgumentBitRatio = null; + this.triggerImplyArgumentType = null; + this.triggerImplyArgumentBitRatio = null; + this.onNestedFunction = null; + this.onFunctionCall = null; + this.optimizeFloatMemory = null; + this.precision = null; + this.loopMaxIterations = null; + this.argumentNames = (typeof this.source === 'string' ? getArgumentNamesFromString(this.source) : null); + this.argumentTypes = []; + this.argumentSizes = []; + this.argumentBitRatios = null; + this.returnType = null; + this.output = []; + this.plugins = null; + this.leadingReturnStatement = null; + this.followingReturnStatement = null; + this.dynamicOutput = null; + this.dynamicArguments = null; + this.strictTypingChecking = false; + this.fixIntegerDivisionAccuracy = null; + this.warnVarUsage = true; + + if (settings) { + for (const p in settings) { + if (!settings.hasOwnProperty(p)) continue; + if (!this.hasOwnProperty(p)) continue; + this[p] = settings[p]; + } + } + + this.literalTypes = {}; + + this.validate(); + this._string = null; + this._internalVariableNames = {}; + } + + validate() { + if (typeof this.source !== 'string' && !this.ast) { + throw new Error('this.source not a string'); + } + + if (!this.ast && !isFunctionString(this.source)) { + throw new Error('this.source not a function string'); + } + + if (!this.name) { + throw new Error('this.name could not be set'); + } + + if (this.argumentTypes.length > 0 && this.argumentTypes.length !== this.argumentNames.length) { + throw new Error(`argumentTypes count of ${ this.argumentTypes.length } exceeds ${ this.argumentNames.length }`); + } + + if (this.output.length < 1) { + throw new Error('this.output is not big enough'); + } + } + + /** + * @param {String} name + * @returns {boolean} + */ + isIdentifierConstant(name) { + if (!this.constants) return false; + return this.constants.hasOwnProperty(name); + } + + isInput(argumentName) { + return this.argumentTypes[this.argumentNames.indexOf(argumentName)] === 'Input'; + } + + pushState(state) { + this.states.push(state); + } + + popState(state) { + if (this.state !== state) { + throw new Error(`Cannot popState ${ state } when in ${ this.state }`); + } + this.states.pop(); + } + + isState(state) { + return this.state === state; + } + + get state() { + return this.states[this.states.length - 1]; + } + + /** + * @function + * @name astMemberExpressionUnroll + * @desc Parses the abstract syntax tree for binary expression. + * + *Utility function for astCallExpression.
+ * + * @param {Object} ast - the AST object to parse + * + * @returns {String} the function namespace call, unrolled + */ + astMemberExpressionUnroll(ast) { + if (ast.type === 'Identifier') { + return ast.name; + } else if (ast.type === 'ThisExpression') { + return 'this'; + } + + if (ast.type === 'MemberExpression') { + if (ast.object && ast.property) { + //babel sniffing + if (ast.object.hasOwnProperty('name') && ast.object.name[0] === '_') { + return this.astMemberExpressionUnroll(ast.property); + } + + return ( + this.astMemberExpressionUnroll(ast.object) + + '.' + + this.astMemberExpressionUnroll(ast.property) + ); + } + } + + //babel sniffing + if (ast.hasOwnProperty('expressions')) { + const firstExpression = ast.expressions[0]; + if (firstExpression.type === 'Literal' && firstExpression.value === 0 && ast.expressions.length === 2) { + return this.astMemberExpressionUnroll(ast.expressions[1]); + } + } + + // Failure, unknown expression + throw this.astErrorOutput('Unknown astMemberExpressionUnroll', ast); + } + + /** + * @desc Parses the class function JS, and returns its Abstract Syntax Tree object. + * This is used internally to convert to shader code + * + * @param {Object} [inParser] - Parser to use, assumes in scope 'parser' if null or undefined + * + * @returns {Object} The function AST Object, note that result is cached under this.ast; + */ + getJsAST(inParser) { + if (this.ast) { + return this.ast; + } + if (typeof this.source === 'object') { + this.traceFunctionAST(this.source); + return this.ast = this.source; + } + + const parser = inParser && inParser.hasOwnProperty('parse') ? inParser.parse : parse + if (inParser === null) { + throw new Error('Missing JS to AST parser'); + } + + const ast = Object.freeze(parser(`const parser_${ this.name } = ${ this.source };`, { + locations: true + })); + // take out the function object, outside the var declarations + const functionAST = ast.body[0].declarations[0].init; + this.traceFunctionAST(functionAST); + + if (!ast) { + throw new Error('Failed to parse JS code'); + } + + return this.ast = functionAST; + } + + traceFunctionAST(ast) { + const { contexts, declarations, functions, identifiers, functionCalls } = new FunctionTracer(ast); + this.contexts = contexts; + this.identifiers = identifiers; + this.functionCalls = functionCalls; + this.declarations = []; + this.functions = functions; + for (let i = 0; i < declarations.length; i++) { + const declaration = declarations[i]; + const { ast, context, name, origin, forceInteger, assignable } = declaration; + const { init } = ast; + const dependencies = this.getDependencies(init); + let valueType = null; + + if (forceInteger) { + valueType = 'Integer'; + } else { + if (init) { + const realType = this.getType(init); + switch (realType) { + case 'Integer': + case 'Float': + case 'Number': + if (init.type === 'MemberExpression') { + valueType = realType; + } else { + valueType = 'Number'; + } + break; + case 'LiteralInteger': + valueType = 'Number'; + break; + default: + valueType = realType; + } + } + } + this.declarations.push({ + valueType, + dependencies, + isSafe: this.isSafeDependencies(dependencies), + ast, + name, + context, + origin, + assignable, + }); + } + + for (let i = 0; i < functions.length; i++) { + this.onNestedFunction(functions[i]); + } + } + + getDeclaration(ast) { + for (let i = 0; i < this.identifiers.length; i++) { + const identifier = this.identifiers[i]; + if (ast === identifier.ast && identifier.context.hasOwnProperty(ast.name)) { + for (let j = 0; j < this.declarations.length; j++) { + const declaration = this.declarations[j]; + if (declaration.name === ast.name && declaration.context[ast.name] === identifier.context[ast.name]) { + return declaration; + } + } + } + } + return null; + } + + /** + * @desc Return the type of parameter sent to subKernel/Kernel. + * @param {Object} ast - Identifier + * @returns {String} Type of the parameter + */ + getVariableType(ast) { + if (ast.type !== 'Identifier') { + throw new Error(`ast of ${ast.type} not "Identifier"`); + } + let type = null; + const argumentIndex = this.argumentNames.indexOf(ast.name); + if (argumentIndex === -1) { + const declaration = this.getDeclaration(ast); + if (declaration) { + return declaration.valueType; + } + } else { + const argumentType = this.argumentTypes[argumentIndex]; + if (argumentType) { + type = argumentType; + } + } + if (!type && this.strictTypingChecking) { + throw new Error(`Declaration of ${name} not found`); + } + return type; + } + + /** + * Generally used to lookup the value type returned from a member expressions + * @param {String} type + * @return {String} + */ + getLookupType(type) { + if (!typeLookupMap.hasOwnProperty(type)) { + throw new Error(`unknown typeLookupMap ${ type }`); + } + return typeLookupMap[type]; + } + + getConstantType(constantName) { + if (this.constantTypes[constantName]) { + const type = this.constantTypes[constantName]; + if (type === 'Float') { + return 'Number'; + } else { + return type; + } + } + throw new Error(`Type for constant "${ constantName }" not declared`); + } + + toString() { + if (this._string) return this._string; + return this._string = this.astGeneric(this.getJsAST(), []).join('').trim(); + } + + toJSON() { + const settings = { + source: this.source, + name: this.name, + constants: this.constants, + constantTypes: this.constantTypes, + isRootKernel: this.isRootKernel, + isSubKernel: this.isSubKernel, + debug: this.debug, + output: this.output, + loopMaxIterations: this.loopMaxIterations, + argumentNames: this.argumentNames, + argumentTypes: this.argumentTypes, + argumentSizes: this.argumentSizes, + returnType: this.returnType, + leadingReturnStatement: this.leadingReturnStatement, + followingReturnStatement: this.followingReturnStatement, + }; + + return { + ast: this.ast, + settings + }; + } + + /** + * Recursively looks up type for ast expression until it's found + * @param ast + * @returns {String|null} + */ + getType(ast) { + if (Array.isArray(ast)) { + return this.getType(ast[ast.length - 1]); + } + switch (ast.type) { + case 'BlockStatement': + return this.getType(ast.body); + case 'ArrayExpression': + return `Array(${ ast.elements.length })`; + case 'Literal': + const literalKey = `${ast.start},${ast.end}`; + if (this.literalTypes[literalKey]) { + return this.literalTypes[literalKey]; + } + if (Number.isInteger(ast.value)) { + return 'LiteralInteger'; + } else if (ast.value === true || ast.value === false) { + return 'Boolean'; + } else { + return 'Number'; + } + case 'AssignmentExpression': + return this.getType(ast.left); + case 'CallExpression': + if (this.isAstMathFunction(ast)) { + return 'Number'; + } + if (!ast.callee || !ast.callee.name) { + if (ast.callee.type === 'SequenceExpression' && ast.callee.expressions[ast.callee.expressions.length - 1].property.name) { + const functionName = ast.callee.expressions[ast.callee.expressions.length - 1].property.name; + this.inferArgumentTypesIfNeeded(functionName, ast.arguments); + return this.lookupReturnType(functionName, ast, this); + } + throw this.astErrorOutput('Unknown call expression', ast); + } + if (ast.callee && ast.callee.name) { + const functionName = ast.callee.name; + this.inferArgumentTypesIfNeeded(functionName, ast.arguments); + return this.lookupReturnType(functionName, ast, this); + } + throw this.astErrorOutput(`Unhandled getType Type "${ ast.type }"`, ast); + case 'BinaryExpression': + // modulos is Number + switch (ast.operator) { + case '%': + case '/': + if (this.fixIntegerDivisionAccuracy) { + return 'Number'; + } else { + break; + } + case '>': + case '<': + return 'Boolean'; + case '&': + case '|': + case '^': + case '<<': + case '>>': + case '>>>': + return 'Integer'; + } + const type = this.getType(ast.left); + if (this.isState('skip-literal-correction')) return type; + if (type === 'LiteralInteger') { + const rightType = this.getType(ast.right); + if (rightType === 'LiteralInteger') { + if (ast.left.value % 1 === 0) { + return 'Integer'; + } else { + return 'Float'; + } + } + return rightType; + } + return typeLookupMap[type] || type; + case 'UpdateExpression': + return this.getType(ast.argument); + case 'UnaryExpression': + if (ast.operator === '~') { + return 'Integer'; + } + return this.getType(ast.argument); + case 'VariableDeclaration': { + const declarations = ast.declarations; + let lastType; + for (let i = 0; i < declarations.length; i++) { + const declaration = declarations[i]; + lastType = this.getType(declaration); + } + if (!lastType) { + throw this.astErrorOutput(`Unable to find type for declaration`, ast); + } + return lastType; + } + case 'VariableDeclarator': + const declaration = this.getDeclaration(ast.id); + if (!declaration) { + throw this.astErrorOutput(`Unable to find declarator`, ast); + } + + if (!declaration.valueType) { + throw this.astErrorOutput(`Unable to find declarator valueType`, ast); + } + + return declaration.valueType; + case 'Identifier': + if (ast.name === 'Infinity') { + return 'Number'; + } + if (this.isAstVariable(ast)) { + const signature = this.getVariableSignature(ast); + if (signature === 'value') { + const type = this.getVariableType(ast); + if (!type) { + throw this.astErrorOutput(`Unable to find identifier valueType`, ast); + } + return type; + } + } + const origin = this.findIdentifierOrigin(ast); + if (origin && origin.init) { + return this.getType(origin.init); + } + return null; + case 'ReturnStatement': + return this.getType(ast.argument); + case 'MemberExpression': + if (this.isAstMathFunction(ast)) { + switch (ast.property.name) { + case 'ceil': + return 'Integer'; + case 'floor': + return 'Integer'; + case 'round': + return 'Integer'; + } + return 'Number'; + } + if (this.isAstVariable(ast)) { + const variableSignature = this.getVariableSignature(ast); + switch (variableSignature) { + case 'value[]': + return this.getLookupType(this.getVariableType(ast.object)); + case 'value[][]': + return this.getLookupType(this.getVariableType(ast.object.object)); + case 'value[][][]': + return this.getLookupType(this.getVariableType(ast.object.object.object)); + case 'value[][][][]': + return this.getLookupType(this.getVariableType(ast.object.object.object.object)); + case 'value.thread.value': + case 'this.thread.value': + return 'Integer'; + case 'this.output.value': + return this.dynamicOutput ? 'Integer' : 'LiteralInteger'; + case 'this.constants.value': + return this.getConstantType(ast.property.name); + case 'this.constants.value[]': + return this.getLookupType(this.getConstantType(ast.object.property.name)); + case 'this.constants.value[][]': + return this.getLookupType(this.getConstantType(ast.object.object.property.name)); + case 'this.constants.value[][][]': + return this.getLookupType(this.getConstantType(ast.object.object.object.property.name)); + case 'this.constants.value[][][][]': + return this.getLookupType(this.getConstantType(ast.object.object.object.object.property.name)); + case 'fn()[]': + return this.getLookupType(this.getType(ast.object)); + case 'fn()[][]': + return this.getLookupType(this.getType(ast.object)); + case 'fn()[][][]': + return this.getLookupType(this.getType(ast.object)); + case 'value.value': + if (this.isAstMathVariable(ast)) { + return 'Number'; + } + switch (ast.property.name) { + case 'r': + return this.getLookupType(this.getVariableType(ast.object)); + case 'g': + return this.getLookupType(this.getVariableType(ast.object)); + case 'b': + return this.getLookupType(this.getVariableType(ast.object)); + case 'a': + return this.getLookupType(this.getVariableType(ast.object)); + } + case '[][]': + return 'Number'; + } + throw this.astErrorOutput('Unhandled getType MemberExpression', ast); + } + throw this.astErrorOutput('Unhandled getType MemberExpression', ast); + case 'ConditionalExpression': + return this.getType(ast.consequent); + case 'FunctionDeclaration': + case 'FunctionExpression': + const lastReturn = this.findLastReturn(ast.body); + if (lastReturn) { + return this.getType(lastReturn); + } + return null; + case 'IfStatement': + return this.getType(ast.consequent); + default: + throw this.astErrorOutput(`Unhandled getType Type "${ ast.type }"`, ast); + } + } + + inferArgumentTypesIfNeeded(functionName, args) { + // ensure arguments are filled in, so when we lookup return type, we already can infer it + for (let i = 0; i < args.length; i++) { + if (!this.needsArgumentType(functionName, i)) continue; + const type = this.getType(args[i]); + if (!type) { + throw this.astErrorOutput(`Unable to infer argument ${i}`, args[i]); + } + this.assignArgumentType(functionName, i, type); + } + } + + isAstMathVariable(ast) { + const mathProperties = [ + 'E', + 'PI', + 'SQRT2', + 'SQRT1_2', + 'LN2', + 'LN10', + 'LOG2E', + 'LOG10E', + ]; + return ast.type === 'MemberExpression' && + ast.object && ast.object.type === 'Identifier' && + ast.object.name === 'Math' && + ast.property && + ast.property.type === 'Identifier' && + mathProperties.indexOf(ast.property.name) > -1; + } + + isAstMathFunction(ast) { + const mathFunctions = [ + 'abs', + 'acos', + 'asin', + 'atan', + 'atan2', + 'ceil', + 'cos', + 'exp', + 'floor', + 'log', + 'log2', + 'max', + 'min', + 'pow', + 'random', + 'round', + 'sign', + 'sin', + 'sqrt', + 'tan', + ]; + return ast.type === 'CallExpression' && + ast.callee && + ast.callee.type === 'MemberExpression' && + ast.callee.object && + ast.callee.object.type === 'Identifier' && + ast.callee.object.name === 'Math' && + ast.callee.property && + ast.callee.property.type === 'Identifier' && + mathFunctions.indexOf(ast.callee.property.name) > -1; + } + + isAstVariable(ast) { + return ast.type === 'Identifier' || ast.type === 'MemberExpression'; + } + + isSafe(ast) { + return this.isSafeDependencies(this.getDependencies(ast)); + } + + isSafeDependencies(dependencies) { + return dependencies && dependencies.every ? dependencies.every(dependency => dependency.isSafe) : true; + } + + /** + * + * @param ast + * @param dependencies + * @param isNotSafe + * @return {Array} + */ + getDependencies(ast, dependencies, isNotSafe) { + if (!dependencies) { + dependencies = []; + } + if (!ast) return null; + if (Array.isArray(ast)) { + for (let i = 0; i < ast.length; i++) { + this.getDependencies(ast[i], dependencies, isNotSafe); + } + return dependencies; + } + switch (ast.type) { + case 'AssignmentExpression': + this.getDependencies(ast.left, dependencies, isNotSafe); + this.getDependencies(ast.right, dependencies, isNotSafe); + return dependencies; + case 'ConditionalExpression': + this.getDependencies(ast.test, dependencies, isNotSafe); + this.getDependencies(ast.alternate, dependencies, isNotSafe); + this.getDependencies(ast.consequent, dependencies, isNotSafe); + return dependencies; + case 'Literal': + dependencies.push({ + origin: 'literal', + value: ast.value, + isSafe: isNotSafe === true ? false : ast.value > -Infinity && ast.value < Infinity && !isNaN(ast.value) + }); + break; + case 'VariableDeclarator': + return this.getDependencies(ast.init, dependencies, isNotSafe); + case 'Identifier': + const declaration = this.getDeclaration(ast); + if (declaration) { + dependencies.push({ + name: ast.name, + origin: 'declaration', + isSafe: isNotSafe ? false : this.isSafeDependencies(declaration.dependencies), + }); + } else if (this.argumentNames.indexOf(ast.name) > -1) { + dependencies.push({ + name: ast.name, + origin: 'argument', + isSafe: false, + }); + } else if (this.strictTypingChecking) { + throw new Error(`Cannot find identifier origin "${ast.name}"`); + } + break; + case 'FunctionDeclaration': + return this.getDependencies(ast.body.body[ast.body.body.length - 1], dependencies, isNotSafe); + case 'ReturnStatement': + return this.getDependencies(ast.argument, dependencies); + case 'BinaryExpression': + isNotSafe = (ast.operator === '/' || ast.operator === '*'); + this.getDependencies(ast.left, dependencies, isNotSafe); + this.getDependencies(ast.right, dependencies, isNotSafe); + return dependencies; + case 'UnaryExpression': + case 'UpdateExpression': + return this.getDependencies(ast.argument, dependencies, isNotSafe); + case 'VariableDeclaration': + return this.getDependencies(ast.declarations, dependencies, isNotSafe); + case 'ArrayExpression': + dependencies.push({ + origin: 'declaration', + isSafe: true, + }); + return dependencies; + case 'CallExpression': + dependencies.push({ + origin: 'function', + isSafe: true, + }); + return dependencies; + case 'MemberExpression': + const details = this.getMemberExpressionDetails(ast); + switch (details.signature) { + case 'value[]': + this.getDependencies(ast.object, dependencies, isNotSafe); + break; + case 'value[][]': + this.getDependencies(ast.object.object, dependencies, isNotSafe); + break; + case 'value[][][]': + this.getDependencies(ast.object.object.object, dependencies, isNotSafe); + break; + case 'this.output.value': + if (this.dynamicOutput) { + dependencies.push({ + name: details.name, + origin: 'output', + isSafe: false, + }); + } + break; + } + if (details) { + if (details.property) { + this.getDependencies(details.property, dependencies, isNotSafe); + } + if (details.xProperty) { + this.getDependencies(details.xProperty, dependencies, isNotSafe); + } + if (details.yProperty) { + this.getDependencies(details.yProperty, dependencies, isNotSafe); + } + if (details.zProperty) { + this.getDependencies(details.zProperty, dependencies, isNotSafe); + } + return dependencies; + } + default: + throw this.astErrorOutput(`Unhandled type ${ ast.type } in getDependencies`, ast); + } + return dependencies; + } + + getVariableSignature(ast) { + if (!this.isAstVariable(ast)) { + throw new Error(`ast of type "${ ast.type }" is not a variable signature`); + } + if (ast.type === 'Identifier') { + return 'value'; + } + const signature = []; + while (true) { + if (!ast) break; + if (ast.computed) { + signature.push('[]'); + } else if (ast.type === 'ThisExpression') { + signature.unshift('this'); + } else if (ast.property && ast.property.name) { + if ( + ast.property.name === 'x' || + ast.property.name === 'y' || + ast.property.name === 'z' + ) { + signature.unshift('.value'); + } else if ( + ast.property.name === 'constants' || + ast.property.name === 'thread' || + ast.property.name === 'output' + ) { + signature.unshift('.' + ast.property.name); + } else { + signature.unshift('.value'); + } + } else if (ast.name) { + signature.unshift('value'); + } else if (ast.callee && ast.callee.name) { + signature.unshift('fn()'); + } else if (ast.elements) { + signature.unshift('[]'); + } else { + signature.unshift('unknown'); + } + ast = ast.object; + } + + const signatureString = signature.join(''); + const allowedExpressions = [ + 'value', + 'value[]', + 'value[][]', + 'value[][][]', + 'value[][][][]', + 'value.value', + 'value.thread.value', + 'this.thread.value', + 'this.output.value', + 'this.constants.value', + 'this.constants.value[]', + 'this.constants.value[][]', + 'this.constants.value[][][]', + 'this.constants.value[][][][]', + 'fn()[]', + 'fn()[][]', + 'fn()[][][]', + '[][]', + ]; + if (allowedExpressions.indexOf(signatureString) > -1) { + return signatureString; + } + return null; + } + + build() { + return this.toString().length > 0; + } + + /** + * @desc Parses the abstract syntax tree for generically to its respective function + * @param {Object} ast - the AST object to parse + * @param {Array} retArr - return array string + * @returns {Array} the parsed string array + */ + astGeneric(ast, retArr) { + if (ast === null) { + throw this.astErrorOutput('NULL ast', ast); + } else { + if (Array.isArray(ast)) { + for (let i = 0; i < ast.length; i++) { + this.astGeneric(ast[i], retArr); + } + return retArr; + } + + switch (ast.type) { + case 'FunctionDeclaration': + return this.astFunctionDeclaration(ast, retArr); + case 'FunctionExpression': + return this.astFunctionExpression(ast, retArr); + case 'ReturnStatement': + return this.astReturnStatement(ast, retArr); + case 'Literal': + return this.astLiteral(ast, retArr); + case 'BinaryExpression': + return this.astBinaryExpression(ast, retArr); + case 'Identifier': + return this.astIdentifierExpression(ast, retArr); + case 'AssignmentExpression': + return this.astAssignmentExpression(ast, retArr); + case 'ExpressionStatement': + return this.astExpressionStatement(ast, retArr); + case 'EmptyStatement': + return this.astEmptyStatement(ast, retArr); + case 'BlockStatement': + return this.astBlockStatement(ast, retArr); + case 'IfStatement': + return this.astIfStatement(ast, retArr); + case 'SwitchStatement': + return this.astSwitchStatement(ast, retArr); + case 'BreakStatement': + return this.astBreakStatement(ast, retArr); + case 'ContinueStatement': + return this.astContinueStatement(ast, retArr); + case 'ForStatement': + return this.astForStatement(ast, retArr); + case 'WhileStatement': + return this.astWhileStatement(ast, retArr); + case 'DoWhileStatement': + return this.astDoWhileStatement(ast, retArr); + case 'VariableDeclaration': + return this.astVariableDeclaration(ast, retArr); + case 'VariableDeclarator': + return this.astVariableDeclarator(ast, retArr); + case 'ThisExpression': + return this.astThisExpression(ast, retArr); + case 'SequenceExpression': + return this.astSequenceExpression(ast, retArr); + case 'UnaryExpression': + return this.astUnaryExpression(ast, retArr); + case 'UpdateExpression': + return this.astUpdateExpression(ast, retArr); + case 'LogicalExpression': + return this.astLogicalExpression(ast, retArr); + case 'MemberExpression': + return this.astMemberExpression(ast, retArr); + case 'CallExpression': + return this.astCallExpression(ast, retArr); + case 'ArrayExpression': + return this.astArrayExpression(ast, retArr); + case 'DebuggerStatement': + return this.astDebuggerStatement(ast, retArr); + case 'ConditionalExpression': + return this.astConditionalExpression(ast, retArr); + } + + throw this.astErrorOutput('Unknown ast type : ' + ast.type, ast); + } + } + /** + * @desc To throw the AST error, with its location. + * @param {string} error - the error message output + * @param {Object} ast - the AST object where the error is + */ + astErrorOutput(error, ast) { + if (typeof this.source !== 'string') { + return new Error(error); + } + + const debugString = getAstString(this.source, ast); + const leadingSource = this.source.substr(ast.start); + const splitLines = leadingSource.split(/\n/); + const lineBefore = splitLines.length > 0 ? splitLines[splitLines.length - 1] : 0; + return new Error(`${error} on line ${ splitLines.length }, position ${ lineBefore.length }:\n ${ debugString }`); + } + + astDebuggerStatement(arrNode, retArr) { + return retArr; + } + + astConditionalExpression(ast, retArr) { + if (ast.type !== 'ConditionalExpression') { + throw this.astErrorOutput('Not a conditional expression', ast); + } + retArr.push('('); + this.astGeneric(ast.test, retArr); + retArr.push('?'); + this.astGeneric(ast.consequent, retArr); + retArr.push(':'); + this.astGeneric(ast.alternate, retArr); + retArr.push(')'); + return retArr; + } + + /** + * @abstract + * @param {Object} ast + * @param {String[]} retArr + * @returns {String[]} + */ + astFunction(ast, retArr) { + throw new Error(`"astFunction" not defined on ${ this.constructor.name }`); + } + + /** + * @desc Parses the abstract syntax tree for to its *named function declaration* + * @param {Object} ast - the AST object to parse + * @param {Array} retArr - return array string + * @returns {Array} the append retArr + */ + astFunctionDeclaration(ast, retArr) { + if (this.isChildFunction(ast)) { + return retArr; + } + return this.astFunction(ast, retArr); + } + astFunctionExpression(ast, retArr) { + if (this.isChildFunction(ast)) { + return retArr; + } + return this.astFunction(ast, retArr); + } + isChildFunction(ast) { + for (let i = 0; i < this.functions.length; i++) { + if (this.functions[i] === ast) { + return true; + } + } + return false; + } + astReturnStatement(ast, retArr) { + return retArr; + } + astLiteral(ast, retArr) { + this.literalTypes[`${ast.start},${ast.end}`] = 'Number'; + return retArr; + } + astBinaryExpression(ast, retArr) { + return retArr; + } + astIdentifierExpression(ast, retArr) { + return retArr; + } + astAssignmentExpression(ast, retArr) { + return retArr; + } + /** + * @desc Parses the abstract syntax tree for *generic expression* statement + * @param {Object} esNode - An ast Node + * @param {Array} retArr - return array string + * @returns {Array} the append retArr + */ + astExpressionStatement(esNode, retArr) { + this.astGeneric(esNode.expression, retArr); + retArr.push(';'); + return retArr; + } + /** + * @desc Parses the abstract syntax tree for an *Empty* Statement + * @param {Object} eNode - An ast Node + * @param {Array} retArr - return array string + * @returns {Array} the append retArr + */ + astEmptyStatement(eNode, retArr) { + return retArr; + } + astBlockStatement(ast, retArr) { + return retArr; + } + astIfStatement(ast, retArr) { + return retArr; + } + astSwitchStatement(ast, retArr) { + return retArr; + } + /** + * @desc Parses the abstract syntax tree for *Break* Statement + * @param {Object} brNode - An ast Node + * @param {Array} retArr - return array string + * @returns {Array} the append retArr + */ + astBreakStatement(brNode, retArr) { + retArr.push('break;'); + return retArr; + } + /** + * @desc Parses the abstract syntax tree for *Continue* Statement + * @param {Object} crNode - An ast Node + * @param {Array} retArr - return array string + * @returns {Array} the append retArr + */ + astContinueStatement(crNode, retArr) { + retArr.push('continue;\n'); + return retArr; + } + astForStatement(ast, retArr) { + return retArr; + } + astWhileStatement(ast, retArr) { + return retArr; + } + astDoWhileStatement(ast, retArr) { + return retArr; + } + /** + * @desc Parses the abstract syntax tree for *Variable Declaration* + * @param {Object} varDecNode - An ast Node + * @param {Array} retArr - return array string + * @returns {Array} the append retArr + */ + astVariableDeclaration(varDecNode, retArr) { + const declarations = varDecNode.declarations; + if (!declarations || !declarations[0] || !declarations[0].init) { + throw this.astErrorOutput('Unexpected expression', varDecNode); + } + const result = []; + const firstDeclaration = declarations[0]; + const init = firstDeclaration.init; + let type = this.isState('in-for-loop-init') ? 'Integer' : this.getType(init); + if (type === 'LiteralInteger') { + // We had the choice to go either float or int, choosing float + type = 'Number'; + } + const markupType = typeMap[type]; + if (!markupType) { + throw this.astErrorOutput(`Markup type ${ markupType } not handled`, varDecNode); + } + let dependencies = this.getDependencies(firstDeclaration.init); + throw new Error('remove me'); + this.declarations[firstDeclaration.id.name] = Object.freeze({ + type, + dependencies, + isSafe: dependencies.every(dependency => dependency.isSafe) + }); + const initResult = [`${type} user_${firstDeclaration.id.name}=`]; + this.astGeneric(init, initResult); + result.push(initResult.join('')); + + // first declaration is done, now any added ones setup + for (let i = 1; i < declarations.length; i++) { + const declaration = declarations[i]; + dependencies = this.getDependencies(declaration); + throw new Error('Remove me'); + this.declarations[declaration.id.name] = Object.freeze({ + type, + dependencies, + isSafe: false + }); + this.astGeneric(declaration, result); + } + + retArr.push(retArr, result.join(',')); + retArr.push(';'); + return retArr; + } + /** + * @desc Parses the abstract syntax tree for *Variable Declarator* + * @param {Object} iVarDecNode - An ast Node + * @param {Array} retArr - return array string + * @returns {Array} the append retArr + */ + astVariableDeclarator(iVarDecNode, retArr) { + this.astGeneric(iVarDecNode.id, retArr); + if (iVarDecNode.init !== null) { + retArr.push('='); + this.astGeneric(iVarDecNode.init, retArr); + } + return retArr; + } + astThisExpression(ast, retArr) { + return retArr; + } + astSequenceExpression(sNode, retArr) { + for (let i = 0; i < sNode.expressions.length; i++) { + if (i > 0) { + retArr.push(','); + } + this.astGeneric(sNode.expressions, retArr); + } + return retArr; + } + /** + * @desc Parses the abstract syntax tree for *Unary* Expression + * @param {Object} uNode - An ast Node + * @param {Array} retArr - return array string + * @returns {Array} the append retArr + */ + astUnaryExpression(uNode, retArr) { + const unaryResult = this.checkAndUpconvertBitwiseUnary(uNode, retArr); + if (unaryResult) { + return retArr; + } + + if (uNode.prefix) { + retArr.push(uNode.operator); + this.astGeneric(uNode.argument, retArr); + } else { + this.astGeneric(uNode.argument, retArr); + retArr.push(uNode.operator); + } + + return retArr; + } + + checkAndUpconvertBitwiseUnary(uNode, retArr) {} + + /** + * @desc Parses the abstract syntax tree for *Update* Expression + * @param {Object} uNode - An ast Node + * @param {Array} retArr - return array string + * @returns {Array} the append retArr + */ + astUpdateExpression(uNode, retArr) { + if (uNode.prefix) { + retArr.push(uNode.operator); + this.astGeneric(uNode.argument, retArr); + } else { + this.astGeneric(uNode.argument, retArr); + retArr.push(uNode.operator); + } + + return retArr; + } + /** + * @desc Parses the abstract syntax tree for *Logical* Expression + * @param {Object} logNode - An ast Node + * @param {Array} retArr - return array string + * @returns {Array} the append retArr + */ + astLogicalExpression(logNode, retArr) { + retArr.push('('); + this.astGeneric(logNode.left, retArr); + retArr.push(logNode.operator); + this.astGeneric(logNode.right, retArr); + retArr.push(')'); + return retArr; + } + astMemberExpression(ast, retArr) { + return retArr; + } + astCallExpression(ast, retArr) { + return retArr; + } + astArrayExpression(ast, retArr) { + return retArr; + } + + /** + * + * @param ast + * @return {IFunctionNodeMemberExpressionDetails} + */ + getMemberExpressionDetails(ast) { + if (ast.type !== 'MemberExpression') { + throw this.astErrorOutput(`Expression ${ ast.type } not a MemberExpression`, ast); + } + let name = null; + let type = null; + const variableSignature = this.getVariableSignature(ast); + switch (variableSignature) { + case 'value': + return null; + case 'value.thread.value': + case 'this.thread.value': + case 'this.output.value': + return { + signature: variableSignature, + type: 'Integer', + name: ast.property.name + }; + case 'value[]': + if (typeof ast.object.name !== 'string') { + throw this.astErrorOutput('Unexpected expression', ast); + } + name = ast.object.name; + return { + name, + origin: 'user', + signature: variableSignature, + type: this.getVariableType(ast.object), + xProperty: ast.property + }; + case 'value[][]': + if (typeof ast.object.object.name !== 'string') { + throw this.astErrorOutput('Unexpected expression', ast); + } + name = ast.object.object.name; + return { + name, + origin: 'user', + signature: variableSignature, + type: this.getVariableType(ast.object.object), + yProperty: ast.object.property, + xProperty: ast.property, + }; + case 'value[][][]': + if (typeof ast.object.object.object.name !== 'string') { + throw this.astErrorOutput('Unexpected expression', ast); + } + name = ast.object.object.object.name; + return { + name, + origin: 'user', + signature: variableSignature, + type: this.getVariableType(ast.object.object.object), + zProperty: ast.object.object.property, + yProperty: ast.object.property, + xProperty: ast.property, + }; + case 'value[][][][]': + if (typeof ast.object.object.object.object.name !== 'string') { + throw this.astErrorOutput('Unexpected expression', ast); + } + name = ast.object.object.object.object.name; + return { + name, + origin: 'user', + signature: variableSignature, + type: this.getVariableType(ast.object.object.object.object), + zProperty: ast.object.object.property, + yProperty: ast.object.property, + xProperty: ast.property, + }; + case 'value.value': + if (typeof ast.property.name !== 'string') { + throw this.astErrorOutput('Unexpected expression', ast); + } + if (this.isAstMathVariable(ast)) { + name = ast.property.name; + return { + name, + origin: 'Math', + type: 'Number', + signature: variableSignature, + }; + } + switch (ast.property.name) { + case 'r': + case 'g': + case 'b': + case 'a': + name = ast.object.name; + return { + name, + property: ast.property.name, + origin: 'user', + signature: variableSignature, + type: 'Number' + }; + default: + throw this.astErrorOutput('Unexpected expression', ast); + } + case 'this.constants.value': + if (typeof ast.property.name !== 'string') { + throw this.astErrorOutput('Unexpected expression', ast); + } + name = ast.property.name; + type = this.getConstantType(name); + if (!type) { + throw this.astErrorOutput('Constant has no type', ast); + } + return { + name, + type, + origin: 'constants', + signature: variableSignature, + }; + case 'this.constants.value[]': + if (typeof ast.object.property.name !== 'string') { + throw this.astErrorOutput('Unexpected expression', ast); + } + name = ast.object.property.name; + type = this.getConstantType(name); + if (!type) { + throw this.astErrorOutput('Constant has no type', ast); + } + return { + name, + type, + origin: 'constants', + signature: variableSignature, + xProperty: ast.property, + }; + case 'this.constants.value[][]': { + if (typeof ast.object.object.property.name !== 'string') { + throw this.astErrorOutput('Unexpected expression', ast); + } + name = ast.object.object.property.name; + type = this.getConstantType(name); + if (!type) { + throw this.astErrorOutput('Constant has no type', ast); + } + return { + name, + type, + origin: 'constants', + signature: variableSignature, + yProperty: ast.object.property, + xProperty: ast.property, + }; + } + case 'this.constants.value[][][]': { + if (typeof ast.object.object.object.property.name !== 'string') { + throw this.astErrorOutput('Unexpected expression', ast); + } + name = ast.object.object.object.property.name; + type = this.getConstantType(name); + if (!type) { + throw this.astErrorOutput('Constant has no type', ast); + } + return { + name, + type, + origin: 'constants', + signature: variableSignature, + zProperty: ast.object.object.property, + yProperty: ast.object.property, + xProperty: ast.property, + }; + } + case 'fn()[]': + case '[][]': + return { + signature: variableSignature, + property: ast.property, + }; + default: + throw this.astErrorOutput('Unexpected expression', ast); + } + } + + findIdentifierOrigin(astToFind) { + const stack = [this.ast]; + + while (stack.length > 0) { + const atNode = stack[0]; + if (atNode.type === 'VariableDeclarator' && atNode.id && atNode.id.name && atNode.id.name === astToFind.name) { + return atNode; + } + stack.shift(); + if (atNode.argument) { + stack.push(atNode.argument); + } else if (atNode.body) { + stack.push(atNode.body); + } else if (atNode.declarations) { + stack.push(atNode.declarations); + } else if (Array.isArray(atNode)) { + for (let i = 0; i < atNode.length; i++) { + stack.push(atNode[i]); + } + } + } + return null; + } + + findLastReturn(ast) { + const stack = [ast || this.ast]; + + while (stack.length > 0) { + const atNode = stack.pop(); + if (atNode.type === 'ReturnStatement') { + return atNode; + } + if (atNode.type === 'FunctionDeclaration') { + continue; + } + if (atNode.argument) { + stack.push(atNode.argument); + } else if (atNode.body) { + stack.push(atNode.body); + } else if (atNode.declarations) { + stack.push(atNode.declarations); + } else if (Array.isArray(atNode)) { + for (let i = 0; i < atNode.length; i++) { + stack.push(atNode[i]); + } + } else if (atNode.consequent) { + stack.push(atNode.consequent); + } else if (atNode.cases) { + stack.push(atNode.cases); + } + } + return null; + } + + getInternalVariableName(name) { + if (!this._internalVariableNames.hasOwnProperty(name)) { + this._internalVariableNames[name] = 0; + } + this._internalVariableNames[name]++; + if (this._internalVariableNames[name] === 1) { + return name; + } + return name + this._internalVariableNames[name]; + } + + varWarn() { + console.warn('var declarations are deprecated, weird things happen when falling back to CPU because var scope differs in javascript than in most languages. Use const or let'); + } +} + +const typeLookupMap = { + 'Number': 'Number', + 'Float': 'Float', + 'Integer': 'Integer', + 'Array': 'Number', + 'Array(2)': 'Number', + 'Array(3)': 'Number', + 'Array(4)': 'Number', + 'Array2D': 'Number', + 'Array3D': 'Number', + 'Input': 'Number', + 'HTMLImage': 'Array(4)', + 'HTMLVideo': 'Array(4)', + 'HTMLImageArray': 'Array(4)', + 'NumberTexture': 'Number', + 'MemoryOptimizedNumberTexture': 'Number', + 'Array1D(2)': 'Array(2)', + 'Array1D(3)': 'Array(3)', + 'Array1D(4)': 'Array(4)', + 'Array2D(2)': 'Array(2)', + 'Array2D(3)': 'Array(3)', + 'Array2D(4)': 'Array(4)', + 'Array3D(2)': 'Array(2)', + 'Array3D(3)': 'Array(3)', + 'Array3D(4)': 'Array(4)', + 'ArrayTexture(1)': 'Number', + 'ArrayTexture(2)': 'Array(2)', + 'ArrayTexture(3)': 'Array(3)', + 'ArrayTexture(4)': 'Array(4)', +}; diff --git a/src/backend/function-tracer.js b/src/backend/function-tracer.js index a464a796..38a59358 100644 --- a/src/backend/function-tracer.js +++ b/src/backend/function-tracer.js @@ -1,167 +1,163 @@ -class FunctionTracer { - constructor(ast) { - this.runningContexts = []; - this.contexts = []; - this.functionCalls = []; - this.declarations = []; - this.identifiers = []; - this.functions = []; - this.returnStatements = []; - this.inLoopInit = false; - this.scan(ast); - } - - get currentContext() { - return this.runningContexts.length > 0 ? this.runningContexts[this.runningContexts.length - 1] : null; - } - - newContext(run) { - const newContext = Object.assign({}, this.currentContext); - this.contexts.push(newContext); - this.runningContexts.push(newContext); - run(); - this.runningContexts.pop(); - } - - /** - * Recursively scans AST for declarations and functions, and add them to their respective context - * @param ast - */ - scan(ast) { - if (!ast) return; - if (Array.isArray(ast)) { - for (let i = 0; i < ast.length; i++) { - this.scan(ast[i]); - } - return; - } - switch (ast.type) { - case 'Program': - this.scan(ast.body); - break; - case 'BlockStatement': - this.newContext(() => { - this.scan(ast.body); - }); - break; - case 'AssignmentExpression': - case 'LogicalExpression': - this.scan(ast.left); - this.scan(ast.right); - break; - case 'BinaryExpression': - this.scan(ast.left); - this.scan(ast.right); - break; - case 'UpdateExpression': - case 'UnaryExpression': - this.scan(ast.argument); - break; - case 'VariableDeclaration': - this.scan(ast.declarations); - break; - case 'VariableDeclarator': - const { currentContext } = this; - const declaration = { - ast: ast, - context: currentContext, - name: ast.id.name, - origin: 'declaration', - forceInteger: this.inLoopInit, - assignable: !this.inLoopInit && !currentContext.hasOwnProperty(ast.id.name), - }; - currentContext[ast.id.name] = declaration; - this.declarations.push(declaration); - this.scan(ast.id); - this.scan(ast.init); - break; - case 'FunctionExpression': - case 'FunctionDeclaration': - if (this.runningContexts.length === 0) { - this.scan(ast.body); - } else { - this.functions.push(ast); - } - break; - case 'IfStatement': - this.scan(ast.test); - this.scan(ast.consequent); - if (ast.alternate) this.scan(ast.alternate); - break; - case 'ForStatement': - this.newContext(() => { - this.inLoopInit = true; - this.scan(ast.init); - this.inLoopInit = false; - this.scan(ast.test); - this.scan(ast.update); - this.newContext(() => { - this.scan(ast.body); - }); - }); - break; - case 'DoWhileStatement': - case 'WhileStatement': - this.newContext(() => { - this.scan(ast.body); - this.scan(ast.test); - }); - break; - case 'Identifier': - this.identifiers.push({ - context: this.currentContext, - ast, - }); - break; - case 'ReturnStatement': - this.returnStatements.push(ast); - this.scan(ast.argument); - break; - case 'MemberExpression': - this.scan(ast.object); - this.scan(ast.property); - break; - case 'ExpressionStatement': - this.scan(ast.expression); - break; - case 'CallExpression': - this.functionCalls.push({ - context: this.currentContext, - ast, - }); - this.scan(ast.arguments); - break; - case 'ArrayExpression': - this.scan(ast.elements); - break; - case 'ConditionalExpression': - this.scan(ast.test); - this.scan(ast.alternate); - this.scan(ast.consequent); - break; - case 'SwitchStatement': - this.scan(ast.discriminant); - this.scan(ast.cases); - break; - case 'SwitchCase': - this.scan(ast.test); - this.scan(ast.consequent); - break; - - case 'ThisExpression': - case 'Literal': - case 'DebuggerStatement': - case 'EmptyStatement': - case 'BreakStatement': - case 'ContinueStatement': - break; - - default: - throw new Error(`unhandled type "${ast.type}"`); - } - } -} - -module.exports = { - FunctionTracer, -}; \ No newline at end of file +export class FunctionTracer { + constructor(ast) { + this.runningContexts = []; + this.contexts = []; + this.functionCalls = []; + this.declarations = []; + this.identifiers = []; + this.functions = []; + this.returnStatements = []; + this.inLoopInit = false; + this.scan(ast); + } + + get currentContext() { + return this.runningContexts.length > 0 ? this.runningContexts[this.runningContexts.length - 1] : null; + } + + newContext(run) { + const newContext = Object.assign({}, this.currentContext); + this.contexts.push(newContext); + this.runningContexts.push(newContext); + run(); + this.runningContexts.pop(); + } + + /** + * Recursively scans AST for declarations and functions, and add them to their respective context + * @param ast + */ + scan(ast) { + if (Array.isArray(ast)) { + for (let i = 0; i < ast.length; i++) { + this.scan(ast[i]); + } + return; + } + switch (ast.type) { + case 'Program': + this.scan(ast.body); + break; + case 'BlockStatement': + this.newContext(() => { + this.scan(ast.body); + }); + break; + case 'AssignmentExpression': + case 'LogicalExpression': + this.scan(ast.left); + this.scan(ast.right); + break; + case 'BinaryExpression': + this.scan(ast.left); + this.scan(ast.right); + break; + case 'UpdateExpression': + case 'UnaryExpression': + this.scan(ast.argument); + break; + case 'VariableDeclaration': + this.scan(ast.declarations); + break; + case 'VariableDeclarator': + const { currentContext } = this; + const declaration = { + ast: ast, + context: currentContext, + name: ast.id.name, + origin: 'declaration', + forceInteger: this.inLoopInit, + assignable: !this.inLoopInit && !currentContext.hasOwnProperty(ast.id.name), + }; + currentContext[ast.id.name] = declaration; + this.declarations.push(declaration); + this.scan(ast.id); + this.scan(ast.init); + break; + case 'FunctionExpression': + case 'FunctionDeclaration': + if (this.runningContexts.length === 0) { + this.scan(ast.body); + } else { + this.functions.push(ast); + } + break; + case 'IfStatement': + this.scan(ast.test); + this.scan(ast.consequent); + if (ast.alternate) this.scan(ast.alternate); + break; + case 'ForStatement': + this.newContext(() => { + this.inLoopInit = true; + this.scan(ast.init); + this.inLoopInit = false; + this.scan(ast.test); + this.scan(ast.update); + this.newContext(() => { + this.scan(ast.body); + }); + }); + break; + case 'DoWhileStatement': + case 'WhileStatement': + this.newContext(() => { + this.scan(ast.body); + this.scan(ast.test); + }); + break; + case 'Identifier': + this.identifiers.push({ + context: this.currentContext, + ast, + }); + break; + case 'ReturnStatement': + this.returnStatements.push(ast); + this.scan(ast.argument); + break; + case 'MemberExpression': + this.scan(ast.object); + this.scan(ast.property); + break; + case 'ExpressionStatement': + this.scan(ast.expression); + break; + case 'CallExpression': + this.functionCalls.push({ + context: this.currentContext, + ast, + }); + this.scan(ast.arguments); + break; + case 'ArrayExpression': + this.scan(ast.elements); + break; + case 'ConditionalExpression': + this.scan(ast.test); + this.scan(ast.alternate); + this.scan(ast.consequent); + break; + case 'SwitchStatement': + this.scan(ast.discriminant); + this.scan(ast.cases); + break; + case 'SwitchCase': + this.scan(ast.test); + this.scan(ast.consequent); + break; + case 'ThisExpression': + this.scan(ast.left); + this.scan(ast.right); + break; + case 'Literal': + case 'DebuggerStatement': + case 'EmptyStatement': + case 'BreakStatement': + case 'ContinueStatement': + break; + default: + throw new Error(`unhandled type "${ast.type}"`); + } + } +} diff --git a/src/backend/gl/kernel-string.js b/src/backend/gl/kernel-string.js index db5b7a12..7f21dad6 100644 --- a/src/backend/gl/kernel-string.js +++ b/src/backend/gl/kernel-string.js @@ -1,341 +1,337 @@ -const { glWiretap } = require('gl-wiretap'); -const { utils } = require('../../utils'); - -function toStringWithoutUtils(fn) { - return fn.toString() - .replace('=>', '') - .replace(/^function /, '') - .replace(/utils[.]/g, '/*utils.*/'); -} - -/** - * - * @param {Kernel} Kernel - * @param {KernelVariable[]} args - * @param {Kernel} originKernel - * @param {string} [setupContextString] - * @param {string} [destroyContextString] - * @returns {string} - */ -function glKernelString(Kernel, args, originKernel, setupContextString, destroyContextString) { - args = args ? Array.from(args).map(arg => { - switch (typeof arg) { - case 'boolean': - return new Boolean(arg); - case 'number': - return new Number(arg); - default: - return arg; - } - }) : null; - const uploadedValues = []; - const postResult = []; - const context = glWiretap(originKernel.context, { - useTrackablePrimitives: true, - onReadPixels: (targetName) => { - if (kernel.subKernels) { - if (!subKernelsResultVariableSetup) { - postResult.push(` const result = { result: ${getRenderString(targetName, kernel)} };`); - subKernelsResultVariableSetup = true; - } else { - const property = kernel.subKernels[subKernelsResultIndex++].property; - postResult.push(` result${isNaN(property) ? '.' + property : `[${property}]`} = ${getRenderString(targetName, kernel)};`); - } - if (subKernelsResultIndex === kernel.subKernels.length) { - postResult.push(' return result;'); - } - return; - } - if (targetName) { - postResult.push(` return ${getRenderString(targetName, kernel)};`); - } else { - postResult.push(` return null;`); - } - }, - onUnrecognizedArgumentLookup: (argument) => { - const argumentName = findKernelValue(argument, kernel.kernelArguments, [], context, uploadedValues); - if (argumentName) { - return argumentName; - } - const constantName = findKernelValue(argument, kernel.kernelConstants, constants ? Object.keys(constants).map(key => constants[key]) : [], context, uploadedValues); - if (constantName) { - return constantName; - } - return null; - } - }); - let subKernelsResultVariableSetup = false; - let subKernelsResultIndex = 0; - const { - source, - canvas, - output, - pipeline, - graphical, - loopMaxIterations, - constants, - optimizeFloatMemory, - precision, - fixIntegerDivisionAccuracy, - functions, - nativeFunctions, - subKernels, - immutable, - argumentTypes, - constantTypes, - kernelArguments, - kernelConstants, - } = originKernel; - const kernel = new Kernel(source, { - canvas, - context, - checkContext: false, - output, - pipeline, - graphical, - loopMaxIterations, - constants, - optimizeFloatMemory, - precision, - fixIntegerDivisionAccuracy, - functions, - nativeFunctions, - subKernels, - immutable, - argumentTypes, - constantTypes, - }); - let result = []; - context.setIndent(2); - kernel.build.apply(kernel, args); - result.push(context.toString()); - context.reset(); - - kernel.kernelArguments.forEach((kernelArgument, i) => { - switch (kernelArgument.type) { - // primitives - case 'Integer': - case 'Boolean': - case 'Number': - case 'Float': - // non-primitives - case 'Array': - case 'Array(2)': - case 'Array(3)': - case 'Array(4)': - case 'HTMLImage': - case 'HTMLVideo': - context.insertVariable(`uploadValue_${kernelArgument.name}`, kernelArgument.uploadValue); - break; - case 'HTMLImageArray': - for (let imageIndex = 0; imageIndex < args[i].length; imageIndex++) { - const arg = args[i]; - context.insertVariable(`uploadValue_${kernelArgument.name}[${imageIndex}]`, arg[imageIndex]); - } - break; - case 'Input': - context.insertVariable(`uploadValue_${kernelArgument.name}`, kernelArgument.uploadValue); - break; - case 'MemoryOptimizedNumberTexture': - case 'NumberTexture': - case 'Array1D(2)': - case 'Array1D(3)': - case 'Array1D(4)': - case 'Array2D(2)': - case 'Array2D(3)': - case 'Array2D(4)': - case 'Array3D(2)': - case 'Array3D(3)': - case 'Array3D(4)': - case 'ArrayTexture(1)': - case 'ArrayTexture(2)': - case 'ArrayTexture(3)': - case 'ArrayTexture(4)': - context.insertVariable(`uploadValue_${kernelArgument.name}`, args[i].texture); - break; - default: - throw new Error(`unhandled kernelArgumentType insertion for glWiretap of type ${kernelArgument.type}`); - } - }); - result.push('/** start of injected functions **/'); - result.push(`function ${toStringWithoutUtils(utils.flattenTo)}`); - result.push(`function ${toStringWithoutUtils(utils.flatten2dArrayTo)}`); - result.push(`function ${toStringWithoutUtils(utils.flatten3dArrayTo)}`); - result.push(`function ${toStringWithoutUtils(utils.flatten4dArrayTo)}`); - result.push(`function ${toStringWithoutUtils(utils.isArray)}`); - if (kernel.renderOutput !== kernel.renderTexture && kernel.formatValues) { - result.push( - ` const renderOutput = function ${toStringWithoutUtils(kernel.formatValues)};` - ); - } - result.push('/** end of injected functions **/'); - result.push(` const innerKernel = function (${kernel.kernelArguments.map(kernelArgument => kernelArgument.varName).join(', ')}) {`); - context.setIndent(4); - kernel.run.apply(kernel, args); - if (kernel.renderKernels) { - kernel.renderKernels(); - } else if (kernel.renderOutput) { - kernel.renderOutput(); - } - result.push(' /** start setup uploads for kernel values **/'); - kernel.kernelArguments.forEach(kernelArgument => { - result.push(' ' + kernelArgument.getStringValueHandler().split('\n').join('\n ')); - }); - result.push(' /** end setup uploads for kernel values **/'); - result.push(context.toString()); - if (kernel.renderOutput === kernel.renderTexture) { - context.reset(); - const results = kernel.renderKernels(); - const textureName = context.getContextVariableName(kernel.outputTexture); - result.push(` return { - result: { - texture: ${ textureName }, - type: '${ results.result.type }', - toArray: ${ getToArrayString(results.result, textureName) } - },`); - const { subKernels, subKernelOutputTextures } = kernel; - for (let i = 0; i < subKernels.length; i++) { - const texture = subKernelOutputTextures[i]; - const subKernel = subKernels[i]; - const subKernelResult = results[subKernel.property]; - const subKernelTextureName = context.getContextVariableName(texture); - result.push(` - ${subKernel.property}: { - texture: ${ subKernelTextureName }, - type: '${ subKernelResult.type }', - toArray: ${ getToArrayString(subKernelResult, subKernelTextureName) } - },`); - } - result.push(` };`); - } - result.push(` ${destroyContextString ? '\n' + destroyContextString + ' ': ''}`); - result.push(postResult.join('\n')); - result.push(' };'); - if (kernel.graphical) { - result.push(getGetPixelsString(kernel)); - result.push(` innerKernel.getPixels = getPixels;`); - } - result.push(' return innerKernel;'); - - let constantsUpload = []; - kernelConstants.forEach((kernelConstant) => { - constantsUpload.push(`${ kernelConstant.getStringValueHandler()}`); - }); - return `function kernel(settings) { - const { context, constants } = settings; - ${constantsUpload.join('')} - ${setupContextString ? setupContextString : ''} -${result.join('\n')} -}`; -} - -function getRenderString(targetName, kernel) { - const readBackValue = kernel.precision === 'single' ? targetName : `new Float32Array(${targetName}.buffer)`; - if (kernel.output[2]) { - return `renderOutput(${readBackValue}, ${kernel.output[0]}, ${kernel.output[1]}, ${kernel.output[2]})`; - } - if (kernel.output[1]) { - return `renderOutput(${readBackValue}, ${kernel.output[0]}, ${kernel.output[1]})`; - } - - return `renderOutput(${readBackValue}, ${kernel.output[0]})`; -} - -function getGetPixelsString(kernel) { - const getPixels = kernel.getPixels.toString(); - const useFunctionKeyword = !/^function/.test(getPixels); - return utils.flattenFunctionToString(`${useFunctionKeyword ? 'function ' : ''}${ getPixels }`, { - findDependency: (object, name) => { - if (object === 'utils') { - return `const ${name} = ${utils[name].toString()};`; - } - return null; - }, - thisLookup: (property) => { - if (property === 'context') { - return null; - } - if (kernel.hasOwnProperty(property)) { - return JSON.stringify(kernel[property]); - } - throw new Error(`unhandled thisLookup ${ property }`); - } - }); -} - -function getToArrayString(kernelResult, textureName) { - const toArray = kernelResult.toArray.toString(); - const useFunctionKeyword = !/^function/.test(toArray); - const flattenedFunctions = utils.flattenFunctionToString(`${useFunctionKeyword ? 'function ' : ''}${ toArray }`, { - findDependency: (object, name) => { - if (object === 'utils') { - return `const ${name} = ${utils[name].toString()};`; - } else if (object === 'this') { - return `${useFunctionKeyword ? 'function ' : ''}${kernelResult[name].toString()}`; - } else { - throw new Error('unhandled fromObject'); - } - }, - thisLookup: (property) => { - if (property === 'texture') { - return textureName; - } - if (kernelResult.hasOwnProperty(property)) { - return JSON.stringify(kernelResult[property]); - } - throw new Error(`unhandled thisLookup ${ property }`); - } - }); - return `() => { - ${flattenedFunctions} - return toArray(); - }`; -} - -/** - * - * @param {KernelVariable} argument - * @param {KernelValue[]} kernelValues - * @param {KernelVariable[]} values - * @param context - * @param {KernelVariable[]} uploadedValues - * @return {string|null} - */ -function findKernelValue(argument, kernelValues, values, context, uploadedValues) { - if (argument === null) return null; - switch (typeof argument) { - case 'boolean': - case 'number': - return null; - } - if ( - typeof HTMLImageElement !== 'undefined' && - argument instanceof HTMLImageElement - ) { - for (let i = 0; i < kernelValues.length; i++) { - const kernelValue = kernelValues[i]; - if (kernelValue.type !== 'HTMLImageArray') continue; - if (kernelValue.uploadValue !== argument) continue; - // TODO: if we send two of the same image, the parser could get confused, and short circuit to the first, handle that here - const variableIndex = values[i].indexOf(argument); - if (variableIndex === -1) continue; - const variableName = `uploadValue_${kernelValue.name}[${variableIndex}]`; - context.insertVariable(variableName, argument); - return variableName; - } - return null; - } - - for (let i = 0; i < kernelValues.length; i++) { - const kernelValue = kernelValues[i]; - if (argument !== kernelValue.uploadValue) continue; - const variable = `uploadValue_${kernelValue.name}`; - context.insertVariable(variable, kernelValue); - return variable; - } - return null; -} - -module.exports = { - glKernelString -}; \ No newline at end of file +import { glWiretap } from 'gl-wiretap'; +import { utils } from '../../utils'; + +function toStringWithoutUtils(fn) { + return fn.toString() + .replace('=>', '') + .replace(/^function /, '') + .replace(/utils[.]/g, '/*utils.*/'); +} + +/** + * + * @param {Kernel} Kernel + * @param {KernelVariable[]} args + * @param {Kernel} originKernel + * @param {string} [setupContextString] + * @param {string} [destroyContextString] + * @returns {string} + */ +export function glKernelString(Kernel, args, originKernel, setupContextString, destroyContextString) { + args = args ? Array.from(args).map(arg => { + switch (typeof arg) { + case 'boolean': + return new Boolean(arg); + case 'number': + return new Number(arg); + default: + return arg; + } + }) : null; + const uploadedValues = []; + const postResult = []; + const context = glWiretap(originKernel.context, { + useTrackablePrimitives: true, + onReadPixels: (targetName) => { + if (kernel.subKernels) { + if (!subKernelsResultVariableSetup) { + postResult.push(` const result = { result: ${getRenderString(targetName, kernel)} };`); + subKernelsResultVariableSetup = true; + } else { + const property = kernel.subKernels[subKernelsResultIndex++].property; + postResult.push(` result${isNaN(property) ? '.' + property : `[${property}]`} = ${getRenderString(targetName, kernel)};`); + } + if (subKernelsResultIndex === kernel.subKernels.length) { + postResult.push(' return result;'); + } + return; + } + if (targetName) { + postResult.push(` return ${getRenderString(targetName, kernel)};`); + } else { + postResult.push(` return null;`); + } + }, + onUnrecognizedArgumentLookup: (argument) => { + const argumentName = findKernelValue(argument, kernel.kernelArguments, [], context, uploadedValues); + if (argumentName) { + return argumentName; + } + const constantName = findKernelValue(argument, kernel.kernelConstants, constants ? Object.keys(constants).map(key => constants[key]) : [], context, uploadedValues); + if (constantName) { + return constantName; + } + return null; + } + }); + let subKernelsResultVariableSetup = false; + let subKernelsResultIndex = 0; + const { + source, + canvas, + output, + pipeline, + graphical, + loopMaxIterations, + constants, + optimizeFloatMemory, + precision, + fixIntegerDivisionAccuracy, + functions, + nativeFunctions, + subKernels, + immutable, + argumentTypes, + constantTypes, + kernelArguments, + kernelConstants, + } = originKernel; + const kernel = new Kernel(source, { + canvas, + context, + checkContext: false, + output, + pipeline, + graphical, + loopMaxIterations, + constants, + optimizeFloatMemory, + precision, + fixIntegerDivisionAccuracy, + functions, + nativeFunctions, + subKernels, + immutable, + argumentTypes, + constantTypes, + }); + let result = []; + context.setIndent(2); + kernel.build.apply(kernel, args); + result.push(context.toString()); + context.reset(); + + kernel.kernelArguments.forEach((kernelArgument, i) => { + switch (kernelArgument.type) { + // primitives + case 'Integer': + case 'Boolean': + case 'Number': + case 'Float': + // non-primitives + case 'Array': + case 'Array(2)': + case 'Array(3)': + case 'Array(4)': + case 'HTMLImage': + case 'HTMLVideo': + context.insertVariable(`uploadValue_${kernelArgument.name}`, kernelArgument.uploadValue); + break; + case 'HTMLImageArray': + for (let imageIndex = 0; imageIndex < args[i].length; imageIndex++) { + const arg = args[i]; + context.insertVariable(`uploadValue_${kernelArgument.name}[${imageIndex}]`, arg[imageIndex]); + } + break; + case 'Input': + context.insertVariable(`uploadValue_${kernelArgument.name}`, kernelArgument.uploadValue); + break; + case 'MemoryOptimizedNumberTexture': + case 'NumberTexture': + case 'Array1D(2)': + case 'Array1D(3)': + case 'Array1D(4)': + case 'Array2D(2)': + case 'Array2D(3)': + case 'Array2D(4)': + case 'Array3D(2)': + case 'Array3D(3)': + case 'Array3D(4)': + case 'ArrayTexture(1)': + case 'ArrayTexture(2)': + case 'ArrayTexture(3)': + case 'ArrayTexture(4)': + context.insertVariable(`uploadValue_${kernelArgument.name}`, args[i].texture); + break; + default: + throw new Error(`unhandled kernelArgumentType insertion for glWiretap of type ${kernelArgument.type}`); + } + }); + result.push('/** start of injected functions **/'); + result.push(`function ${toStringWithoutUtils(utils.flattenTo)}`); + result.push(`function ${toStringWithoutUtils(utils.flatten2dArrayTo)}`); + result.push(`function ${toStringWithoutUtils(utils.flatten3dArrayTo)}`); + result.push(`function ${toStringWithoutUtils(utils.flatten4dArrayTo)}`); + result.push(`function ${toStringWithoutUtils(utils.isArray)}`); + if (kernel.renderOutput !== kernel.renderTexture && kernel.formatValues) { + result.push( + ` const renderOutput = function ${toStringWithoutUtils(kernel.formatValues)};` + ); + } + result.push('/** end of injected functions **/'); + result.push(` const innerKernel = function (${kernel.kernelArguments.map(kernelArgument => kernelArgument.varName).join(', ')}) {`); + context.setIndent(4); + kernel.run.apply(kernel, args); + if (kernel.renderKernels) { + kernel.renderKernels(); + } else if (kernel.renderOutput) { + kernel.renderOutput(); + } + result.push(' /** start setup uploads for kernel values **/'); + kernel.kernelArguments.forEach(kernelArgument => { + result.push(' ' + kernelArgument.getStringValueHandler().split('\n').join('\n ')); + }); + result.push(' /** end setup uploads for kernel values **/'); + result.push(context.toString()); + if (kernel.renderOutput === kernel.renderTexture) { + context.reset(); + const results = kernel.renderKernels(); + const textureName = context.getContextVariableName(kernel.outputTexture); + result.push(` return { + result: { + texture: ${ textureName }, + type: '${ results.result.type }', + toArray: ${ getToArrayString(results.result, textureName) } + },`); + const { subKernels, subKernelOutputTextures } = kernel; + for (let i = 0; i < subKernels.length; i++) { + const texture = subKernelOutputTextures[i]; + const subKernel = subKernels[i]; + const subKernelResult = results[subKernel.property]; + const subKernelTextureName = context.getContextVariableName(texture); + result.push(` + ${subKernel.property}: { + texture: ${ subKernelTextureName }, + type: '${ subKernelResult.type }', + toArray: ${ getToArrayString(subKernelResult, subKernelTextureName) } + },`); + } + result.push(` };`); + } + result.push(` ${destroyContextString ? '\n' + destroyContextString + ' ': ''}`); + result.push(postResult.join('\n')); + result.push(' };'); + if (kernel.graphical) { + result.push(getGetPixelsString(kernel)); + result.push(` innerKernel.getPixels = getPixels;`); + } + result.push(' return innerKernel;'); + + let constantsUpload = []; + kernelConstants.forEach((kernelConstant) => { + constantsUpload.push(`${ kernelConstant.getStringValueHandler()}`); + }); + return `function kernel(settings) { + const { context, constants } = settings; + ${constantsUpload.join('')} + ${setupContextString ? setupContextString : ''} +${result.join('\n')} +}`; +} + +function getRenderString(targetName, kernel) { + const readBackValue = kernel.precision === 'single' ? targetName : `new Float32Array(${targetName}.buffer)`; + if (kernel.output[2]) { + return `renderOutput(${readBackValue}, ${kernel.output[0]}, ${kernel.output[1]}, ${kernel.output[2]})`; + } + if (kernel.output[1]) { + return `renderOutput(${readBackValue}, ${kernel.output[0]}, ${kernel.output[1]})`; + } + + return `renderOutput(${readBackValue}, ${kernel.output[0]})`; +} + +function getGetPixelsString(kernel) { + const getPixels = kernel.getPixels.toString(); + const useFunctionKeyword = !/^function/.test(getPixels); + return utils.flattenFunctionToString(`${useFunctionKeyword ? 'function ' : ''}${ getPixels }`, { + findDependency: (object, name) => { + if (object === 'utils') { + return `const ${name} = ${utils[name].toString()};`; + } + return null; + }, + thisLookup: (property) => { + if (property === 'context') { + return null; + } + if (kernel.hasOwnProperty(property)) { + return JSON.stringify(kernel[property]); + } + throw new Error(`unhandled thisLookup ${ property }`); + } + }); +} + +function getToArrayString(kernelResult, textureName) { + const toArray = kernelResult.toArray.toString(); + const useFunctionKeyword = !/^function/.test(toArray); + const flattenedFunctions = utils.flattenFunctionToString(`${useFunctionKeyword ? 'function ' : ''}${ toArray }`, { + findDependency: (object, name) => { + if (object === 'utils') { + return `const ${name} = ${utils[name].toString()};`; + } else if (object === 'this') { + return `${useFunctionKeyword ? 'function ' : ''}${kernelResult[name].toString()}`; + } else { + throw new Error('unhandled fromObject'); + } + }, + thisLookup: (property) => { + if (property === 'texture') { + return textureName; + } + if (kernelResult.hasOwnProperty(property)) { + return JSON.stringify(kernelResult[property]); + } + throw new Error(`unhandled thisLookup ${ property }`); + } + }); + return `() => { + ${flattenedFunctions} + return toArray(); + }`; +} + +/** + * + * @param {KernelVariable} argument + * @param {KernelValue[]} kernelValues + * @param {KernelVariable[]} values + * @param context + * @param {KernelVariable[]} uploadedValues + * @return {string|null} + */ +function findKernelValue(argument, kernelValues, values, context, uploadedValues) { + if (argument === null) return null; + switch (typeof argument) { + case 'boolean': + case 'number': + return null; + } + if ( + typeof HTMLImageElement !== 'undefined' && + argument instanceof HTMLImageElement + ) { + for (let i = 0; i < kernelValues.length; i++) { + const kernelValue = kernelValues[i]; + if (kernelValue.type !== 'HTMLImageArray') continue; + if (kernelValue.uploadValue !== argument) continue; + // TODO: if we send two of the same image, the parser could get confused, and short circuit to the first, handle that here + const variableIndex = values[i].indexOf(argument); + if (variableIndex === -1) continue; + const variableName = `uploadValue_${kernelValue.name}[${variableIndex}]`; + context.insertVariable(variableName, argument); + return variableName; + } + return null; + } + + for (let i = 0; i < kernelValues.length; i++) { + const kernelValue = kernelValues[i]; + if (argument !== kernelValue.uploadValue) continue; + const variable = `uploadValue_${kernelValue.name}`; + context.insertVariable(variable, kernelValue); + return variable; + } + return null; +} diff --git a/src/backend/gl/kernel.js b/src/backend/gl/kernel.js index 9f40045a..02081258 100644 --- a/src/backend/gl/kernel.js +++ b/src/backend/gl/kernel.js @@ -1,1001 +1,995 @@ -const { Kernel } = require('../kernel'); -const { Texture } = require('../../texture'); -const { utils } = require('../../utils'); -const { GLTextureArray2Float } = require('./texture/array-2-float'); -const { GLTextureArray2Float2D } = require('./texture/array-2-float-2d'); -const { GLTextureArray2Float3D } = require('./texture/array-2-float-3d'); -const { GLTextureArray3Float } = require('./texture/array-3-float'); -const { GLTextureArray3Float2D } = require('./texture/array-3-float-2d'); -const { GLTextureArray3Float3D } = require('./texture/array-3-float-3d'); -const { GLTextureArray4Float } = require('./texture/array-4-float'); -const { GLTextureArray4Float2D } = require('./texture/array-4-float-2d'); -const { GLTextureArray4Float3D } = require('./texture/array-4-float-3d'); -const { GLTextureFloat } = require('./texture/float'); -const { GLTextureFloat2D } = require('./texture/float-2d'); -const { GLTextureFloat3D } = require('./texture/float-3d'); -const { GLTextureMemoryOptimized } = require('./texture/memory-optimized'); -const { GLTextureMemoryOptimized2D } = require('./texture/memory-optimized-2d'); -const { GLTextureMemoryOptimized3D } = require('./texture/memory-optimized-3d'); -const { GLTextureUnsigned } = require('./texture/unsigned'); -const { GLTextureUnsigned2D } = require('./texture/unsigned-2d'); -const { GLTextureUnsigned3D } = require('./texture/unsigned-3d'); -const { GLTextureGraphical } = require('./texture/graphical'); - -/** - * @abstract - * @extends Kernel - */ -class GLKernel extends Kernel { - static get mode() { - return 'gpu'; - } - - static getIsFloatRead() { - const kernelString = `function kernelFunction() { - return 1; - }`; - const kernel = new this(kernelString, { - context: this.testContext, - canvas: this.testCanvas, - validate: false, - output: [1], - precision: 'single', - returnType: 'Number', - tactic: 'speed', - }); - kernel.build(); - kernel.run(); - const result = kernel.renderOutput(); - kernel.destroy(true); - return result[0] === 1; - } - - static getIsIntegerDivisionAccurate() { - function kernelFunction(v1, v2) { - return v1[this.thread.x] / v2[this.thread.x]; - } - const kernel = new this(kernelFunction.toString(), { - context: this.testContext, - canvas: this.testCanvas, - validate: false, - output: [2], - returnType: 'Number', - precision: 'unsigned', - tactic: 'speed', - }); - const args = [ - [6, 6030401], - [3, 3991] - ]; - kernel.build.apply(kernel, args); - kernel.run.apply(kernel, args); - const result = kernel.renderOutput(); - kernel.destroy(true); - // have we not got whole numbers for 6/3 or 6030401/3991 - // add more here if others see this problem - return result[0] === 2 && result[1] === 1511; - } - - /** - * @abstract - */ - static get testCanvas() { - throw new Error(`"testCanvas" not defined on ${ this.name }`); - } - - /** - * @abstract - */ - static get testContext() { - throw new Error(`"testContext" not defined on ${ this.name }`); - } - - /** - * @type {IKernelFeatures} - */ - static get features() { - throw new Error(`"features" not defined on ${ this.name }`); - } - - /** - * @abstract - */ - static setupFeatureChecks() { - throw new Error(`"setupFeatureChecks" not defined on ${ this.name }`); - } - - /** - * @desc Fix division by factor of 3 FP accuracy bug - * @param {Boolean} fix - should fix - */ - setFixIntegerDivisionAccuracy(fix) { - this.fixIntegerDivisionAccuracy = fix; - return this; - } - - /** - * @desc Toggle output mode - * @param {String} flag - 'single' or 'unsigned' - */ - setPrecision(flag) { - this.precision = flag; - return this; - } - - /** - * @desc Toggle texture output mode - * @param {Boolean} flag - true to enable floatTextures - * @deprecated - */ - setFloatTextures(flag) { - utils.warnDeprecated('method', 'setFloatTextures', 'setOptimizeFloatMemory'); - this.floatTextures = flag; - return this; - } - - /** - * A highly readable very forgiving micro-parser for a glsl function that gets argument types - * @param {String} source - * @returns {{argumentTypes: String[], argumentNames: String[]}} - */ - static nativeFunctionArguments(source) { - const argumentTypes = []; - const argumentNames = []; - const states = []; - const isStartingVariableName = /^[a-zA-Z_]/; - const isVariableChar = /[a-zA-Z_0-9]/; - let i = 0; - let argumentName = null; - let argumentType = null; - while (i < source.length) { - const char = source[i]; - const nextChar = source[i + 1]; - const state = states.length > 0 ? states[states.length - 1] : null; - - // begin MULTI_LINE_COMMENT handling - if (state === 'FUNCTION_ARGUMENTS' && char === '/' && nextChar === '*') { - states.push('MULTI_LINE_COMMENT'); - i += 2; - continue; - } else if (state === 'MULTI_LINE_COMMENT' && char === '*' && nextChar === '/') { - states.pop(); - i += 2; - continue; - } - // end MULTI_LINE_COMMENT handling - - // begin COMMENT handling - else if (state === 'FUNCTION_ARGUMENTS' && char === '/' && nextChar === '/') { - states.push('COMMENT'); - i += 2; - continue; - } else if (state === 'COMMENT' && char === '\n') { - states.pop(); - i++; - continue; - } - // end COMMENT handling - - // being FUNCTION_ARGUMENTS handling - else if (state === null && char === '(') { - states.push('FUNCTION_ARGUMENTS'); - i++; - continue; - } else if (state === 'FUNCTION_ARGUMENTS') { - if (char === ')') { - states.pop(); - break; - } - if (char === 'f' && nextChar === 'l' && source[i + 2] === 'o' && source[i + 3] === 'a' && source[i + 4] === 't' && source[i + 5] === ' ') { - states.push('DECLARE_VARIABLE'); - argumentType = 'float'; - argumentName = ''; - i += 6; - continue; - } else if (char === 'i' && nextChar === 'n' && source[i + 2] === 't' && source[i + 3] === ' ') { - states.push('DECLARE_VARIABLE'); - argumentType = 'int'; - argumentName = ''; - i += 4; - continue; - } else if (char === 'v' && nextChar === 'e' && source[i + 2] === 'c' && source[i + 3] === '2' && source[i + 4] === ' ') { - states.push('DECLARE_VARIABLE'); - argumentType = 'vec2'; - argumentName = ''; - i += 5; - continue; - } else if (char === 'v' && nextChar === 'e' && source[i + 2] === 'c' && source[i + 3] === '3' && source[i + 4] === ' ') { - states.push('DECLARE_VARIABLE'); - argumentType = 'vec3'; - argumentName = ''; - i += 5; - continue; - } else if (char === 'v' && nextChar === 'e' && source[i + 2] === 'c' && source[i + 3] === '4' && source[i + 4] === ' ') { - states.push('DECLARE_VARIABLE'); - argumentType = 'vec4'; - argumentName = ''; - i += 5; - continue; - } - } - // end FUNCTION_ARGUMENTS handling - - // begin DECLARE_VARIABLE handling - else if (state === 'DECLARE_VARIABLE') { - if (argumentName === '') { - if (char === ' ') { - i++; - continue; - } - if (!isStartingVariableName.test(char)) { - throw new Error('variable name is not expected string'); - } - } - argumentName += char; - if (!isVariableChar.test(nextChar)) { - states.pop(); - argumentNames.push(argumentName); - argumentTypes.push(typeMap[argumentType]); - } - } - // end DECLARE_VARIABLE handling - - // Progress to next character - i++; - } - if (states.length > 0) { - throw new Error('GLSL function was not parsable'); - } - return { - argumentNames, - argumentTypes, - }; - } - - static nativeFunctionReturnType(source) { - return typeMap[source.match(/int|float|vec[2-4]/)[0]]; - } - - static combineKernels(combinedKernel, lastKernel) { - combinedKernel.apply(null, arguments); - const { - texSize, - context, - threadDim - } = lastKernel.texSize; - let result; - if (lastKernel.precision === 'single') { - const w = texSize[0]; - const h = Math.ceil(texSize[1] / 4); - result = new Float32Array(w * h * 4 * 4); - context.readPixels(0, 0, w, h * 4, context.RGBA, context.FLOAT, result); - } else { - const bytes = new Uint8Array(texSize[0] * texSize[1] * 4); - context.readPixels(0, 0, texSize[0], texSize[1], context.RGBA, context.UNSIGNED_BYTE, bytes); - result = new Float32Array(bytes.buffer); - } - - result = result.subarray(0, threadDim[0] * threadDim[1] * threadDim[2]); - - if (lastKernel.output.length === 1) { - return result; - } else if (lastKernel.output.length === 2) { - return utils.splitArray(result, lastKernel.output[0]); - } else if (lastKernel.output.length === 3) { - const cube = utils.splitArray(result, lastKernel.output[0] * lastKernel.output[1]); - return cube.map(function(x) { - return utils.splitArray(x, lastKernel.output[0]); - }); - } - } - - constructor(source, settings) { - super(source, settings); - this.transferValues = null; - this.formatValues = null; - this.TextureConstructor = null; - this.renderOutput = null; - this.renderRawOutput = null; - this.texSize = null; - this.translatedSource = null; - this.renderStrategy = null; - this.compiledFragmentShader = null; - this.compiledVertexShader = null; - } - - checkTextureSize() { - const { features } = this.constructor; - if (this.texSize[0] > features.maxTextureSize || this.texSize[1] > features.maxTextureSize) { - throw new Error(`Texture size [${this.texSize[0]},${this.texSize[1]}] generated by kernel is larger than supported size [${features.maxTextureSize},${features.maxTextureSize}]`); - } - } - - translateSource() { - throw new Error(`"translateSource" not defined on ${this.constructor.name}`); - } - - /** - * Picks a render strategy for the now finally parsed kernel - * @param args - * @return {null|KernelOutput} - */ - pickRenderStrategy(args) { - if (this.graphical) { - this.renderRawOutput = this.readPackedPixelsToUint8Array; - this.transferValues = (pixels) => pixels; - this.TextureConstructor = GLTextureGraphical; - return null; - } - if (this.precision === 'unsigned') { - this.renderRawOutput = this.readPackedPixelsToUint8Array; - this.transferValues = this.readPackedPixelsToFloat32Array; - if (this.pipeline) { - this.renderOutput = this.renderTexture; - if (this.subKernels !== null) { - this.renderKernels = this.renderKernelsToTextures; - } - switch (this.returnType) { - case 'LiteralInteger': - case 'Float': - case 'Number': - case 'Integer': - if (this.output[2] > 0) { - this.TextureConstructor = GLTextureUnsigned3D; - this.renderStrategy = renderStrategy.PackedPixelTo3DFloat; - return null; - } else if (this.output[1] > 0) { - this.TextureConstructor = GLTextureUnsigned2D; - this.renderStrategy = renderStrategy.PackedPixelTo2DFloat; - return null; - } else { - this.TextureConstructor = GLTextureUnsigned; - this.renderStrategy = renderStrategy.PackedPixelToFloat; - return null; - } - break; - case 'Array(2)': - case 'Array(3)': - case 'Array(4)': - return this.requestFallback(args); - } - } else { - if (this.subKernels !== null) { - this.renderKernels = this.renderKernelsToArrays; - } - switch (this.returnType) { - case 'LiteralInteger': - case 'Float': - case 'Number': - case 'Integer': - this.renderOutput = this.renderValues; - if (this.output[2] > 0) { - this.TextureConstructor = GLTextureUnsigned3D; - this.renderStrategy = renderStrategy.PackedPixelTo3DFloat; - this.formatValues = utils.erect3DPackedFloat; - return null; - } else if (this.output[1] > 0) { - this.TextureConstructor = GLTextureUnsigned2D; - this.renderStrategy = renderStrategy.PackedPixelTo2DFloat; - this.formatValues = utils.erect2DPackedFloat; - return null; - } else { - this.TextureConstructor = GLTextureUnsigned; - this.renderStrategy = renderStrategy.PackedPixelToFloat; - this.formatValues = utils.erectPackedFloat; - return null; - } - - break; - case 'Array(2)': - case 'Array(3)': - case 'Array(4)': - return this.requestFallback(args); - } - } - } else if (this.precision === 'single') { - this.renderRawOutput = this.readFloatPixelsToFloat32Array; - this.transferValues = this.readFloatPixelsToFloat32Array; - if (this.pipeline) { - this.renderStrategy = renderStrategy.FloatTexture; - this.renderOutput = this.renderTexture; - if (this.subKernels !== null) { - this.renderKernels = this.renderKernelsToTextures; - } - switch (this.returnType) { - case 'LiteralInteger': - case 'Float': - case 'Number': - case 'Integer': - if (this.optimizeFloatMemory) { - if (this.output[2] > 0) { - this.TextureConstructor = GLTextureMemoryOptimized3D; - return null; - } else if (this.output[1] > 0) { - this.TextureConstructor = GLTextureMemoryOptimized2D; - return null; - } else { - this.TextureConstructor = GLTextureMemoryOptimized; - return null; - } - } else { - if (this.output[2] > 0) { - this.TextureConstructor = GLTextureFloat3D; - return null; - } else if (this.output[1] > 0) { - this.TextureConstructor = GLTextureFloat2D; - return null; - } else { - this.TextureConstructor = GLTextureFloat; - return null; - } - } - break; - case 'Array(2)': - if (this.output[2] > 0) { - this.TextureConstructor = GLTextureArray2Float3D; - return null; - } else if (this.output[1] > 0) { - this.TextureConstructor = GLTextureArray2Float2D; - return null; - } else { - this.TextureConstructor = GLTextureArray2Float; - return null; - } - break; - case 'Array(3)': - if (this.output[2] > 0) { - this.TextureConstructor = GLTextureArray3Float3D; - return null; - } else if (this.output[1] > 0) { - this.TextureConstructor = GLTextureArray3Float2D; - return null; - } else { - this.TextureConstructor = GLTextureArray3Float; - return null; - } - break; - case 'Array(4)': - if (this.output[2] > 0) { - this.TextureConstructor = GLTextureArray4Float3D; - return null; - } else if (this.output[1] > 0) { - this.TextureConstructor = GLTextureArray4Float2D; - return null; - } else { - this.TextureConstructor = GLTextureArray4Float; - return null; - } - } - } - this.renderOutput = this.renderValues; - if (this.subKernels !== null) { - this.renderKernels = this.renderKernelsToArrays; - } - if (this.optimizeFloatMemory) { - switch (this.returnType) { - case 'LiteralInteger': - case 'Float': - case 'Number': - case 'Integer': - if (this.output[2] > 0) { - this.TextureConstructor = GLTextureMemoryOptimized3D; - this.renderStrategy = renderStrategy.MemoryOptimizedFloatPixelToMemoryOptimized3DFloat; - this.formatValues = utils.erectMemoryOptimized3DFloat; - return null; - } else if (this.output[1] > 0) { - this.TextureConstructor = GLTextureMemoryOptimized2D; - this.renderStrategy = renderStrategy.MemoryOptimizedFloatPixelToMemoryOptimized2DFloat; - this.formatValues = utils.erectMemoryOptimized2DFloat; - return null; - } else { - this.TextureConstructor = GLTextureMemoryOptimized; - this.renderStrategy = renderStrategy.MemoryOptimizedFloatPixelToMemoryOptimizedFloat; - this.formatValues = utils.erectMemoryOptimizedFloat; - return null; - } - break; - case 'Array(2)': - if (this.output[2] > 0) { - this.TextureConstructor = GLTextureArray2Float3D; - this.renderStrategy = renderStrategy.FloatPixelTo3DArray2; - this.formatValues = utils.erect3DArray2; - return null; - } else if (this.output[1] > 0) { - this.TextureConstructor = GLTextureArray2Float2D; - this.renderStrategy = renderStrategy.FloatPixelTo2DArray2; - this.formatValues = utils.erect2DArray2; - return null; - } else { - this.TextureConstructor = GLTextureArray2Float; - this.renderStrategy = renderStrategy.FloatPixelToArray2; - this.formatValues = utils.erectArray2; - return null; - } - break; - case 'Array(3)': - if (this.output[2] > 0) { - this.TextureConstructor = GLTextureArray3Float3D; - this.renderStrategy = renderStrategy.FloatPixelTo3DArray3; - this.formatValues = utils.erect3DArray3; - return null; - } else if (this.output[1] > 0) { - this.TextureConstructor = GLTextureArray3Float2D; - this.renderStrategy = renderStrategy.FloatPixelTo2DArray3; - this.formatValues = utils.erect2DArray3; - return null; - } else { - this.TextureConstructor = GLTextureArray3Float; - this.renderStrategy = renderStrategy.FloatPixelToArray3; - this.formatValues = utils.erectArray3; - return null; - } - break; - case 'Array(4)': - if (this.output[2] > 0) { - this.TextureConstructor = GLTextureArray4Float3D; - this.renderStrategy = renderStrategy.FloatPixelTo3DArray4; - this.formatValues = utils.erect3DArray4; - return null; - } else if (this.output[1] > 0) { - this.TextureConstructor = GLTextureArray4Float2D; - this.renderStrategy = renderStrategy.FloatPixelTo2DArray4; - this.formatValues = utils.erect2DArray4; - return null; - } else { - this.TextureConstructor = GLTextureArray4Float; - this.renderStrategy = renderStrategy.FloatPixelToArray4; - this.formatValues = utils.erectArray4; - return null; - } - } - } else { - switch (this.returnType) { - case 'LiteralInteger': - case 'Float': - case 'Number': - case 'Integer': - if (this.output[2] > 0) { - this.TextureConstructor = GLTextureFloat3D; - this.renderStrategy = renderStrategy.FloatPixelTo3DFloat; - this.formatValues = utils.erect3DFloat; - return null; - } else if (this.output[1] > 0) { - this.TextureConstructor = GLTextureFloat2D; - this.renderStrategy = renderStrategy.FloatPixelTo2DFloat; - this.formatValues = utils.erect2DFloat; - return null; - } else { - this.TextureConstructor = GLTextureFloat; - this.renderStrategy = renderStrategy.FloatPixelToFloat; - this.formatValues = utils.erectFloat; - return null; - } - break; - case 'Array(2)': - if (this.output[2] > 0) { - this.TextureConstructor = GLTextureArray2Float3D; - this.renderStrategy = renderStrategy.FloatPixelTo3DArray2; - this.formatValues = utils.erect3DArray2; - return null; - } else if (this.output[1] > 0) { - this.TextureConstructor = GLTextureArray2Float2D; - this.renderStrategy = renderStrategy.FloatPixelTo2DArray2; - this.formatValues = utils.erect2DArray2; - return null; - } else { - this.TextureConstructor = GLTextureArray2Float; - this.renderStrategy = renderStrategy.FloatPixelToArray2; - this.formatValues = utils.erectArray2; - return null; - } - break; - case 'Array(3)': - if (this.output[2] > 0) { - this.TextureConstructor = GLTextureArray3Float3D; - this.renderStrategy = renderStrategy.FloatPixelTo3DArray3; - this.formatValues = utils.erect3DArray3; - return null; - } else if (this.output[1] > 0) { - this.TextureConstructor = GLTextureArray3Float2D; - this.renderStrategy = renderStrategy.FloatPixelTo2DArray3; - this.formatValues = utils.erect2DArray3; - return null; - } else { - this.TextureConstructor = GLTextureArray3Float; - this.renderStrategy = renderStrategy.FloatPixelToArray3; - this.formatValues = utils.erectArray3; - return null; - } - break; - case 'Array(4)': - if (this.output[2] > 0) { - this.TextureConstructor = GLTextureArray4Float3D; - this.renderStrategy = renderStrategy.FloatPixelTo3DArray4; - this.formatValues = utils.erect3DArray4; - return null; - } else if (this.output[1] > 0) { - this.TextureConstructor = GLTextureArray4Float2D; - this.renderStrategy = renderStrategy.FloatPixelTo2DArray4; - this.formatValues = utils.erect2DArray4; - return null; - } else { - this.TextureConstructor = GLTextureArray4Float; - this.renderStrategy = renderStrategy.FloatPixelToArray4; - this.formatValues = utils.erectArray4; - return null; - } - } - } - } else { - throw new Error(`unhandled precision of "${this.precision}"`); - } - - throw new Error(`unhandled return type "${this.returnType}"`); - } - - /** - * @abstract - * @returns String - */ - getKernelString() { - throw new Error(`abstract method call`); - } - - getMainResultTexture() { - switch (this.returnType) { - case 'LiteralInteger': - case 'Float': - case 'Integer': - case 'Number': - return this.getMainResultNumberTexture(); - case 'Array(2)': - return this.getMainResultArray2Texture(); - case 'Array(3)': - return this.getMainResultArray3Texture(); - case 'Array(4)': - return this.getMainResultArray4Texture(); - default: - throw new Error(`unhandled returnType type ${ this.returnType }`); - } - } - - /** - * @abstract - * @returns String[] - */ - getMainResultKernelNumberTexture() { - throw new Error(`abstract method call`); - } - /** - * @abstract - * @returns String[] - */ - getMainResultSubKernelNumberTexture() { - throw new Error(`abstract method call`); - } - /** - * @abstract - * @returns String[] - */ - getMainResultKernelArray2Texture() { - throw new Error(`abstract method call`); - } - /** - * @abstract - * @returns String[] - */ - getMainResultSubKernelArray2Texture() { - throw new Error(`abstract method call`); - } - /** - * @abstract - * @returns String[] - */ - getMainResultKernelArray3Texture() { - throw new Error(`abstract method call`); - } - /** - * @abstract - * @returns String[] - */ - getMainResultSubKernelArray3Texture() { - throw new Error(`abstract method call`); - } - /** - * @abstract - * @returns String[] - */ - getMainResultKernelArray4Texture() { - throw new Error(`abstract method call`); - } - /** - * @abstract - * @returns String[] - */ - getMainResultSubKernelArray4Texture() { - throw new Error(`abstract method call`); - } - /** - * @abstract - * @returns String[] - */ - getMainResultGraphical() { - throw new Error(`abstract method call`); - } - /** - * @abstract - * @returns String[] - */ - getMainResultMemoryOptimizedFloats() { - throw new Error(`abstract method call`); - } - /** - * @abstract - * @returns String[] - */ - getMainResultPackedPixels() { - throw new Error(`abstract method call`); - } - - getMainResultString() { - if (this.graphical) { - return this.getMainResultGraphical(); - } else if (this.precision === 'single') { - if (this.optimizeFloatMemory) { - return this.getMainResultMemoryOptimizedFloats(); - } - return this.getMainResultTexture(); - } else { - return this.getMainResultPackedPixels(); - } - } - - getMainResultNumberTexture() { - return utils.linesToString(this.getMainResultKernelNumberTexture()) + - utils.linesToString(this.getMainResultSubKernelNumberTexture()); - } - - getMainResultArray2Texture() { - return utils.linesToString(this.getMainResultKernelArray2Texture()) + - utils.linesToString(this.getMainResultSubKernelArray2Texture()); - } - - getMainResultArray3Texture() { - return utils.linesToString(this.getMainResultKernelArray3Texture()) + - utils.linesToString(this.getMainResultSubKernelArray3Texture()); - } - - getMainResultArray4Texture() { - return utils.linesToString(this.getMainResultKernelArray4Texture()) + - utils.linesToString(this.getMainResultSubKernelArray4Texture()); - } - - /** - * - * @return {string} - */ - getFloatTacticDeclaration() { - switch (this.tactic) { - case 'speed': - return 'precision lowp float;\n'; - case 'performance': - return 'precision highp float;\n'; - case 'balanced': - default: - return 'precision mediump float;\n'; - } - } - - /** - * - * @return {string} - */ - getIntTacticDeclaration() { - switch (this.tactic) { - case 'speed': - return 'precision lowp int;\n'; - case 'performance': - return 'precision highp int;\n'; - case 'balanced': - default: - return 'precision mediump int;\n'; - } - } - - /** - * - * @return {string} - */ - getSampler2DTacticDeclaration() { - switch (this.tactic) { - case 'speed': - return 'precision lowp sampler2D;\n'; - case 'performance': - return 'precision highp sampler2D;\n'; - case 'balanced': - default: - return 'precision mediump sampler2D;\n'; - } - } - - getSampler2DArrayTacticDeclaration() { - switch (this.tactic) { - case 'speed': - return 'precision lowp sampler2DArray;\n'; - case 'performance': - return 'precision highp sampler2DArray;\n'; - case 'balanced': - default: - return 'precision mediump sampler2DArray;\n'; - } - } - - renderTexture() { - return new this.TextureConstructor({ - texture: this.outputTexture, - size: this.texSize, - dimensions: this.threadDim, - output: this.output, - context: this.context, - }); - } - readPackedPixelsToUint8Array() { - if (this.precision !== 'unsigned') throw new Error('Requires this.precision to be "unsigned"'); - const { - texSize, - context: gl - } = this; - const result = new Uint8Array(texSize[0] * texSize[1] * 4); - gl.readPixels(0, 0, texSize[0], texSize[1], gl.RGBA, gl.UNSIGNED_BYTE, result); - return result; - } - - readPackedPixelsToFloat32Array() { - return new Float32Array(this.readPackedPixelsToUint8Array().buffer); - } - - readFloatPixelsToFloat32Array() { - if (this.precision !== 'single') throw new Error('Requires this.precision to be "single"'); - const { - texSize, - context: gl - } = this; - const w = texSize[0]; - const h = texSize[1]; - const result = new Float32Array(w * h * 4); - gl.readPixels(0, 0, w, h, gl.RGBA, gl.FLOAT, result); - return result; - } - - readMemoryOptimizedFloatPixelsToFloat32Array() { - if (this.precision !== 'single') throw new Error('Requires this.precision to be "single"'); - const { - texSize, - context: gl - } = this; - const w = texSize[0]; - const h = texSize[1]; - const result = new Float32Array(w * h * 4); - gl.readPixels(0, 0, w, h, gl.RGBA, gl.FLOAT, result); - return result; - } - - /** - * - * @param {Boolean} [flip] - * @return {Uint8Array} - */ - getPixels(flip) { - const { - context: gl, - output - } = this; - const [width, height] = output; - const pixels = new Uint8Array(width * height * 4); - gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels); - // flipped by default, so invert - return new Uint8ClampedArray((flip ? pixels : utils.flipPixels(pixels, width, height)).buffer); - } - - renderKernelsToArrays() { - const result = { - result: this.renderOutput(), - }; - for (let i = 0; i < this.subKernels.length; i++) { - result[this.subKernels[i].property] = new this.TextureConstructor({ - texture: this.subKernelOutputTextures[i], - size: this.texSize, - dimensions: this.threadDim, - output: this.output, - context: this.context, - }).toArray(); - } - return result; - } - - renderKernelsToTextures() { - const result = { - result: this.renderOutput(), - }; - for (let i = 0; i < this.subKernels.length; i++) { - result[this.subKernels[i].property] = new this.TextureConstructor({ - texture: this.subKernelOutputTextures[i], - size: this.texSize, - dimensions: this.threadDim, - output: this.output, - context: this.context, - }); - } - return result; - } - - setOutput(output) { - super.setOutput(output); - if (this.program) { - this.threadDim = [this.output[0], this.output[1] || 1, this.output[2] || 1]; - this.texSize = utils.getKernelTextureSize({ - optimizeFloatMemory: this.optimizeFloatMemory, - precision: this.precision, - }, this.output); - const { context: gl } = this; - gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); - this.updateMaxTexSize(); - this.framebuffer.width = this.texSize[0]; - this.framebuffer.height = this.texSize[1]; - this.context.viewport(0, 0, this.maxTexSize[0], this.maxTexSize[1]); - this.canvas.width = this.maxTexSize[0]; - this.canvas.height = this.maxTexSize[1]; - this._setupOutputTexture(); - if (this.subKernels && this.subKernels.length > 0) { - this._setupSubOutputTextures(); - } - } - return this; - } - renderValues() { - return this.formatValues( - this.transferValues(), - this.output[0], - this.output[1], - this.output[2] - ); - } -} - -const renderStrategy = Object.freeze({ - PackedPixelToUint8Array: Symbol('PackedPixelToUint8Array'), - PackedPixelToFloat: Symbol('PackedPixelToFloat'), - PackedPixelTo2DFloat: Symbol('PackedPixelTo2DFloat'), - PackedPixelTo3DFloat: Symbol('PackedPixelTo3DFloat'), - PackedTexture: Symbol('PackedTexture'), - FloatPixelToFloat32Array: Symbol('FloatPixelToFloat32Array'), - FloatPixelToFloat: Symbol('FloatPixelToFloat'), - FloatPixelTo2DFloat: Symbol('FloatPixelTo2DFloat'), - FloatPixelTo3DFloat: Symbol('FloatPixelTo3DFloat'), - FloatPixelToArray2: Symbol('FloatPixelToArray2'), - FloatPixelTo2DArray2: Symbol('FloatPixelTo2DArray2'), - FloatPixelTo3DArray2: Symbol('FloatPixelTo3DArray2'), - FloatPixelToArray3: Symbol('FloatPixelToArray3'), - FloatPixelTo2DArray3: Symbol('FloatPixelTo2DArray3'), - FloatPixelTo3DArray3: Symbol('FloatPixelTo3DArray3'), - FloatPixelToArray4: Symbol('FloatPixelToArray4'), - FloatPixelTo2DArray4: Symbol('FloatPixelTo2DArray4'), - FloatPixelTo3DArray4: Symbol('FloatPixelTo3DArray4'), - FloatTexture: Symbol('FloatTexture'), - MemoryOptimizedFloatPixelToMemoryOptimizedFloat: Symbol('MemoryOptimizedFloatPixelToFloat'), - MemoryOptimizedFloatPixelToMemoryOptimized2DFloat: Symbol('MemoryOptimizedFloatPixelTo2DFloat'), - MemoryOptimizedFloatPixelToMemoryOptimized3DFloat: Symbol('MemoryOptimizedFloatPixelTo3DFloat'), -}); - -const typeMap = { - int: 'Integer', - float: 'Number', - vec2: 'Array(2)', - vec3: 'Array(3)', - vec4: 'Array(4)', -}; - -module.exports = { - GLKernel, - renderStrategy -}; \ No newline at end of file +import { Kernel } from '../kernel'; +import { utils } from '../../utils'; +import { GLTextureArray2Float } from './texture/array-2-float'; +import { GLTextureArray2Float2D } from './texture/array-2-float-2d'; +import { GLTextureArray2Float3D } from './texture/array-2-float-3d'; +import { GLTextureArray3Float } from './texture/array-3-float'; +import { GLTextureArray3Float2D } from './texture/array-3-float-2d'; +import { GLTextureArray3Float3D } from './texture/array-3-float-3d'; +import { GLTextureArray4Float } from './texture/array-4-float'; +import { GLTextureArray4Float2D } from './texture/array-4-float-2d'; +import { GLTextureArray4Float3D } from './texture/array-4-float-3d'; +import { GLTextureFloat } from './texture/float'; +import { GLTextureFloat2D } from './texture/float-2d'; +import { GLTextureFloat3D } from './texture/float-3d'; +import { GLTextureMemoryOptimized } from './texture/memory-optimized'; +import { GLTextureMemoryOptimized2D } from './texture/memory-optimized-2d'; +import { GLTextureMemoryOptimized3D } from './texture/memory-optimized-3d'; +import { GLTextureUnsigned } from './texture/unsigned'; +import { GLTextureUnsigned2D } from './texture/unsigned-2d'; +import { GLTextureUnsigned3D } from './texture/unsigned-3d'; +import { GLTextureGraphical } from './texture/graphical'; + +/** + * @abstract + * @extends Kernel + */ +export class GLKernel extends Kernel { + static get mode() { + return 'gpu'; + } + + static getIsFloatRead() { + const kernelString = `function kernelFunction() { + return 1; + }`; + const kernel = new this(kernelString, { + context: this.testContext, + canvas: this.testCanvas, + validate: false, + output: [1], + precision: 'single', + returnType: 'Number', + tactic: 'speed', + }); + kernel.build(); + kernel.run(); + const result = kernel.renderOutput(); + kernel.destroy(true); + return result[0] === 1; + } + + static getIsIntegerDivisionAccurate() { + function kernelFunction(v1, v2) { + return v1[this.thread.x] / v2[this.thread.x]; + } + const kernel = new this(kernelFunction.toString(), { + context: this.testContext, + canvas: this.testCanvas, + validate: false, + output: [2], + returnType: 'Number', + precision: 'unsigned', + tactic: 'speed', + }); + const args = [ + [6, 6030401], + [3, 3991] + ]; + kernel.build.apply(kernel, args); + kernel.run.apply(kernel, args); + const result = kernel.renderOutput(); + kernel.destroy(true); + // have we not got whole numbers for 6/3 or 6030401/3991 + // add more here if others see this problem + return result[0] === 2 && result[1] === 1511; + } + + /** + * @abstract + */ + static get testCanvas() { + throw new Error(`"testCanvas" not defined on ${ this.name }`); + } + + /** + * @abstract + */ + static get testContext() { + throw new Error(`"testContext" not defined on ${ this.name }`); + } + + /** + * @type {IKernelFeatures} + */ + static get features() { + throw new Error(`"features" not defined on ${ this.name }`); + } + + /** + * @abstract + */ + static setupFeatureChecks() { + throw new Error(`"setupFeatureChecks" not defined on ${ this.name }`); + } + + /** + * @desc Fix division by factor of 3 FP accuracy bug + * @param {Boolean} fix - should fix + */ + setFixIntegerDivisionAccuracy(fix) { + this.fixIntegerDivisionAccuracy = fix; + return this; + } + + /** + * @desc Toggle output mode + * @param {String} flag - 'single' or 'unsigned' + */ + setPrecision(flag) { + this.precision = flag; + return this; + } + + /** + * @desc Toggle texture output mode + * @param {Boolean} flag - true to enable floatTextures + * @deprecated + */ + setFloatTextures(flag) { + utils.warnDeprecated('method', 'setFloatTextures', 'setOptimizeFloatMemory'); + this.floatTextures = flag; + return this; + } + + /** + * A highly readable very forgiving micro-parser for a glsl function that gets argument types + * @param {String} source + * @returns {{argumentTypes: String[], argumentNames: String[]}} + */ + static nativeFunctionArguments(source) { + const argumentTypes = []; + const argumentNames = []; + const states = []; + const isStartingVariableName = /^[a-zA-Z_]/; + const isVariableChar = /[a-zA-Z_0-9]/; + let i = 0; + let argumentName = null; + let argumentType = null; + while (i < source.length) { + const char = source[i]; + const nextChar = source[i + 1]; + const state = states.length > 0 ? states[states.length - 1] : null; + + // begin MULTI_LINE_COMMENT handling + if (state === 'FUNCTION_ARGUMENTS' && char === '/' && nextChar === '*') { + states.push('MULTI_LINE_COMMENT'); + i += 2; + continue; + } else if (state === 'MULTI_LINE_COMMENT' && char === '*' && nextChar === '/') { + states.pop(); + i += 2; + continue; + } + // end MULTI_LINE_COMMENT handling + + // begin COMMENT handling + else if (state === 'FUNCTION_ARGUMENTS' && char === '/' && nextChar === '/') { + states.push('COMMENT'); + i += 2; + continue; + } else if (state === 'COMMENT' && char === '\n') { + states.pop(); + i++; + continue; + } + // end COMMENT handling + + // being FUNCTION_ARGUMENTS handling + else if (state === null && char === '(') { + states.push('FUNCTION_ARGUMENTS'); + i++; + continue; + } else if (state === 'FUNCTION_ARGUMENTS') { + if (char === ')') { + states.pop(); + break; + } + if (char === 'f' && nextChar === 'l' && source[i + 2] === 'o' && source[i + 3] === 'a' && source[i + 4] === 't' && source[i + 5] === ' ') { + states.push('DECLARE_VARIABLE'); + argumentType = 'float'; + argumentName = ''; + i += 6; + continue; + } else if (char === 'i' && nextChar === 'n' && source[i + 2] === 't' && source[i + 3] === ' ') { + states.push('DECLARE_VARIABLE'); + argumentType = 'int'; + argumentName = ''; + i += 4; + continue; + } else if (char === 'v' && nextChar === 'e' && source[i + 2] === 'c' && source[i + 3] === '2' && source[i + 4] === ' ') { + states.push('DECLARE_VARIABLE'); + argumentType = 'vec2'; + argumentName = ''; + i += 5; + continue; + } else if (char === 'v' && nextChar === 'e' && source[i + 2] === 'c' && source[i + 3] === '3' && source[i + 4] === ' ') { + states.push('DECLARE_VARIABLE'); + argumentType = 'vec3'; + argumentName = ''; + i += 5; + continue; + } else if (char === 'v' && nextChar === 'e' && source[i + 2] === 'c' && source[i + 3] === '4' && source[i + 4] === ' ') { + states.push('DECLARE_VARIABLE'); + argumentType = 'vec4'; + argumentName = ''; + i += 5; + continue; + } + } + // end FUNCTION_ARGUMENTS handling + + // begin DECLARE_VARIABLE handling + else if (state === 'DECLARE_VARIABLE') { + if (argumentName === '') { + if (char === ' ') { + i++; + continue; + } + if (!isStartingVariableName.test(char)) { + throw new Error('variable name is not expected string'); + } + } + argumentName += char; + if (!isVariableChar.test(nextChar)) { + states.pop(); + argumentNames.push(argumentName); + argumentTypes.push(typeMap[argumentType]); + } + } + // end DECLARE_VARIABLE handling + + // Progress to next character + i++; + } + if (states.length > 0) { + throw new Error('GLSL function was not parsable'); + } + return { + argumentNames, + argumentTypes, + }; + } + + static nativeFunctionReturnType(source) { + return typeMap[source.match(/int|float|vec[2-4]/)[0]]; + } + + static combineKernels(combinedKernel, lastKernel) { + combinedKernel.apply(null, arguments); + const { + texSize, + context, + threadDim + } = lastKernel.texSize; + let result; + if (lastKernel.precision === 'single') { + const w = texSize[0]; + const h = Math.ceil(texSize[1] / 4); + result = new Float32Array(w * h * 4 * 4); + context.readPixels(0, 0, w, h * 4, context.RGBA, context.FLOAT, result); + } else { + const bytes = new Uint8Array(texSize[0] * texSize[1] * 4); + context.readPixels(0, 0, texSize[0], texSize[1], context.RGBA, context.UNSIGNED_BYTE, bytes); + result = new Float32Array(bytes.buffer); + } + + result = result.subarray(0, threadDim[0] * threadDim[1] * threadDim[2]); + + if (lastKernel.output.length === 1) { + return result; + } else if (lastKernel.output.length === 2) { + return utils.splitArray(result, lastKernel.output[0]); + } else if (lastKernel.output.length === 3) { + const cube = utils.splitArray(result, lastKernel.output[0] * lastKernel.output[1]); + return cube.map(function(x) { + return utils.splitArray(x, lastKernel.output[0]); + }); + } + } + + constructor(source, settings) { + super(source, settings); + this.transferValues = null; + this.formatValues = null; + this.TextureConstructor = null; + this.renderOutput = null; + this.renderRawOutput = null; + this.texSize = null; + this.translatedSource = null; + this.renderStrategy = null; + this.compiledFragmentShader = null; + this.compiledVertexShader = null; + } + + checkTextureSize() { + const { features } = this.constructor; + if (this.texSize[0] > features.maxTextureSize || this.texSize[1] > features.maxTextureSize) { + throw new Error(`Texture size [${this.texSize[0]},${this.texSize[1]}] generated by kernel is larger than supported size [${features.maxTextureSize},${features.maxTextureSize}]`); + } + } + + translateSource() { + throw new Error(`"translateSource" not defined on ${this.constructor.name}`); + } + + /** + * Picks a render strategy for the now finally parsed kernel + * @param args + * @return {null|KernelOutput} + */ + pickRenderStrategy(args) { + if (this.graphical) { + this.renderRawOutput = this.readPackedPixelsToUint8Array; + this.transferValues = (pixels) => pixels; + this.TextureConstructor = GLTextureGraphical; + return null; + } + if (this.precision === 'unsigned') { + this.renderRawOutput = this.readPackedPixelsToUint8Array; + this.transferValues = this.readPackedPixelsToFloat32Array; + if (this.pipeline) { + this.renderOutput = this.renderTexture; + if (this.subKernels !== null) { + this.renderKernels = this.renderKernelsToTextures; + } + switch (this.returnType) { + case 'LiteralInteger': + case 'Float': + case 'Number': + case 'Integer': + if (this.output[2] > 0) { + this.TextureConstructor = GLTextureUnsigned3D; + this.renderStrategy = renderStrategy.PackedPixelTo3DFloat; + return null; + } else if (this.output[1] > 0) { + this.TextureConstructor = GLTextureUnsigned2D; + this.renderStrategy = renderStrategy.PackedPixelTo2DFloat; + return null; + } else { + this.TextureConstructor = GLTextureUnsigned; + this.renderStrategy = renderStrategy.PackedPixelToFloat; + return null; + } + break; + case 'Array(2)': + case 'Array(3)': + case 'Array(4)': + return this.requestFallback(args); + } + } else { + if (this.subKernels !== null) { + this.renderKernels = this.renderKernelsToArrays; + } + switch (this.returnType) { + case 'LiteralInteger': + case 'Float': + case 'Number': + case 'Integer': + this.renderOutput = this.renderValues; + if (this.output[2] > 0) { + this.TextureConstructor = GLTextureUnsigned3D; + this.renderStrategy = renderStrategy.PackedPixelTo3DFloat; + this.formatValues = utils.erect3DPackedFloat; + return null; + } else if (this.output[1] > 0) { + this.TextureConstructor = GLTextureUnsigned2D; + this.renderStrategy = renderStrategy.PackedPixelTo2DFloat; + this.formatValues = utils.erect2DPackedFloat; + return null; + } else { + this.TextureConstructor = GLTextureUnsigned; + this.renderStrategy = renderStrategy.PackedPixelToFloat; + this.formatValues = utils.erectPackedFloat; + return null; + } + + break; + case 'Array(2)': + case 'Array(3)': + case 'Array(4)': + return this.requestFallback(args); + } + } + } else if (this.precision === 'single') { + this.renderRawOutput = this.readFloatPixelsToFloat32Array; + this.transferValues = this.readFloatPixelsToFloat32Array; + if (this.pipeline) { + this.renderStrategy = renderStrategy.FloatTexture; + this.renderOutput = this.renderTexture; + if (this.subKernels !== null) { + this.renderKernels = this.renderKernelsToTextures; + } + switch (this.returnType) { + case 'LiteralInteger': + case 'Float': + case 'Number': + case 'Integer': + if (this.optimizeFloatMemory) { + if (this.output[2] > 0) { + this.TextureConstructor = GLTextureMemoryOptimized3D; + return null; + } else if (this.output[1] > 0) { + this.TextureConstructor = GLTextureMemoryOptimized2D; + return null; + } else { + this.TextureConstructor = GLTextureMemoryOptimized; + return null; + } + } else { + if (this.output[2] > 0) { + this.TextureConstructor = GLTextureFloat3D; + return null; + } else if (this.output[1] > 0) { + this.TextureConstructor = GLTextureFloat2D; + return null; + } else { + this.TextureConstructor = GLTextureFloat; + return null; + } + } + break; + case 'Array(2)': + if (this.output[2] > 0) { + this.TextureConstructor = GLTextureArray2Float3D; + return null; + } else if (this.output[1] > 0) { + this.TextureConstructor = GLTextureArray2Float2D; + return null; + } else { + this.TextureConstructor = GLTextureArray2Float; + return null; + } + break; + case 'Array(3)': + if (this.output[2] > 0) { + this.TextureConstructor = GLTextureArray3Float3D; + return null; + } else if (this.output[1] > 0) { + this.TextureConstructor = GLTextureArray3Float2D; + return null; + } else { + this.TextureConstructor = GLTextureArray3Float; + return null; + } + break; + case 'Array(4)': + if (this.output[2] > 0) { + this.TextureConstructor = GLTextureArray4Float3D; + return null; + } else if (this.output[1] > 0) { + this.TextureConstructor = GLTextureArray4Float2D; + return null; + } else { + this.TextureConstructor = GLTextureArray4Float; + return null; + } + } + } + this.renderOutput = this.renderValues; + if (this.subKernels !== null) { + this.renderKernels = this.renderKernelsToArrays; + } + if (this.optimizeFloatMemory) { + switch (this.returnType) { + case 'LiteralInteger': + case 'Float': + case 'Number': + case 'Integer': + if (this.output[2] > 0) { + this.TextureConstructor = GLTextureMemoryOptimized3D; + this.renderStrategy = renderStrategy.MemoryOptimizedFloatPixelToMemoryOptimized3DFloat; + this.formatValues = utils.erectMemoryOptimized3DFloat; + return null; + } else if (this.output[1] > 0) { + this.TextureConstructor = GLTextureMemoryOptimized2D; + this.renderStrategy = renderStrategy.MemoryOptimizedFloatPixelToMemoryOptimized2DFloat; + this.formatValues = utils.erectMemoryOptimized2DFloat; + return null; + } else { + this.TextureConstructor = GLTextureMemoryOptimized; + this.renderStrategy = renderStrategy.MemoryOptimizedFloatPixelToMemoryOptimizedFloat; + this.formatValues = utils.erectMemoryOptimizedFloat; + return null; + } + break; + case 'Array(2)': + if (this.output[2] > 0) { + this.TextureConstructor = GLTextureArray2Float3D; + this.renderStrategy = renderStrategy.FloatPixelTo3DArray2; + this.formatValues = utils.erect3DArray2; + return null; + } else if (this.output[1] > 0) { + this.TextureConstructor = GLTextureArray2Float2D; + this.renderStrategy = renderStrategy.FloatPixelTo2DArray2; + this.formatValues = utils.erect2DArray2; + return null; + } else { + this.TextureConstructor = GLTextureArray2Float; + this.renderStrategy = renderStrategy.FloatPixelToArray2; + this.formatValues = utils.erectArray2; + return null; + } + break; + case 'Array(3)': + if (this.output[2] > 0) { + this.TextureConstructor = GLTextureArray3Float3D; + this.renderStrategy = renderStrategy.FloatPixelTo3DArray3; + this.formatValues = utils.erect3DArray3; + return null; + } else if (this.output[1] > 0) { + this.TextureConstructor = GLTextureArray3Float2D; + this.renderStrategy = renderStrategy.FloatPixelTo2DArray3; + this.formatValues = utils.erect2DArray3; + return null; + } else { + this.TextureConstructor = GLTextureArray3Float; + this.renderStrategy = renderStrategy.FloatPixelToArray3; + this.formatValues = utils.erectArray3; + return null; + } + break; + case 'Array(4)': + if (this.output[2] > 0) { + this.TextureConstructor = GLTextureArray4Float3D; + this.renderStrategy = renderStrategy.FloatPixelTo3DArray4; + this.formatValues = utils.erect3DArray4; + return null; + } else if (this.output[1] > 0) { + this.TextureConstructor = GLTextureArray4Float2D; + this.renderStrategy = renderStrategy.FloatPixelTo2DArray4; + this.formatValues = utils.erect2DArray4; + return null; + } else { + this.TextureConstructor = GLTextureArray4Float; + this.renderStrategy = renderStrategy.FloatPixelToArray4; + this.formatValues = utils.erectArray4; + return null; + } + } + } else { + switch (this.returnType) { + case 'LiteralInteger': + case 'Float': + case 'Number': + case 'Integer': + if (this.output[2] > 0) { + this.TextureConstructor = GLTextureFloat3D; + this.renderStrategy = renderStrategy.FloatPixelTo3DFloat; + this.formatValues = utils.erect3DFloat; + return null; + } else if (this.output[1] > 0) { + this.TextureConstructor = GLTextureFloat2D; + this.renderStrategy = renderStrategy.FloatPixelTo2DFloat; + this.formatValues = utils.erect2DFloat; + return null; + } else { + this.TextureConstructor = GLTextureFloat; + this.renderStrategy = renderStrategy.FloatPixelToFloat; + this.formatValues = utils.erectFloat; + return null; + } + break; + case 'Array(2)': + if (this.output[2] > 0) { + this.TextureConstructor = GLTextureArray2Float3D; + this.renderStrategy = renderStrategy.FloatPixelTo3DArray2; + this.formatValues = utils.erect3DArray2; + return null; + } else if (this.output[1] > 0) { + this.TextureConstructor = GLTextureArray2Float2D; + this.renderStrategy = renderStrategy.FloatPixelTo2DArray2; + this.formatValues = utils.erect2DArray2; + return null; + } else { + this.TextureConstructor = GLTextureArray2Float; + this.renderStrategy = renderStrategy.FloatPixelToArray2; + this.formatValues = utils.erectArray2; + return null; + } + break; + case 'Array(3)': + if (this.output[2] > 0) { + this.TextureConstructor = GLTextureArray3Float3D; + this.renderStrategy = renderStrategy.FloatPixelTo3DArray3; + this.formatValues = utils.erect3DArray3; + return null; + } else if (this.output[1] > 0) { + this.TextureConstructor = GLTextureArray3Float2D; + this.renderStrategy = renderStrategy.FloatPixelTo2DArray3; + this.formatValues = utils.erect2DArray3; + return null; + } else { + this.TextureConstructor = GLTextureArray3Float; + this.renderStrategy = renderStrategy.FloatPixelToArray3; + this.formatValues = utils.erectArray3; + return null; + } + break; + case 'Array(4)': + if (this.output[2] > 0) { + this.TextureConstructor = GLTextureArray4Float3D; + this.renderStrategy = renderStrategy.FloatPixelTo3DArray4; + this.formatValues = utils.erect3DArray4; + return null; + } else if (this.output[1] > 0) { + this.TextureConstructor = GLTextureArray4Float2D; + this.renderStrategy = renderStrategy.FloatPixelTo2DArray4; + this.formatValues = utils.erect2DArray4; + return null; + } else { + this.TextureConstructor = GLTextureArray4Float; + this.renderStrategy = renderStrategy.FloatPixelToArray4; + this.formatValues = utils.erectArray4; + return null; + } + } + } + } else { + throw new Error(`unhandled precision of "${this.precision}"`); + } + + throw new Error(`unhandled return type "${this.returnType}"`); + } + + /** + * @abstract + * @returns String + */ + getKernelString() { + throw new Error(`abstract method call`); + } + + getMainResultTexture() { + switch (this.returnType) { + case 'LiteralInteger': + case 'Float': + case 'Integer': + case 'Number': + return this.getMainResultNumberTexture(); + case 'Array(2)': + return this.getMainResultArray2Texture(); + case 'Array(3)': + return this.getMainResultArray3Texture(); + case 'Array(4)': + return this.getMainResultArray4Texture(); + default: + throw new Error(`unhandled returnType type ${ this.returnType }`); + } + } + + /** + * @abstract + * @returns String[] + */ + getMainResultKernelNumberTexture() { + throw new Error(`abstract method call`); + } + /** + * @abstract + * @returns String[] + */ + getMainResultSubKernelNumberTexture() { + throw new Error(`abstract method call`); + } + /** + * @abstract + * @returns String[] + */ + getMainResultKernelArray2Texture() { + throw new Error(`abstract method call`); + } + /** + * @abstract + * @returns String[] + */ + getMainResultSubKernelArray2Texture() { + throw new Error(`abstract method call`); + } + /** + * @abstract + * @returns String[] + */ + getMainResultKernelArray3Texture() { + throw new Error(`abstract method call`); + } + /** + * @abstract + * @returns String[] + */ + getMainResultSubKernelArray3Texture() { + throw new Error(`abstract method call`); + } + /** + * @abstract + * @returns String[] + */ + getMainResultKernelArray4Texture() { + throw new Error(`abstract method call`); + } + /** + * @abstract + * @returns String[] + */ + getMainResultSubKernelArray4Texture() { + throw new Error(`abstract method call`); + } + /** + * @abstract + * @returns String[] + */ + getMainResultGraphical() { + throw new Error(`abstract method call`); + } + /** + * @abstract + * @returns String[] + */ + getMainResultMemoryOptimizedFloats() { + throw new Error(`abstract method call`); + } + /** + * @abstract + * @returns String[] + */ + getMainResultPackedPixels() { + throw new Error(`abstract method call`); + } + + getMainResultString() { + if (this.graphical) { + return this.getMainResultGraphical(); + } else if (this.precision === 'single') { + if (this.optimizeFloatMemory) { + return this.getMainResultMemoryOptimizedFloats(); + } + return this.getMainResultTexture(); + } else { + return this.getMainResultPackedPixels(); + } + } + + getMainResultNumberTexture() { + return utils.linesToString(this.getMainResultKernelNumberTexture()) + + utils.linesToString(this.getMainResultSubKernelNumberTexture()); + } + + getMainResultArray2Texture() { + return utils.linesToString(this.getMainResultKernelArray2Texture()) + + utils.linesToString(this.getMainResultSubKernelArray2Texture()); + } + + getMainResultArray3Texture() { + return utils.linesToString(this.getMainResultKernelArray3Texture()) + + utils.linesToString(this.getMainResultSubKernelArray3Texture()); + } + + getMainResultArray4Texture() { + return utils.linesToString(this.getMainResultKernelArray4Texture()) + + utils.linesToString(this.getMainResultSubKernelArray4Texture()); + } + + /** + * + * @return {string} + */ + getFloatTacticDeclaration() { + switch (this.tactic) { + case 'speed': + return 'precision lowp float;\n'; + case 'performance': + return 'precision highp float;\n'; + case 'balanced': + default: + return 'precision mediump float;\n'; + } + } + + /** + * + * @return {string} + */ + getIntTacticDeclaration() { + switch (this.tactic) { + case 'speed': + return 'precision lowp int;\n'; + case 'performance': + return 'precision highp int;\n'; + case 'balanced': + default: + return 'precision mediump int;\n'; + } + } + + /** + * + * @return {string} + */ + getSampler2DTacticDeclaration() { + switch (this.tactic) { + case 'speed': + return 'precision lowp sampler2D;\n'; + case 'performance': + return 'precision highp sampler2D;\n'; + case 'balanced': + default: + return 'precision mediump sampler2D;\n'; + } + } + + getSampler2DArrayTacticDeclaration() { + switch (this.tactic) { + case 'speed': + return 'precision lowp sampler2DArray;\n'; + case 'performance': + return 'precision highp sampler2DArray;\n'; + case 'balanced': + default: + return 'precision mediump sampler2DArray;\n'; + } + } + + renderTexture() { + return new this.TextureConstructor({ + texture: this.outputTexture, + size: this.texSize, + dimensions: this.threadDim, + output: this.output, + context: this.context, + }); + } + readPackedPixelsToUint8Array() { + if (this.precision !== 'unsigned') throw new Error('Requires this.precision to be "unsigned"'); + const { + texSize, + context: gl + } = this; + const result = new Uint8Array(texSize[0] * texSize[1] * 4); + gl.readPixels(0, 0, texSize[0], texSize[1], gl.RGBA, gl.UNSIGNED_BYTE, result); + return result; + } + + readPackedPixelsToFloat32Array() { + return new Float32Array(this.readPackedPixelsToUint8Array().buffer); + } + + readFloatPixelsToFloat32Array() { + if (this.precision !== 'single') throw new Error('Requires this.precision to be "single"'); + const { + texSize, + context: gl + } = this; + const w = texSize[0]; + const h = texSize[1]; + const result = new Float32Array(w * h * 4); + gl.readPixels(0, 0, w, h, gl.RGBA, gl.FLOAT, result); + return result; + } + + readMemoryOptimizedFloatPixelsToFloat32Array() { + if (this.precision !== 'single') throw new Error('Requires this.precision to be "single"'); + const { + texSize, + context: gl + } = this; + const w = texSize[0]; + const h = texSize[1]; + const result = new Float32Array(w * h * 4); + gl.readPixels(0, 0, w, h, gl.RGBA, gl.FLOAT, result); + return result; + } + + /** + * + * @param {Boolean} [flip] + * @return {Uint8Array} + */ + getPixels(flip) { + const { + context: gl, + output + } = this; + const [width, height] = output; + const pixels = new Uint8Array(width * height * 4); + gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels); + // flipped by default, so invert + return new Uint8ClampedArray((flip ? pixels : utils.flipPixels(pixels, width, height)).buffer); + } + + renderKernelsToArrays() { + const result = { + result: this.renderOutput(), + }; + for (let i = 0; i < this.subKernels.length; i++) { + result[this.subKernels[i].property] = new this.TextureConstructor({ + texture: this.subKernelOutputTextures[i], + size: this.texSize, + dimensions: this.threadDim, + output: this.output, + context: this.context, + }).toArray(); + } + return result; + } + + renderKernelsToTextures() { + const result = { + result: this.renderOutput(), + }; + for (let i = 0; i < this.subKernels.length; i++) { + result[this.subKernels[i].property] = new this.TextureConstructor({ + texture: this.subKernelOutputTextures[i], + size: this.texSize, + dimensions: this.threadDim, + output: this.output, + context: this.context, + }); + } + return result; + } + + setOutput(output) { + super.setOutput(output); + if (this.program) { + this.threadDim = [this.output[0], this.output[1] || 1, this.output[2] || 1]; + this.texSize = utils.getKernelTextureSize({ + optimizeFloatMemory: this.optimizeFloatMemory, + precision: this.precision, + }, this.output); + const { context: gl } = this; + gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); + this.updateMaxTexSize(); + this.framebuffer.width = this.texSize[0]; + this.framebuffer.height = this.texSize[1]; + this.context.viewport(0, 0, this.maxTexSize[0], this.maxTexSize[1]); + this.canvas.width = this.maxTexSize[0]; + this.canvas.height = this.maxTexSize[1]; + this._setupOutputTexture(); + if (this.subKernels && this.subKernels.length > 0) { + this._setupSubOutputTextures(); + } + } + return this; + } + renderValues() { + return this.formatValues( + this.transferValues(), + this.output[0], + this.output[1], + this.output[2] + ); + } +} + +export const renderStrategy = Object.freeze({ + PackedPixelToUint8Array: Symbol('PackedPixelToUint8Array'), + PackedPixelToFloat: Symbol('PackedPixelToFloat'), + PackedPixelTo2DFloat: Symbol('PackedPixelTo2DFloat'), + PackedPixelTo3DFloat: Symbol('PackedPixelTo3DFloat'), + PackedTexture: Symbol('PackedTexture'), + FloatPixelToFloat32Array: Symbol('FloatPixelToFloat32Array'), + FloatPixelToFloat: Symbol('FloatPixelToFloat'), + FloatPixelTo2DFloat: Symbol('FloatPixelTo2DFloat'), + FloatPixelTo3DFloat: Symbol('FloatPixelTo3DFloat'), + FloatPixelToArray2: Symbol('FloatPixelToArray2'), + FloatPixelTo2DArray2: Symbol('FloatPixelTo2DArray2'), + FloatPixelTo3DArray2: Symbol('FloatPixelTo3DArray2'), + FloatPixelToArray3: Symbol('FloatPixelToArray3'), + FloatPixelTo2DArray3: Symbol('FloatPixelTo2DArray3'), + FloatPixelTo3DArray3: Symbol('FloatPixelTo3DArray3'), + FloatPixelToArray4: Symbol('FloatPixelToArray4'), + FloatPixelTo2DArray4: Symbol('FloatPixelTo2DArray4'), + FloatPixelTo3DArray4: Symbol('FloatPixelTo3DArray4'), + FloatTexture: Symbol('FloatTexture'), + MemoryOptimizedFloatPixelToMemoryOptimizedFloat: Symbol('MemoryOptimizedFloatPixelToFloat'), + MemoryOptimizedFloatPixelToMemoryOptimized2DFloat: Symbol('MemoryOptimizedFloatPixelTo2DFloat'), + MemoryOptimizedFloatPixelToMemoryOptimized3DFloat: Symbol('MemoryOptimizedFloatPixelTo3DFloat'), +}); + +const typeMap = { + int: 'Integer', + float: 'Number', + vec2: 'Array(2)', + vec3: 'Array(3)', + vec4: 'Array(4)', +}; diff --git a/src/backend/gl/texture/array-2-float-2d.js b/src/backend/gl/texture/array-2-float-2d.js index a0e7defd..60e14603 100644 --- a/src/backend/gl/texture/array-2-float-2d.js +++ b/src/backend/gl/texture/array-2-float-2d.js @@ -1,16 +1,12 @@ -const { utils } = require('../../../utils'); -const { GLTextureFloat } = require('./float'); - -class GLTextureArray2Float2D extends GLTextureFloat { - constructor(settings) { - super(settings); - this.type = 'ArrayTexture(2)'; - } - toArray() { - return utils.erect2DArray2(this.renderValues(), this.output[0], this.output[1]); - } -} - -module.exports = { - GLTextureArray2Float2D -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { GLTextureFloat } from './float'; + +export class GLTextureArray2Float2D extends GLTextureFloat { + constructor(settings) { + super(settings); + this.type = 'ArrayTexture(2)'; + } + toArray() { + return utils.erect2DArray2(this.renderValues(), this.output[0], this.output[1]); + } +} diff --git a/src/backend/gl/texture/array-2-float-3d.js b/src/backend/gl/texture/array-2-float-3d.js index 28f5b092..fb960a2f 100644 --- a/src/backend/gl/texture/array-2-float-3d.js +++ b/src/backend/gl/texture/array-2-float-3d.js @@ -1,16 +1,12 @@ -const { utils } = require('../../../utils'); -const { GLTextureFloat } = require('./float'); - -class GLTextureArray2Float3D extends GLTextureFloat { - constructor(settings) { - super(settings); - this.type = 'ArrayTexture(2)'; - } - toArray() { - return utils.erect3DArray2(this.renderValues(), this.output[0], this.output[1], this.output[2]); - } -} - -module.exports = { - GLTextureArray2Float3D -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { GLTextureFloat } from './float'; + +export class GLTextureArray2Float3D extends GLTextureFloat { + constructor(settings) { + super(settings); + this.type = 'ArrayTexture(2)'; + } + toArray() { + return utils.erect3DArray2(this.renderValues(), this.output[0], this.output[1], this.output[2]); + } +} diff --git a/src/backend/gl/texture/array-2-float.js b/src/backend/gl/texture/array-2-float.js index fab1a2ae..122ed4e6 100644 --- a/src/backend/gl/texture/array-2-float.js +++ b/src/backend/gl/texture/array-2-float.js @@ -1,16 +1,12 @@ -const { utils } = require('../../../utils'); -const { GLTextureFloat } = require('./float'); - -class GLTextureArray2Float extends GLTextureFloat { - constructor(settings) { - super(settings); - this.type = 'ArrayTexture(2)'; - } - toArray() { - return utils.erectArray2(this.renderValues(), this.output[0], this.output[1]); - } -} - -module.exports = { - GLTextureArray2Float -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { GLTextureFloat } from './float'; + +export class GLTextureArray2Float extends GLTextureFloat { + constructor(settings) { + super(settings); + this.type = 'ArrayTexture(2)'; + } + toArray() { + return utils.erectArray2(this.renderValues(), this.output[0], this.output[1]); + } +} diff --git a/src/backend/gl/texture/array-3-float-2d.js b/src/backend/gl/texture/array-3-float-2d.js index 4f615879..d5292524 100644 --- a/src/backend/gl/texture/array-3-float-2d.js +++ b/src/backend/gl/texture/array-3-float-2d.js @@ -1,16 +1,12 @@ -const { utils } = require('../../../utils'); -const { GLTextureFloat } = require('./float'); - -class GLTextureArray3Float2D extends GLTextureFloat { - constructor(settings) { - super(settings); - this.type = 'ArrayTexture(3)'; - } - toArray() { - return utils.erect2DArray3(this.renderValues(), this.output[0], this.output[1]); - } -} - -module.exports = { - GLTextureArray3Float2D -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { GLTextureFloat } from './float'; + +export class GLTextureArray3Float2D extends GLTextureFloat { + constructor(settings) { + super(settings); + this.type = 'ArrayTexture(3)'; + } + toArray() { + return utils.erect2DArray3(this.renderValues(), this.output[0], this.output[1]); + } +} diff --git a/src/backend/gl/texture/array-3-float-3d.js b/src/backend/gl/texture/array-3-float-3d.js index ea53aee0..ad8910ad 100644 --- a/src/backend/gl/texture/array-3-float-3d.js +++ b/src/backend/gl/texture/array-3-float-3d.js @@ -1,16 +1,12 @@ -const { utils } = require('../../../utils'); -const { GLTextureFloat } = require('./float'); - -class GLTextureArray3Float3D extends GLTextureFloat { - constructor(settings) { - super(settings); - this.type = 'ArrayTexture(3)'; - } - toArray() { - return utils.erect3DArray3(this.renderValues(), this.output[0], this.output[1], this.output[2]); - } -} - -module.exports = { - GLTextureArray3Float3D -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { GLTextureFloat } from './float'; + +export class GLTextureArray3Float3D extends GLTextureFloat { + constructor(settings) { + super(settings); + this.type = 'ArrayTexture(3)'; + } + toArray() { + return utils.erect3DArray3(this.renderValues(), this.output[0], this.output[1], this.output[2]); + } +} diff --git a/src/backend/gl/texture/array-3-float.js b/src/backend/gl/texture/array-3-float.js index 5f99396b..009ea8d4 100644 --- a/src/backend/gl/texture/array-3-float.js +++ b/src/backend/gl/texture/array-3-float.js @@ -1,16 +1,12 @@ -const { utils } = require('../../../utils'); -const { GLTextureFloat } = require('./float'); - -class GLTextureArray3Float extends GLTextureFloat { - constructor(settings) { - super(settings); - this.type = 'ArrayTexture(3)'; - } - toArray() { - return utils.erectArray3(this.renderValues(), this.output[0]); - } -} - -module.exports = { - GLTextureArray3Float -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { GLTextureFloat } from './float'; + +export class GLTextureArray3Float extends GLTextureFloat { + constructor(settings) { + super(settings); + this.type = 'ArrayTexture(3)'; + } + toArray() { + return utils.erectArray3(this.renderValues(), this.output[0]); + } +} diff --git a/src/backend/gl/texture/array-4-float-2d.js b/src/backend/gl/texture/array-4-float-2d.js index a370eaa2..2c1a2bbe 100644 --- a/src/backend/gl/texture/array-4-float-2d.js +++ b/src/backend/gl/texture/array-4-float-2d.js @@ -1,16 +1,12 @@ -const { utils } = require('../../../utils'); -const { GLTextureFloat } = require('./float'); - -class GLTextureArray4Float2D extends GLTextureFloat { - constructor(settings) { - super(settings); - this.type = 'ArrayTexture(4)'; - } - toArray() { - return utils.erect2DArray4(this.renderValues(), this.output[0], this.output[1]); - } -} - -module.exports = { - GLTextureArray4Float2D -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { GLTextureFloat } from './float'; + +export class GLTextureArray4Float2D extends GLTextureFloat { + constructor(settings) { + super(settings); + this.type = 'ArrayTexture(4)'; + } + toArray() { + return utils.erect2DArray4(this.renderValues(), this.output[0], this.output[1]); + } +} diff --git a/src/backend/gl/texture/array-4-float-3d.js b/src/backend/gl/texture/array-4-float-3d.js index 21e09249..6319565a 100644 --- a/src/backend/gl/texture/array-4-float-3d.js +++ b/src/backend/gl/texture/array-4-float-3d.js @@ -1,16 +1,12 @@ -const { utils } = require('../../../utils'); -const { GLTextureFloat } = require('./float'); - -class GLTextureArray4Float3D extends GLTextureFloat { - constructor(settings) { - super(settings); - this.type = 'ArrayTexture(4)'; - } - toArray() { - return utils.erect3DArray4(this.renderValues(), this.output[0], this.output[1], this.output[2]); - } -} - -module.exports = { - GLTextureArray4Float3D -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { GLTextureFloat } from './float'; + +export class GLTextureArray4Float3D extends GLTextureFloat { + constructor(settings) { + super(settings); + this.type = 'ArrayTexture(4)'; + } + toArray() { + return utils.erect3DArray4(this.renderValues(), this.output[0], this.output[1], this.output[2]); + } +} diff --git a/src/backend/gl/texture/array-4-float.js b/src/backend/gl/texture/array-4-float.js index 80553d15..4ac9d45a 100644 --- a/src/backend/gl/texture/array-4-float.js +++ b/src/backend/gl/texture/array-4-float.js @@ -1,16 +1,12 @@ -const { utils } = require('../../../utils'); -const { GLTextureFloat } = require('./float'); - -class GLTextureArray4Float extends GLTextureFloat { - constructor(settings) { - super(settings); - this.type = 'ArrayTexture(4)'; - } - toArray() { - return utils.erectArray4(this.renderValues(), this.output[0]); - } -} - -module.exports = { - GLTextureArray4Float -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { GLTextureFloat } from './float'; + +export class GLTextureArray4Float extends GLTextureFloat { + constructor(settings) { + super(settings); + this.type = 'ArrayTexture(4)'; + } + toArray() { + return utils.erectArray4(this.renderValues(), this.output[0]); + } +} diff --git a/src/backend/gl/texture/float-2d.js b/src/backend/gl/texture/float-2d.js index 1fb927e9..441e7d71 100644 --- a/src/backend/gl/texture/float-2d.js +++ b/src/backend/gl/texture/float-2d.js @@ -1,16 +1,12 @@ -const { utils } = require('../../../utils'); -const { GLTextureFloat } = require('./float'); - -class GLTextureFloat2D extends GLTextureFloat { - constructor(settings) { - super(settings); - this.type = 'ArrayTexture(1)'; - } - toArray() { - return utils.erect2DFloat(this.renderValues(), this.output[0], this.output[1]); - } -} - -module.exports = { - GLTextureFloat2D -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { GLTextureFloat } from './float'; + +export class GLTextureFloat2D extends GLTextureFloat { + constructor(settings) { + super(settings); + this.type = 'ArrayTexture(1)'; + } + toArray() { + return utils.erect2DFloat(this.renderValues(), this.output[0], this.output[1]); + } +} diff --git a/src/backend/gl/texture/float-3d.js b/src/backend/gl/texture/float-3d.js index 9a8a536f..61a19039 100644 --- a/src/backend/gl/texture/float-3d.js +++ b/src/backend/gl/texture/float-3d.js @@ -1,16 +1,12 @@ -const { utils } = require('../../../utils'); -const { GLTextureFloat } = require('./float'); - -class GLTextureFloat3D extends GLTextureFloat { - constructor(settings) { - super(settings); - this.type = 'ArrayTexture(1)'; - } - toArray() { - return utils.erect3DFloat(this.renderValues(), this.output[0], this.output[1], this.output[2]); - } -} - -module.exports = { - GLTextureFloat3D -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { GLTextureFloat } from './float'; + +export class GLTextureFloat3D extends GLTextureFloat { + constructor(settings) { + super(settings); + this.type = 'ArrayTexture(1)'; + } + toArray() { + return utils.erect3DFloat(this.renderValues(), this.output[0], this.output[1], this.output[2]); + } +} diff --git a/src/backend/gl/texture/float.js b/src/backend/gl/texture/float.js index 8f0b172f..8e050d08 100644 --- a/src/backend/gl/texture/float.js +++ b/src/backend/gl/texture/float.js @@ -1,34 +1,30 @@ -const { utils } = require('../../../utils'); -const { Texture } = require('../../../texture'); - -class GLTextureFloat extends Texture { - constructor(settings) { - super(settings); - this.type = 'ArrayTexture(1)'; - } - renderRawOutput() { - const { context: gl } = this; - const framebuffer = gl.createFramebuffer(); - gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); - gl.framebufferTexture2D( - gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0, - gl.TEXTURE_2D, - this.texture, - 0 - ); - const result = new Float32Array(this.size[0] * this.size[1] * 4); - gl.readPixels(0, 0, this.size[0], this.size[1], gl.RGBA, gl.FLOAT, result); - return result; - } - renderValues() { - return this.renderRawOutput(); - } - toArray() { - return utils.erectFloat(this.renderValues(), this.output[0]); - } -} - -module.exports = { - GLTextureFloat -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { Texture } from '../../../texture'; + +export class GLTextureFloat extends Texture { + constructor(settings) { + super(settings); + this.type = 'ArrayTexture(1)'; + } + renderRawOutput() { + const { context: gl } = this; + const framebuffer = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + this.texture, + 0 + ); + const result = new Float32Array(this.size[0] * this.size[1] * 4); + gl.readPixels(0, 0, this.size[0], this.size[1], gl.RGBA, gl.FLOAT, result); + return result; + } + renderValues() { + return this.renderRawOutput(); + } + toArray() { + return utils.erectFloat(this.renderValues(), this.output[0]); + } +} diff --git a/src/backend/gl/texture/graphical.js b/src/backend/gl/texture/graphical.js index 18360021..d2d8b98e 100644 --- a/src/backend/gl/texture/graphical.js +++ b/src/backend/gl/texture/graphical.js @@ -1,15 +1,11 @@ -const { GLTextureUnsigned } = require('./unsigned'); - -class GLTextureGraphical extends GLTextureUnsigned { - constructor(settings) { - super(settings); - this.type = 'ArrayTexture(4)'; - } - toArray() { - return this.renderValues(); - } -} - -module.exports = { - GLTextureGraphical -}; \ No newline at end of file +import { GLTextureUnsigned } from './unsigned'; + +export class GLTextureGraphical extends GLTextureUnsigned { + constructor(settings) { + super(settings); + this.type = 'ArrayTexture(4)'; + } + toArray() { + return this.renderValues(); + } +} diff --git a/src/backend/gl/texture/memory-optimized-2d.js b/src/backend/gl/texture/memory-optimized-2d.js index 20234ee3..f271274f 100644 --- a/src/backend/gl/texture/memory-optimized-2d.js +++ b/src/backend/gl/texture/memory-optimized-2d.js @@ -1,16 +1,12 @@ -const { utils } = require('../../../utils'); -const { GLTextureFloat } = require('./float'); - -class GLTextureMemoryOptimized2D extends GLTextureFloat { - constructor(settings) { - super(settings); - this.type = 'MemoryOptimizedNumberTexture'; - } - toArray() { - return utils.erectMemoryOptimized2DFloat(this.renderValues(), this.output[0], this.output[1]); - } -} - -module.exports = { - GLTextureMemoryOptimized2D -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { GLTextureFloat } from './float'; + +export class GLTextureMemoryOptimized2D extends GLTextureFloat { + constructor(settings) { + super(settings); + this.type = 'MemoryOptimizedNumberTexture'; + } + toArray() { + return utils.erectMemoryOptimized2DFloat(this.renderValues(), this.output[0], this.output[1]); + } +} diff --git a/src/backend/gl/texture/memory-optimized-3d.js b/src/backend/gl/texture/memory-optimized-3d.js index f65e5f9f..847b7975 100644 --- a/src/backend/gl/texture/memory-optimized-3d.js +++ b/src/backend/gl/texture/memory-optimized-3d.js @@ -1,16 +1,12 @@ -const { utils } = require('../../../utils'); -const { GLTextureFloat } = require('./float'); - -class GLTextureMemoryOptimized3D extends GLTextureFloat { - constructor(settings) { - super(settings); - this.type = 'MemoryOptimizedNumberTexture'; - } - toArray() { - return utils.erectMemoryOptimized3DFloat(this.renderValues(), this.output[0], this.output[1], this.output[2]); - } -} - -module.exports = { - GLTextureMemoryOptimized3D -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { GLTextureFloat } from './float'; + +export class GLTextureMemoryOptimized3D extends GLTextureFloat { + constructor(settings) { + super(settings); + this.type = 'MemoryOptimizedNumberTexture'; + } + toArray() { + return utils.erectMemoryOptimized3DFloat(this.renderValues(), this.output[0], this.output[1], this.output[2]); + } +} diff --git a/src/backend/gl/texture/memory-optimized.js b/src/backend/gl/texture/memory-optimized.js index 03474f58..f686b15a 100644 --- a/src/backend/gl/texture/memory-optimized.js +++ b/src/backend/gl/texture/memory-optimized.js @@ -1,16 +1,12 @@ -const { utils } = require('../../../utils'); -const { GLTextureFloat } = require('./float'); - -class GLTextureMemoryOptimized extends GLTextureFloat { - constructor(settings) { - super(settings); - this.type = 'MemoryOptimizedNumberTexture'; - } - toArray() { - return utils.erectMemoryOptimizedFloat(this.renderValues(), this.output[0]); - } -} - -module.exports = { - GLTextureMemoryOptimized -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { GLTextureFloat } from './float'; + +export class GLTextureMemoryOptimized extends GLTextureFloat { + constructor(settings) { + super(settings); + this.type = 'MemoryOptimizedNumberTexture'; + } + toArray() { + return utils.erectMemoryOptimizedFloat(this.renderValues(), this.output[0]); + } +} diff --git a/src/backend/gl/texture/unsigned-2d.js b/src/backend/gl/texture/unsigned-2d.js index 3adba8b1..ab717c9a 100644 --- a/src/backend/gl/texture/unsigned-2d.js +++ b/src/backend/gl/texture/unsigned-2d.js @@ -1,16 +1,12 @@ -const { utils } = require('../../../utils'); -const { GLTextureUnsigned } = require('./unsigned'); - -class GLTextureUnsigned2D extends GLTextureUnsigned { - constructor(settings) { - super(settings); - this.type = 'NumberTexture'; - } - toArray() { - return utils.erect2DPackedFloat(this.renderValues(), this.output[0], this.output[1]); - } -} - -module.exports = { - GLTextureUnsigned2D -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { GLTextureUnsigned } from './unsigned'; + +export class GLTextureUnsigned2D extends GLTextureUnsigned { + constructor(settings) { + super(settings); + this.type = 'NumberTexture'; + } + toArray() { + return utils.erect2DPackedFloat(this.renderValues(), this.output[0], this.output[1]); + } +} diff --git a/src/backend/gl/texture/unsigned-3d.js b/src/backend/gl/texture/unsigned-3d.js index ccedf143..343aff56 100644 --- a/src/backend/gl/texture/unsigned-3d.js +++ b/src/backend/gl/texture/unsigned-3d.js @@ -1,16 +1,12 @@ -const { utils } = require('../../../utils'); -const { GLTextureUnsigned } = require('./unsigned'); - -class GLTextureUnsigned3D extends GLTextureUnsigned { - constructor(settings) { - super(settings); - this.type = 'NumberTexture'; - } - toArray() { - return utils.erect3DPackedFloat(this.renderValues(), this.output[0], this.output[1], this.output[2]); - } -} - -module.exports = { - GLTextureUnsigned3D -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { GLTextureUnsigned } from './unsigned'; + +export class GLTextureUnsigned3D extends GLTextureUnsigned { + constructor(settings) { + super(settings); + this.type = 'NumberTexture'; + } + toArray() { + return utils.erect3DPackedFloat(this.renderValues(), this.output[0], this.output[1], this.output[2]); + } +} diff --git a/src/backend/gl/texture/unsigned.js b/src/backend/gl/texture/unsigned.js index e67de8db..294b4f28 100644 --- a/src/backend/gl/texture/unsigned.js +++ b/src/backend/gl/texture/unsigned.js @@ -1,34 +1,30 @@ -const { utils } = require('../../../utils'); -const { Texture } = require('../../../texture'); - -class GLTextureUnsigned extends Texture { - constructor(settings) { - super(settings); - this.type = 'NumberTexture'; - } - renderRawOutput() { - const { context: gl } = this; - const framebuffer = gl.createFramebuffer(); - gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); - gl.framebufferTexture2D( - gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0, - gl.TEXTURE_2D, - this.texture, - 0 - ); - const result = new Uint8Array(this.size[0] * this.size[1] * 4); - gl.readPixels(0, 0, this.size[0], this.size[1], gl.RGBA, gl.UNSIGNED_BYTE, result); - return result; - } - renderValues() { - return new Float32Array(this.renderRawOutput().buffer); - } - toArray() { - return utils.erectPackedFloat(this.renderValues(), this.output[0]); - } -} - -module.exports = { - GLTextureUnsigned -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { Texture } from '../../../texture'; + +export class GLTextureUnsigned extends Texture { + constructor(settings) { + super(settings); + this.type = 'NumberTexture'; + } + renderRawOutput() { + const { context: gl } = this; + const framebuffer = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + this.texture, + 0 + ); + const result = new Uint8Array(this.size[0] * this.size[1] * 4); + gl.readPixels(0, 0, this.size[0], this.size[1], gl.RGBA, gl.UNSIGNED_BYTE, result); + return result; + } + renderValues() { + return new Float32Array(this.renderRawOutput().buffer); + } + toArray() { + return utils.erectPackedFloat(this.renderValues(), this.output[0]); + } +} diff --git a/src/backend/headless-gl/kernel.js b/src/backend/headless-gl/kernel.js index 2b3ee845..8f208e82 100644 --- a/src/backend/headless-gl/kernel.js +++ b/src/backend/headless-gl/kernel.js @@ -1,157 +1,154 @@ -const getContext = require('gl'); -const { WebGLKernel } = require('../web-gl/kernel'); -const { glKernelString } = require('../gl/kernel-string'); - -let isSupported = null; -let testCanvas = null; -let testContext = null; -let testExtensions = null; -let features = null; - -class HeadlessGLKernel extends WebGLKernel { - static get isSupported() { - if (isSupported !== null) return isSupported; - this.setupFeatureChecks(); - isSupported = testContext !== null; - return isSupported; - } - - static setupFeatureChecks() { - testCanvas = null; - testExtensions = null; - if (typeof getContext !== 'function') return; - try { // just in case, edge cases - testContext = getContext(2, 2, { - preserveDrawingBuffer: true - }); - if (!testContext || !testContext.getExtension) return; - testExtensions = { - STACKGL_resize_drawingbuffer: testContext.getExtension('STACKGL_resize_drawingbuffer'), - STACKGL_destroy_context: testContext.getExtension('STACKGL_destroy_context'), - OES_texture_float: testContext.getExtension('OES_texture_float'), - OES_texture_float_linear: testContext.getExtension('OES_texture_float_linear'), - OES_element_index_uint: testContext.getExtension('OES_element_index_uint'), - WEBGL_draw_buffers: testContext.getExtension('WEBGL_draw_buffers'), - }; - features = this.getFeatures(); - } catch (e) { - console.warn(e); - } - } - - static isContextMatch(context) { - try { - return context.getParameter(context.RENDERER) === 'ANGLE'; - } catch (e) { - return false; - } - } - - static getFeatures() { - const isDrawBuffers = this.getIsDrawBuffers(); - return Object.freeze({ - isFloatRead: this.getIsFloatRead(), - isIntegerDivisionAccurate: this.getIsIntegerDivisionAccurate(), - isTextureFloat: this.getIsTextureFloat(), - isDrawBuffers, - kernelMap: isDrawBuffers, - channelCount: this.getChannelCount(), - maxTextureSize: this.getMaxTextureSize(), - }); - } - - static getIsTextureFloat() { - return Boolean(testExtensions.OES_texture_float); - } - - static getIsDrawBuffers() { - return Boolean(testExtensions.WEBGL_draw_buffers); - } - - static getChannelCount() { - return testExtensions.WEBGL_draw_buffers ? - testContext.getParameter(testExtensions.WEBGL_draw_buffers.MAX_DRAW_BUFFERS_WEBGL) : - 1; - } - - static getMaxTextureSize() { - return testContext.getParameter(testContext.MAX_TEXTURE_SIZE); - } - - static get testCanvas() { - return testCanvas; - } - - static get testContext() { - return testContext; - } - - static get features() { - return features; - } - - initCanvas() { - return {}; - } - - initContext() { - const context = getContext(2, 2, { - preserveDrawingBuffer: true - }); - return context; - } - - initExtensions() { - this.extensions = { - STACKGL_resize_drawingbuffer: this.context.getExtension('STACKGL_resize_drawingbuffer'), - STACKGL_destroy_context: this.context.getExtension('STACKGL_destroy_context'), - OES_texture_float: this.context.getExtension('OES_texture_float'), - OES_texture_float_linear: this.context.getExtension('OES_texture_float_linear'), - OES_element_index_uint: this.context.getExtension('OES_element_index_uint'), - WEBGL_draw_buffers: this.context.getExtension('WEBGL_draw_buffers'), - }; - } - - build() { - super.build.apply(this, arguments); - if (!this.fallbackRequested) { - this.extensions.STACKGL_resize_drawingbuffer.resize(this.maxTexSize[0], this.maxTexSize[1]); - } - } - - destroyExtensions() { - this.extensions.STACKGL_resize_drawingbuffer = null; - this.extensions.STACKGL_destroy_context = null; - this.extensions.OES_texture_float = null; - this.extensions.OES_texture_float_linear = null; - this.extensions.OES_element_index_uint = null; - this.extensions.WEBGL_draw_buffers = null; - } - - static destroyContext(context) { - const extension = context.getExtension('STACKGL_destroy_context'); - if (extension && extension.destroy) { - extension.destroy(); - } - } - - /** - * @desc Returns the *pre-compiled* Kernel as a JS Object String, that can be reused. - */ - toString() { - const setupContextString = `const gl = context || require('gl')(1, 1);\n`; - const destroyContextString = ` if (!context) { gl.getExtension('STACKGL_destroy_context').destroy(); }\n`; - return glKernelString(this.constructor, arguments, this, setupContextString, destroyContextString); - } - - setOutput(output) { - super.setOutput(output); - if (this.graphical && this.extensions.STACKGL_resize_drawingbuffer) { - this.extensions.STACKGL_resize_drawingbuffer.resize(this.maxTexSize[0], this.maxTexSize[1]); - } - } -} - -module.exports = { - HeadlessGLKernel -}; \ No newline at end of file +import getContext from 'gl' +import { WebGLKernel } from '../web-gl/kernel'; +import { glKernelString } from '../gl/kernel-string'; + +let isSupported = null; +let testCanvas = null; +let testContext = null; +let testExtensions = null; +let features = null; + +export class HeadlessGLKernel extends WebGLKernel { + static get isSupported() { + if (isSupported !== null) return isSupported; + this.setupFeatureChecks(); + isSupported = testContext !== null; + return isSupported; + } + + static setupFeatureChecks() { + testCanvas = null; + testExtensions = null; + if (typeof getContext !== 'function') return; + try { + // Edge cases (just in case) + testContext = getContext(2, 2, { + preserveDrawingBuffer: true + }); + if (!testContext || !testContext.getExtension) return; + testExtensions = { + STACKGL_resize_drawingbuffer: testContext.getExtension('STACKGL_resize_drawingbuffer'), + STACKGL_destroy_context: testContext.getExtension('STACKGL_destroy_context'), + OES_texture_float: testContext.getExtension('OES_texture_float'), + OES_texture_float_linear: testContext.getExtension('OES_texture_float_linear'), + OES_element_index_uint: testContext.getExtension('OES_element_index_uint'), + WEBGL_draw_buffers: testContext.getExtension('WEBGL_draw_buffers'), + }; + features = this.getFeatures(); + } catch (e) { + console.warn(e); + } + } + + static isContextMatch(context) { + try { + return context.getParameter(context.RENDERER) === 'ANGLE'; + } catch (e) { + return false; + } + } + + static getFeatures() { + const isDrawBuffers = this.getIsDrawBuffers(); + return Object.freeze({ + isFloatRead: this.getIsFloatRead(), + isIntegerDivisionAccurate: this.getIsIntegerDivisionAccurate(), + isTextureFloat: this.getIsTextureFloat(), + isDrawBuffers, + kernelMap: isDrawBuffers, + channelCount: this.getChannelCount(), + maxTextureSize: this.getMaxTextureSize(), + }); + } + + static getIsTextureFloat() { + return Boolean(testExtensions.OES_texture_float); + } + + static getIsDrawBuffers() { + return Boolean(testExtensions.WEBGL_draw_buffers); + } + + static getChannelCount() { + return testExtensions.WEBGL_draw_buffers ? + testContext.getParameter(testExtensions.WEBGL_draw_buffers.MAX_DRAW_BUFFERS_WEBGL) : + 1; + } + + static get testCanvas() { + return testCanvas; + } + + static getMaxTextureSize() { + return testContext.getParameter(testContext.MAX_TEXTURE_SIZE); + } + + static get testContext() { + return testContext; + } + + static get features() { + return features; + } + + initCanvas() { + return {}; + } + + initContext() { + const context = getContext(2, 2, { + preserveDrawingBuffer: true + }); + return context; + } + + initExtensions() { + this.extensions = { + STACKGL_resize_drawingbuffer: this.context.getExtension('STACKGL_resize_drawingbuffer'), + STACKGL_destroy_context: this.context.getExtension('STACKGL_destroy_context'), + OES_texture_float: this.context.getExtension('OES_texture_float'), + OES_texture_float_linear: this.context.getExtension('OES_texture_float_linear'), + OES_element_index_uint: this.context.getExtension('OES_element_index_uint'), + WEBGL_draw_buffers: this.context.getExtension('WEBGL_draw_buffers'), + }; + } + + build() { + super.build.apply(this, arguments); + if (!this.fallbackRequested) { + this.extensions.STACKGL_resize_drawingbuffer.resize(this.maxTexSize[0], this.maxTexSize[1]); + } + } + + destroyExtensions() { + this.extensions.STACKGL_resize_drawingbuffer = null; + this.extensions.STACKGL_destroy_context = null; + this.extensions.OES_texture_float = null; + this.extensions.OES_texture_float_linear = null; + this.extensions.OES_element_index_uint = null; + this.extensions.WEBGL_draw_buffers = null; + } + + static destroyContext(context) { + const extension = context.getExtension('STACKGL_destroy_context'); + if (extension && extension.destroy) { + extension.destroy(); + } + } + + /** + * @desc Returns the *pre-compiled* Kernel as a JS Object String, that can be reused. + */ + toString() { + const setupContextString = `const gl = context || require('gl')(1, 1);\n`; + const destroyContextString = ` if (!context) { gl.getExtension('STACKGL_destroy_context').destroy(); }\n`; + return glKernelString(this.constructor, arguments, this, setupContextString, destroyContextString); + } + + setOutput(output) { + super.setOutput(output); + if (this.graphical && this.extensions.STACKGL_resize_drawingbuffer) { + this.extensions.STACKGL_resize_drawingbuffer.resize(this.maxTexSize[0], this.maxTexSize[1]); + } + } +}; diff --git a/src/backend/kernel-value.js b/src/backend/kernel-value.js index ca1f9211..826de1f8 100644 --- a/src/backend/kernel-value.js +++ b/src/backend/kernel-value.js @@ -1,70 +1,67 @@ -/** - * @class KernelValue - */ -class KernelValue { - /** - * @param {KernelVariable} value - * @param {IKernelValueSettings} settings - */ - constructor(value, settings) { - const { - name, - kernel, - context, - checkContext, - onRequestContextHandle, - onUpdateValueMismatch, - origin, - strictIntegers, - type, - tactic, - } = settings; - if (!name) { - throw new Error('name not set'); - } - if (!type) { - throw new Error('type not set'); - } - if (!origin) { - throw new Error('origin not set'); - } - if (!tactic) { - throw new Error('tactic not set'); - } - if (origin !== 'user' && origin !== 'constants') { - throw new Error(`origin must be "user" or "constants" value is "${ origin }"`); - } - if (!onRequestContextHandle) { - throw new Error('onRequestContextHandle is not set'); - } - this.name = name; - this.origin = origin; - this.tactic = tactic; - this.id = `${this.origin}_${name}`; - this.varName = origin === 'constants' ? `constants.${name}` : name; - this.kernel = kernel; - this.strictIntegers = strictIntegers; - // handle textures - this.type = value.type || type; - this.size = value.size || null; - this.index = null; - this.context = context; - this.checkContext = checkContext !== null && checkContext !== undefined ? checkContext : true; - this.contextHandle = null; - this.onRequestContextHandle = onRequestContextHandle; - this.onUpdateValueMismatch = onUpdateValueMismatch; - this.forceUploadEachRun = null; - } - - getSource() { - throw new Error(`"getSource" not defined on ${ this.constructor.name }`); - } - - updateValue(value) { - throw new Error(`"updateValue" not defined on ${ this.constructor.name }`); - } -} - -module.exports = { - KernelValue -}; \ No newline at end of file +/** + * @class KernelValue + */ +export class KernelValue { + /** + * + * @param {KernelVariable} value + * @param {IKernelValueSettings} settings + */ + constructor(value, settings) { + const { + name, + kernel, + context, + checkContext, + onRequestContextHandle, + onUpdateValueMismatch, + origin, + strictIntegers, + type, + tactic, + } = settings; + if (!name) { + throw new Error('name not set'); + } + if (!type) { + throw new Error('type not set'); + } + if (!origin) { + throw new Error('origin not set'); + } + if (!tactic) { + throw new Error('tactic not set'); + } + if (origin !== 'user' && origin !== 'constants') { + throw new Error(`origin must be "user" or "constants" value is "${ origin }"`); + } + if (!onRequestContextHandle) { + throw new Error('onRequestContextHandle is not set'); + } + this.name = name; + this.origin = origin; + this.tactic = tactic; + this.id = `${this.origin}_${name}`; + this.varName = origin === 'constants' ? `constants.${name}` : name; + this.kernel = kernel; + this.strictIntegers = strictIntegers; + // handle textures + this.type = value.type || type; + this.size = value.size || null; + this.index = null; + this.context = context; + this.checkContext = checkContext !== null && checkContext !== undefined ? checkContext : true; + this.contextHandle = null; + this.onRequestContextHandle = onRequestContextHandle; + this.onUpdateValueMismatch = onUpdateValueMismatch; + this.forceUploadEachRun = null; + } + + getSource() { + throw new Error(`"getSource" not defined on ${ this.constructor.name }`); + } + + updateValue(value) { + throw new Error(`"updateValue" not defined on ${ this.constructor.name }`); + } +} diff --git a/src/backend/kernel.js b/src/backend/kernel.js index ebcb48c4..58d9fcb6 100644 --- a/src/backend/kernel.js +++ b/src/backend/kernel.js @@ -1,736 +1,738 @@ -const { utils } = require('../utils'); -const { Input } = require('../input'); - -class Kernel { - /** - * @type {Boolean} - */ - static get isSupported() { - throw new Error(`"isSupported" not implemented on ${ this.name }`); - } - - /** - * @type {Boolean} - */ - static isContextMatch(context) { - throw new Error(`"isContextMatch" not implemented on ${ this.name }`); - } - - /** - * @type {IKernelFeatures} - * Used internally to populate the kernel.feature, which is a getter for the output of this value - */ - static getFeatures() { - throw new Error(`"getFeatures" not implemented on ${ this.name }`); - } - - static destroyContext(context) { - throw new Error(`"destroyContext" called on ${ this.name }`); - } - - static nativeFunctionArguments() { - throw new Error(`"nativeFunctionArguments" called on ${ this.name }`); - } - - static nativeFunctionReturnType() { - throw new Error(`"nativeFunctionReturnType" called on ${ this.name }`); - } - - static combineKernels() { - throw new Error(`"combineKernels" called on ${ this.name }`); - } - - /** - * - * @param {string|object} source - * @param [settings] - */ - constructor(source, settings) { - if (typeof source !== 'object') { - if (typeof source !== 'string') { - throw new Error('source not a string'); - } - if (!utils.isFunctionString(source)) { - throw new Error('source not a function string'); - } - } - this.useLegacyEncoder = false; - this.fallbackRequested = false; - this.onRequestFallback = null; - - /** - * Name of the arguments found from parsing source argument - * @type {String[]} - */ - this.argumentNames = typeof source === 'string' ? utils.getArgumentNamesFromString(source) : null; - this.argumentTypes = null; - this.argumentSizes = null; - this.argumentBitRatios = null; - this.kernelArguments = null; - this.kernelConstants = null; - this.forceUploadKernelConstants = null; - - - /** - * The function source - * @type {String} - */ - this.source = source; - - /** - * The size of the kernel's output - * @type {Number[]} - */ - this.output = null; - - /** - * Debug mode - * @type {Boolean} - */ - this.debug = false; - - /** - * Graphical mode - * @type {Boolean} - */ - this.graphical = false; - - /** - * Maximum loops when using argument values to prevent infinity - * @type {Number} - */ - this.loopMaxIterations = 0; - - /** - * Constants used in kernel via `this.constants` - * @type {Object} - */ - this.constants = null; - this.constantTypes = null; - this.constantBitRatios = null; - this.dynamicArguments = false; - this.dynamicOutput = false; - - /** - * - * @type {Object} - */ - this.canvas = null; - - /** - * - * @type {WebGLRenderingContext} - */ - this.context = null; - - /** - * - * @type {Boolean} - */ - this.checkContext = null; - - /** - * - * @type {GPU} - */ - this.gpu = null; - - /** - * - * @type {IGPUFunction[]} - */ - this.functions = null; - - /** - * - * @type {IGPUNativeFunction[]} - */ - this.nativeFunctions = null; - - /** - * - * @type {String} - */ - this.injectedNative = null; - - /** - * - * @type {ISubKernel[]} - */ - this.subKernels = null; - - /** - * - * @type {Boolean} - */ - this.validate = true; - - /** - * Enforces kernel to write to a new array or texture on run - * @type {Boolean} - */ - this.immutable = false; - - /** - * Enforces kernel to write to a texture on run - * @type {Boolean} - */ - this.pipeline = false; - - /** - * Make GPU use single precison or unsigned. Acceptable values: 'single' or 'unsigned' - * @type {String|null} - * @enum 'single' | 'unsigned' - */ - this.precision = null; - - /** - * - * @type {String|null} - * @enum 'speed' | 'balanced' | 'precision' - */ - this.tactic = 'balanced'; - - this.plugins = null; - - this.returnType = null; - this.leadingReturnStatement = null; - this.followingReturnStatement = null; - this.optimizeFloatMemory = null; - this.strictIntegers = false; - this.fixIntegerDivisionAccuracy = null; - this.warnVarUsage = true; - } - - mergeSettings(settings) { - for (let p in settings) { - if (!settings.hasOwnProperty(p) || !this.hasOwnProperty(p)) continue; - switch (p) { - case 'output': - if (!Array.isArray(settings.output)) { - this.setOutput(settings.output); // Flatten output object - continue; - } - break; - case 'functions': - if (typeof settings.functions[0] === 'function') { - this.functions = settings.functions.map(source => utils.functionToIFunction(source)); - continue; - } - break; - case 'graphical': - if (settings[p] && !settings.hasOwnProperty('precision')) { - this.precision = 'unsigned'; - } - this[p] = settings[p]; - continue; - } - this[p] = settings[p]; - } - - if (!this.canvas) this.canvas = this.initCanvas(); - if (!this.context) this.context = this.initContext(); - if (!this.plugins) this.plugins = this.initPlugins(settings); - } - /** - * @desc Builds the Kernel, by compiling Fragment and Vertical Shaders, - * and instantiates the program. - * @abstract - */ - build() { - throw new Error(`"build" not defined on ${ this.constructor.name }`); - } - - /** - * @desc Run the kernel program, and send the output to renderOutput - *This method calls a helper method *renderOutput* to return the result.
- * @returns {Float32Array|Float32Array[]|Float32Array[][]|void} Result The final output of the program, as float, and as Textures for reuse. - * @abstract - */ - run() { - throw new Error(`"run" not defined on ${ this.constructor.name }`) - } - - /** - * @abstract - * @return {Object} - */ - initCanvas() { - throw new Error(`"initCanvas" not defined on ${ this.constructor.name }`); - } - - /** - * @abstract - * @return {Object} - */ - initContext() { - throw new Error(`"initContext" not defined on ${ this.constructor.name }`); - } - - /** - * @param {IFunctionSettings} settings - * @return {Object}; - * @abstract - */ - initPlugins(settings) { - throw new Error(`"initPlugins" not defined on ${ this.constructor.name }`); - } - - /** - * @desc Setup the parameter types for the parameters - * supplied to the Kernel function - * - * @param {IArguments} args - The actual parameters sent to the Kernel - */ - setupArguments(args) { - this.kernelArguments = []; - if (!this.argumentTypes) { - if (!this.argumentTypes) { - this.argumentTypes = []; - for (let i = 0; i < args.length; i++) { - const argType = utils.getVariableType(args[i], this.strictIntegers); - const type = argType === 'Integer' ? 'Number' : argType; - this.argumentTypes.push(type); - this.kernelArguments.push({ - type - }); - } - } - } else { - for (let i = 0; i < this.argumentTypes.length; i++) { - this.kernelArguments.push({ - type: this.argumentTypes[i] - }); - } - } - - // setup sizes - this.argumentSizes = new Array(args.length); - this.argumentBitRatios = new Int32Array(args.length); - - for (let i = 0; i < args.length; i++) { - const arg = args[i]; - this.argumentSizes[i] = arg.constructor === Input ? arg.size : null; - this.argumentBitRatios[i] = this.getBitRatio(arg); - } - - if (this.argumentNames.length !== args.length) { - throw new Error(`arguments are miss-aligned`); - } - } - - /** - * Setup constants - */ - setupConstants() { - this.kernelConstants = []; - let needsConstantTypes = this.constantTypes === null; - if (needsConstantTypes) { - this.constantTypes = {}; - } - this.constantBitRatios = {}; - if (this.constants) { - for (let name in this.constants) { - if (needsConstantTypes) { - const type = utils.getVariableType(this.constants[name], this.strictIntegers); - this.constantTypes[name] = type; - this.kernelConstants.push({ - name, - type - }); - } else { - this.kernelConstants.push({ - name, - type: this.constantTypes[name] - }); - } - this.constantBitRatios[name] = this.getBitRatio(this.constants[name]); - } - } - } - - /** - * - * @param flag - * @return {Kernel} - */ - setOptimizeFloatMemory(flag) { - this.optimizeFloatMemory = flag; - return this; - } - - /** - * @desc Set output dimensions of the kernel function - * @param {Array|Object} output - The output array to set the kernel output size to - */ - setOutput(output) { - if (output.hasOwnProperty('x')) { - if (output.hasOwnProperty('y')) { - if (output.hasOwnProperty('z')) { - this.output = [output.x, output.y, output.z]; - } else { - this.output = [output.x, output.y]; - } - } else { - this.output = [output.x]; - } - } else { - this.output = output; - } - return this; - } - - /** - * @desc Toggle debug mode - * @param {Boolean} flag - true to enable debug - */ - setDebug(flag) { - this.debug = flag; - return this; - } - - /** - * @desc Toggle graphical output mode - * @param {Boolean} flag - true to enable graphical output - */ - setGraphical(flag) { - this.graphical = flag; - this.precision = 'unsigned'; - return this; - } - - /** - * @desc Set the maximum number of loop iterations - * @param {number} max - iterations count - * - */ - setLoopMaxIterations(max) { - this.loopMaxIterations = max; - return this; - } - - /** - * @desc Set Constants - */ - setConstants(constants) { - this.constants = constants; - return this; - } - - /** - * - * @param [IKernelValueTypes] constantTypes - * @return {Kernel} - */ - setConstantTypes(constantTypes) { - this.constantTypes = constantTypes; - return this; - } - - /** - * - * @param {IFunction[]|KernelFunction[]} functions - * @return {Kernel} - */ - setFunctions(functions) { - if (typeof functions[0] === 'function') { - this.functions = functions.map(source => utils.functionToIFunction(source)); - } else { - this.functions = functions; - } - return this; - } - - /** - * - * @param {IGPUNativeFunction} nativeFunctions - * @return {Kernel} - */ - setNativeFunctions(nativeFunctions) { - this.nativeFunctions = nativeFunctions; - return this; - } - - /** - * - * @param {String} injectedNative - * @return {Kernel} - */ - setInjectedNative(injectedNative) { - this.injectedNative = injectedNative; - return this; - } - - /** - * Set writing to texture on/off - * @param flag - * @return {Kernel} - */ - setPipeline(flag) { - this.pipeline = flag; - return this; - } - - /** - * Set precision to 'unsigned' or 'single' - * @param {String} flag 'unsigned' or 'single' - * @return {Kernel} - */ - setPrecision(flag) { - this.precision = flag; - return this; - } - - /** - * @param flag - * @return {Kernel} - * @deprecated - */ - setOutputToTexture(flag) { - utils.warnDeprecated('method', 'setOutputToTexture', 'setPipeline'); - this.pipeline = flag; - return this; - } - - /** - * Set to immutable - * @param flag - * @return {Kernel} - */ - setImmutable(flag) { - this.immutable = flag; - return this; - } - - /** - * @desc Bind the canvas to kernel - * @param {Object} canvas - */ - setCanvas(canvas) { - this.canvas = canvas; - return this; - } - - /** - * @param {Boolean} flag - * @return {Kernel} - */ - setStrictIntegers(flag) { - this.strictIntegers = flag; - return this; - } - - /** - * - * @param flag - * @return {Kernel} - */ - setDynamicOutput(flag) { - this.dynamicOutput = flag; - return this; - } - - /** - * @deprecated - * @param flag - * @return {Kernel} - */ - setHardcodeConstants(flag) { - utils.warnDeprecated('method', 'setHardcodeConstants'); - this.setDynamicOutput(flag); - this.setDynamicArguments(flag); - return this; - } - - /** - * - * @param flag - * @return {Kernel} - */ - setDynamicArguments(flag) { - this.dynamicArguments = flag; - return this; - } - - /** - * @param {Boolean} flag - * @return {Kernel} - */ - setUseLegacyEncoder(flag) { - this.useLegacyEncoder = flag; - return this; - } - - /** - * - * @param {Boolean} flag - * @return {Kernel} - */ - setWarnVarUsage(flag) { - this.warnVarUsage = flag; - return this; - } - - /** - * @deprecated - * @returns {Object} - */ - getCanvas() { - utils.warnDeprecated('method', 'getCanvas'); - return this.canvas; - } - - /** - * @deprecated - * @returns {Object} - */ - getWebGl() { - utils.warnDeprecated('method', 'getWebGl'); - return this.context; - } - - /** - * @desc Bind the webGL instance to kernel - * @param {WebGLRenderingContext} context - webGl instance to bind - */ - setContext(context) { - this.context = context; - return this; - } - - /** - * - * @param [IKernelValueTypes|GPUVariableType[]] argumentTypes - * @return {Kernel} - */ - setArgumentTypes(argumentTypes) { - if (Array.isArray(argumentTypes)) { - this.argumentTypes = argumentTypes; - } else { - this.argumentTypes = []; - for (const p in argumentTypes) { - const argumentIndex = this.argumentNames.indexOf(p); - if (argumentIndex === -1) throw new Error(`unable to find argument ${ p }`); - this.argumentTypes[argumentIndex] = argumentTypes[p]; - } - } - return this; - } - - /** - * - * @param [Tactic] tactic - * @return {Kernel} - */ - setTactic(tactic) { - this.tactic = tactic; - return this; - } - - requestFallback(args) { - if (!this.onRequestFallback) { - throw new Error(`"onRequestFallback" not defined on ${ this.constructor.name }`); - } - this.fallbackRequested = true; - return this.onRequestFallback(args); - } - - /** - * @desc Validate settings - * @abstract - */ - validateSettings() { - throw new Error(`"validateSettings" not defined on ${ this.constructor.name }`); - } - - /** - * @desc Add a sub kernel to the root kernel instance. - * This is what `createKernelMap` uses. - * - * @param {ISubKernel} subKernel - function (as a String) of the subKernel to add - */ - addSubKernel(subKernel) { - if (this.subKernels === null) { - this.subKernels = []; - } - if (!subKernel.source) throw new Error('subKernel missing "source" property'); - if (!subKernel.property && isNaN(subKernel.property)) throw new Error('subKernel missing "property" property'); - if (!subKernel.name) throw new Error('subKernel missing "name" property'); - this.subKernels.push(subKernel); - return this; - } - - /** - * @desc Destroys all memory associated with this kernel - * @param {Boolean} [removeCanvasReferences] remove any associated canvas references - */ - destroy(removeCanvasReferences) { - throw new Error(`"destroy" called on ${ this.constructor.name }`); - } - - /** - * bit storage ratio of source to target 'buffer', i.e. if 8bit array -> 32bit tex = 4 - * @param value - * @returns {number} - */ - getBitRatio(value) { - if (this.precision === 'single') { - // 8 and 16 are upconverted to float32 - return 4; - } else if (Array.isArray(value[0])) { - return this.getBitRatio(value[0]); - } else if (value.constructor === Input) { - return this.getBitRatio(value.value); - } - switch (value.constructor) { - case Uint8ClampedArray: - case Uint8Array: - case Int8Array: - return 1; - case Uint16Array: - case Int16Array: - return 2; - case Float32Array: - case Int32Array: - default: - return 4; - } - } - - /** - * @returns {number[]} - */ - getPixels() { - throw new Error(`"getPixels" called on ${ this.constructor.name }`); - } - - checkOutput() { - if (!this.output || !utils.isArray(this.output)) throw new Error('kernel.output not an array'); - if (this.output.length < 1) throw new Error('kernel.output is empty, needs at least 1 value'); - for (let i = 0; i < this.output.length; i++) { - if (isNaN(this.output[i]) || this.output[i] < 1) { - throw new Error(`${ this.constructor.name }.output[${ i }] incorrectly defined as \`${ this.output[i] }\`, needs to be numeric, and greater than 0`); - } - } - } - - toJSON() { - const settings = { - output: this.output, - threadDim: this.threadDim, - pipeline: this.pipeline, - argumentNames: this.argumentNames, - argumentsTypes: this.argumentTypes, - constants: this.constants, - pluginNames: this.plugins ? this.plugins.map(plugin => plugin.name) : null, - returnType: this.returnType, - }; - return { - settings - }; - } -} - -module.exports = { - Kernel -}; \ No newline at end of file +import { Input } from '../input'; +import { + functionToIFunction, + getArgumentNamesFromString, + isArray, + isFunctionString, + warnDeprecated +} from '../common'; +import { getVariableType } from '../utils'; + +export class Kernel { + /** + * @type {Boolean} + */ + static get isSupported() { + throw new Error(`"isSupported" not implemented on ${ this.name }`); + } + + /** + * @type {Boolean} + */ + static isContextMatch(context) { + throw new Error(`"isContextMatch" not implemented on ${ this.name }`); + } + + /** + * @type {IKernelFeatures} + * Used internally to populate the kernel.feature, which is a getter for the output of this value + */ + static getFeatures() { + throw new Error(`"getFeatures" not implemented on ${ this.name }`); + } + + static destroyContext(context) { + throw new Error(`"destroyContext" called on ${ this.name }`); + } + + static nativeFunctionArguments() { + throw new Error(`"nativeFunctionArguments" called on ${ this.name }`); + } + + static nativeFunctionReturnType() { + throw new Error(`"nativeFunctionReturnType" called on ${ this.name }`); + } + + static combineKernels() { + throw new Error(`"combineKernels" called on ${ this.name }`); + } + + /** + * + * @param {string|object} source + * @param [settings] + */ + constructor(source, settings) { + if (typeof source !== 'object') { + if (typeof source !== 'string') { + throw new Error('source not a string'); + } + if (!isFunctionString(source)) { + throw new Error('source not a function string'); + } + } + this.useLegacyEncoder = false; + this.fallbackRequested = false; + this.onRequestFallback = null; + + /** + * Name of the arguments found from parsing source argument + * @type {String[]} + */ + this.argumentNames = typeof source === 'string' ? getArgumentNamesFromString(source) : null; + this.argumentTypes = null; + this.argumentSizes = null; + this.argumentBitRatios = null; + this.kernelArguments = null; + this.kernelConstants = null; + + + /** + * The function source + * @type {String} + */ + this.source = source; + + /** + * The size of the kernel's output + * @type {Number[]} + */ + this.output = null; + + /** + * Debug mode + * @type {Boolean} + */ + this.debug = false; + + /** + * Graphical mode + * @type {Boolean} + */ + this.graphical = false; + + /** + * Maximum loops when using argument values to prevent infinity + * @type {Number} + */ + this.loopMaxIterations = 0; + + /** + * Constants used in kernel via `this.constants` + * @type {Object} + */ + this.constants = null; + this.constantTypes = null; + this.constantBitRatios = null; + this.dynamicArguments = false; + this.dynamicOutput = false; + + /** + * + * @type {Object} + */ + this.canvas = null; + + /** + * + * @type {WebGLRenderingContext} + */ + this.context = null; + + /** + * + * @type {Boolean} + */ + this.checkContext = null; + + /** + * + * @type {GPU} + */ + this.gpu = null; + + /** + * + * @type {IGPUFunction[]} + */ + this.functions = null; + + /** + * + * @type {IGPUNativeFunction[]} + */ + this.nativeFunctions = null; + + /** + * + * @type {String} + */ + this.injectedNative = null; + + /** + * + * @type {ISubKernel[]} + */ + this.subKernels = null; + + /** + * + * @type {Boolean} + */ + this.validate = true; + + /** + * Enforces kernel to write to a new array or texture on run + * @type {Boolean} + */ + this.immutable = false; + + /** + * Enforces kernel to write to a texture on run + * @type {Boolean} + */ + this.pipeline = false; + + /** + * Make GPU use single precison or unsigned. Acceptable values: 'single' or 'unsigned' + * @type {String|null} + * @enum 'single' | 'unsigned' + */ + this.precision = null; + + /** + * + * @type {String|null} + * @enum 'speed' | 'balanced' | 'precision' + */ + this.tactic = 'balanced'; + + this.plugins = null; + + this.returnType = null; + this.leadingReturnStatement = null; + this.followingReturnStatement = null; + this.optimizeFloatMemory = null; + this.strictIntegers = false; + this.fixIntegerDivisionAccuracy = null; + this.warnVarUsage = true; + } + + mergeSettings(settings) { + for (let p in settings) { + if (!settings.hasOwnProperty(p) || !this.hasOwnProperty(p)) continue; + switch (p) { + case 'output': + if (!Array.isArray(settings.output)) { + this.setOutput(settings.output); // Flatten output object + continue; + } + break; + case 'functions': + if (typeof settings.functions[0] === 'function') { + this.functions = settings.functions.map(source => functionToIFunction(source)); + continue; + } + break; + case 'graphical': + if (settings[p] && !settings.hasOwnProperty('precision')) { + this.precision = 'unsigned'; + } + this[p] = settings[p]; + continue; + } + this[p] = settings[p]; + } + + if (!this.canvas) this.canvas = this.initCanvas(); + if (!this.context) this.context = this.initContext(); + if (!this.plugins) this.plugins = this.initPlugins(settings); + } + /** + * @desc Builds the Kernel, by compiling Fragment and Vertical Shaders, + * and instantiates the program. + * @abstract + */ + build() { + throw new Error(`"build" not defined on ${ this.constructor.name }`); + } + + /** + * @desc Run the kernel program, and send the output to renderOutput + *This method calls a helper method *renderOutput* to return the result.
+ * @returns {Float32Array|Float32Array[]|Float32Array[][]|void} Result The final output of the program, as float, and as Textures for reuse. + * @abstract + */ + run() { + throw new Error(`"run" not defined on ${ this.constructor.name }`) + } + + /** + * @abstract + * @return {Object} + */ + initCanvas() { + throw new Error(`"initCanvas" not defined on ${ this.constructor.name }`); + } + + /** + * @abstract + * @return {Object} + */ + initContext() { + throw new Error(`"initContext" not defined on ${ this.constructor.name }`); + } + + /** + * @param {IFunctionSettings} settings + * @return {Object}; + * @abstract + */ + initPlugins(settings) { + throw new Error(`"initPlugins" not defined on ${ this.constructor.name }`); + } + + /** + * @desc Setup the parameter types for the parameters + * supplied to the Kernel function + * + * @param {IArguments} args - The actual parameters sent to the Kernel + */ + setupArguments(args) { + this.kernelArguments = []; + if (!this.argumentTypes) { + if (!this.argumentTypes) { + this.argumentTypes = []; + for (let i = 0; i < args.length; i++) { + const argType = getVariableType(args[i], this.strictIntegers); + const type = argType === 'Integer' ? 'Number' : argType; + this.argumentTypes.push(type); + this.kernelArguments.push({ + type + }); + } + } + } else { + for (let i = 0; i < this.argumentTypes.length; i++) { + this.kernelArguments.push({ + type: this.argumentTypes[i] + }); + } + } + + // setup sizes + this.argumentSizes = new Array(args.length); + this.argumentBitRatios = new Int32Array(args.length); + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + this.argumentSizes[i] = arg.constructor === Input ? arg.size : null; + this.argumentBitRatios[i] = this.getBitRatio(arg); + } + + if (this.argumentNames.length !== args.length) { + throw new Error(`arguments are miss-aligned`); + } + } + + /** + * Setup constants + */ + setupConstants() { + this.kernelConstants = []; + let needsConstantTypes = this.constantTypes === null; + if (needsConstantTypes) { + this.constantTypes = {}; + } + this.constantBitRatios = {}; + if (this.constants) { + for (let name in this.constants) { + if (needsConstantTypes) { + const type = getVariableType(this.constants[name], this.strictIntegers); + this.constantTypes[name] = type; + this.kernelConstants.push({ + name, + type + }); + } else { + this.kernelConstants.push({ + name, + type: this.constantTypes[name] + }); + } + this.constantBitRatios[name] = this.getBitRatio(this.constants[name]); + } + } + } + + /** + * + * @param flag + * @return {Kernel} + */ + setOptimizeFloatMemory(flag) { + this.optimizeFloatMemory = flag; + return this; + } + + /** + * @desc Set output dimensions of the kernel function + * @param {Array|Object} output - The output array to set the kernel output size to + */ + setOutput(output) { + if (output.hasOwnProperty('x')) { + if (output.hasOwnProperty('y')) { + if (output.hasOwnProperty('z')) { + this.output = [output.x, output.y, output.z]; + } else { + this.output = [output.x, output.y]; + } + } else { + this.output = [output.x]; + } + } else { + this.output = output; + } + return this; + } + + /** + * @desc Toggle debug mode + * @param {Boolean} flag - true to enable debug + */ + setDebug(flag) { + this.debug = flag; + return this; + } + + /** + * @desc Toggle graphical output mode + * @param {Boolean} flag - true to enable graphical output + */ + setGraphical(flag) { + this.graphical = flag; + this.precision = 'unsigned'; + return this; + } + + /** + * @desc Set the maximum number of loop iterations + * @param {number} max - iterations count + * + */ + setLoopMaxIterations(max) { + this.loopMaxIterations = max; + return this; + } + + /** + * @desc Set Constants + */ + setConstants(constants) { + this.constants = constants; + return this; + } + + /** + * + * @param [IKernelValueTypes] constantTypes + * @return {Kernel} + */ + setConstantTypes(constantTypes) { + this.constantTypes = constantTypes; + return this; + } + + /** + * + * @param {IFunction[]|KernelFunction[]} functions + * @return {Kernel} + */ + setFunctions(functions) { + if (typeof functions[0] === 'function') { + this.functions = functions.map(source => functionToIFunction(source)); + } else { + this.functions = functions; + } + return this; + } + + /** + * + * @param {IGPUNativeFunction} nativeFunctions + * @return {Kernel} + */ + setNativeFunctions(nativeFunctions) { + this.nativeFunctions = nativeFunctions; + return this; + } + + /** + * + * @param {String} injectedNative + * @return {Kernel} + */ + setInjectedNative(injectedNative) { + this.injectedNative = injectedNative; + return this; + } + + /** + * Set writing to texture on/off + * @param flag + * @return {Kernel} + */ + setPipeline(flag) { + this.pipeline = flag; + return this; + } + + /** + * Set precision to 'unsigned' or 'single' + * @param {String} flag 'unsigned' or 'single' + * @return {Kernel} + */ + setPrecision(flag) { + this.precision = flag; + return this; + } + + /** + * @param flag + * @return {Kernel} + * @deprecated + */ + setOutputToTexture(flag) { + warnDeprecated('method', 'setOutputToTexture', 'setPipeline'); + this.pipeline = flag; + return this; + } + + /** + * Set to immutable + * @param flag + * @return {Kernel} + */ + setImmutable(flag) { + this.immutable = flag; + return this; + } + + /** + * @desc Bind the canvas to kernel + * @param {Object} canvas + */ + setCanvas(canvas) { + this.canvas = canvas; + return this; + } + + /** + * @param {Boolean} flag + * @return {Kernel} + */ + setStrictIntegers(flag) { + this.strictIntegers = flag; + return this; + } + + /** + * + * @param flag + * @return {Kernel} + */ + setDynamicOutput(flag) { + this.dynamicOutput = flag; + return this; + } + + /** + * @deprecated + * @param flag + * @return {Kernel} + */ + setHardcodeConstants(flag) { + warnDeprecated('method', 'setHardcodeConstants'); + this.setDynamicOutput(flag); + this.setDynamicArguments(flag); + return this; + } + + /** + * + * @param flag + * @return {Kernel} + */ + setDynamicArguments(flag) { + this.dynamicArguments = flag; + return this; + } + + /** + * @param {Boolean} flag + * @return {Kernel} + */ + setUseLegacyEncoder(flag) { + this.useLegacyEncoder = flag; + return this; + } + + /** + * + * @param {Boolean} flag + * @return {Kernel} + */ + setWarnVarUsage(flag) { + this.warnVarUsage = flag; + return this; + } + + /** + * @deprecated + * @returns {Object} + */ + getCanvas() { + warnDeprecated('method', 'getCanvas'); + return this.canvas; + } + + /** + * @deprecated + * @returns {Object} + */ + getWebGl() { + warnDeprecated('method', 'getWebGl'); + return this.context; + } + + /** + * @desc Bind the webGL instance to kernel + * @param {WebGLRenderingContext} context - webGl instance to bind + */ + setContext(context) { + this.context = context; + return this; + } + + /** + * + * @param [IKernelValueTypes|GPUVariableType[]] argumentTypes + * @return {Kernel} + */ + setArgumentTypes(argumentTypes) { + if (Array.isArray(argumentTypes)) { + this.argumentTypes = argumentTypes; + } else { + this.argumentTypes = []; + for (const p in argumentTypes) { + const argumentIndex = this.argumentNames.indexOf(p); + if (argumentIndex === -1) throw new Error(`unable to find argument ${ p }`); + this.argumentTypes[argumentIndex] = argumentTypes[p]; + } + } + return this; + } + + /** + * + * @param [Tactic] tactic + * @return {Kernel} + */ + setTactic(tactic) { + this.tactic = tactic; + return this; + } + + requestFallback(args) { + if (!this.onRequestFallback) { + throw new Error(`"onRequestFallback" not defined on ${ this.constructor.name }`); + } + this.fallbackRequested = true; + return this.onRequestFallback(args); + } + + /** + * @desc Validate settings + * @abstract + */ + validateSettings() { + throw new Error(`"validateSettings" not defined on ${ this.constructor.name }`); + } + + /** + * @desc Add a sub kernel to the root kernel instance. + * This is what `createKernelMap` uses. + * + * @param {ISubKernel} subKernel - function (as a String) of the subKernel to add + */ + addSubKernel(subKernel) { + if (this.subKernels === null) { + this.subKernels = []; + } + if (!subKernel.source) throw new Error('subKernel missing "source" property'); + if (!subKernel.property && isNaN(subKernel.property)) throw new Error('subKernel missing "property" property'); + if (!subKernel.name) throw new Error('subKernel missing "name" property'); + this.subKernels.push(subKernel); + return this; + } + + /** + * @desc Destroys all memory associated with this kernel + * @param {Boolean} [removeCanvasReferences] remove any associated canvas references + */ + destroy(removeCanvasReferences) { + throw new Error(`"destroy" called on ${ this.constructor.name }`); + } + + /** + * bit storage ratio of source to target 'buffer', i.e. if 8bit array -> 32bit tex = 4 + * @param value + * @returns {number} + */ + getBitRatio(value) { + if (this.precision === 'single') { + // 8 and 16 are upconverted to float32 + return 4; + } else if (Array.isArray(value[0])) { + return this.getBitRatio(value[0]); + } else if (value.constructor === Input) { + return this.getBitRatio(value.value); + } + switch (value.constructor) { + case Uint8ClampedArray: + case Uint8Array: + case Int8Array: + return 1; + case Uint16Array: + case Int16Array: + return 2; + case Float32Array: + case Int32Array: + default: + return 4; + } + } + + /** + * @returns {number[]} + */ + getPixels() { + throw new Error(`"getPixels" called on ${ this.constructor.name }`); + } + + checkOutput() { + if (!this.output || !isArray(this.output)) throw new Error('kernel.output not an array'); + if (this.output.length < 1) throw new Error('kernel.output is empty, needs at least 1 value'); + for (let i = 0; i < this.output.length; i++) { + if (isNaN(this.output[i]) || this.output[i] < 1) { + throw new Error(`${ this.constructor.name }.output[${ i }] incorrectly defined as \`${ this.output[i] }\`, needs to be numeric, and greater than 0`); + } + } + } + + toJSON() { + const settings = { + output: this.output, + threadDim: this.threadDim, + pipeline: this.pipeline, + argumentNames: this.argumentNames, + argumentsTypes: this.argumentTypes, + constants: this.constants, + pluginNames: this.plugins ? this.plugins.map(plugin => plugin.name) : null, + returnType: this.returnType, + }; + return { + settings + }; + } +} diff --git a/src/backend/web-gl/fragment-shader.js b/src/backend/web-gl/fragment-shader.js index e281035a..5d8f0366 100644 --- a/src/backend/web-gl/fragment-shader.js +++ b/src/backend/web-gl/fragment-shader.js @@ -1,407 +1,403 @@ -// language=GLSL -const fragmentShader = `__HEADER__; -__FLOAT_TACTIC_DECLARATION__; -__INT_TACTIC_DECLARATION__; -__SAMPLER_2D_TACTIC_DECLARATION__; - -const int LOOP_MAX = __LOOP_MAX__; - -__PLUGINS__; -__CONSTANTS__; - -varying vec2 vTexCoord; - -vec4 round(vec4 x) { - return floor(x + 0.5); -} - -float round(float x) { - return floor(x + 0.5); -} - -const int BIT_COUNT = 32; -int modi(int x, int y) { - return x - y * (x / y); -} - -int bitwiseOr(int a, int b) { - int result = 0; - int n = 1; - - for (int i = 0; i < BIT_COUNT; i++) { - if ((modi(a, 2) == 1) || (modi(b, 2) == 1)) { - result += n; - } - a = a / 2; - b = b / 2; - n = n * 2; - if(!(a > 0 || b > 0)) { - break; - } - } - return result; -} -int bitwiseXOR(int a, int b) { - int result = 0; - int n = 1; - - for (int i = 0; i < BIT_COUNT; i++) { - if ((modi(a, 2) == 1) != (modi(b, 2) == 1)) { - result += n; - } - a = a / 2; - b = b / 2; - n = n * 2; - if(!(a > 0 || b > 0)) { - break; - } - } - return result; -} -int bitwiseAnd(int a, int b) { - int result = 0; - int n = 1; - for (int i = 0; i < BIT_COUNT; i++) { - if ((modi(a, 2) == 1) && (modi(b, 2) == 1)) { - result += n; - } - a = a / 2; - b = b / 2; - n = n * 2; - if(!(a > 0 && b > 0)) { - break; - } - } - return result; -} -int bitwiseNot(int a) { - int result = 0; - int n = 1; - - for (int i = 0; i < BIT_COUNT; i++) { - if (modi(a, 2) == 0) { - result += n; - } - a = a / 2; - n = n * 2; - } - return result; -} -int bitwiseZeroFillLeftShift(int n, int shift) { - int maxBytes = BIT_COUNT; - for (int i = 0; i < BIT_COUNT; i++) { - if (maxBytes >= n) { - break; - } - maxBytes *= 2; - } - for (int i = 0; i < BIT_COUNT; i++) { - if (i >= shift) { - break; - } - n *= 2; - } - - int result = 0; - int byteVal = 1; - for (int i = 0; i < BIT_COUNT; i++) { - if (i >= maxBytes) break; - if (modi(n, 2) > 0) { result += byteVal; } - n = int(n / 2); - byteVal *= 2; - } - return result; -} - -int bitwiseSignedRightShift(int num, int shifts) { - return int(floor(float(num) / pow(2.0, float(shifts)))); -} - -int bitwiseZeroFillRightShift(int n, int shift) { - int maxBytes = BIT_COUNT; - for (int i = 0; i < BIT_COUNT; i++) { - if (maxBytes >= n) { - break; - } - maxBytes *= 2; - } - for (int i = 0; i < BIT_COUNT; i++) { - if (i >= shift) { - break; - } - n /= 2; - } - int result = 0; - int byteVal = 1; - for (int i = 0; i < BIT_COUNT; i++) { - if (i >= maxBytes) break; - if (modi(n, 2) > 0) { result += byteVal; } - n = int(n / 2); - byteVal *= 2; - } - return result; -} - -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); -} - -float integerMod(float x, float y) { - float res = floor(mod(x, y)); - return res * (res > floor(y) - 1.0 ? 0.0 : 1.0); -} - -int integerMod(int x, int y) { - return x - (y * int(x / y)); -} - -__DIVIDE_WITH_INTEGER_CHECK__; - -// 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 -float decode32(vec4 texel) { - __DECODE32_ENDIANNESS__; - texel *= 255.0; - vec2 gte128; - gte128.x = texel.b >= 128.0 ? 1.0 : 0.0; - gte128.y = texel.a >= 128.0 ? 1.0 : 0.0; - float exponent = 2.0 * texel.a - 127.0 + dot(gte128, MAGIC_VEC); - float res = exp2(round(exponent)); - texel.b = texel.b - 128.0 * gte128.x; - res = dot(texel, SCALE_FACTOR) * exp2(round(exponent-23.0)) + res; - res *= gte128.y * -2.0 + 1.0; - return res; -} - -float decode16(vec4 texel, int index) { - int channel = integerMod(index, 2); - if (channel == 0) return texel.r * 255.0 + texel.g * 65280.0; - if (channel == 1) return texel.b * 255.0 + texel.a * 65280.0; - return 0.0; -} - -float decode8(vec4 texel, int index) { - int channel = integerMod(index, 4); - if (channel == 0) return texel.r * 255.0; - if (channel == 1) return texel.g * 255.0; - if (channel == 2) return texel.b * 255.0; - if (channel == 3) return texel.a * 255.0; - return 0.0; -} - -vec4 legacyEncode32(float f) { - float F = abs(f); - float sign = f < 0.0 ? 1.0 : 0.0; - float exponent = floor(log2(F)); - float mantissa = (exp2(-exponent) * F); - // exponent += floor(log2(mantissa)); - vec4 texel = vec4(F * exp2(23.0-exponent)) * SCALE_FACTOR_INV; - texel.rg = integerMod(texel.rg, 256.0); - texel.b = integerMod(texel.b, 128.0); - texel.a = exponent*0.5 + 63.5; - texel.ba += vec2(integerMod(exponent+127.0, 2.0), sign) * 128.0; - texel = floor(texel); - texel *= 0.003921569; // 1/255 - __ENCODE32_ENDIANNESS__; - return texel; -} - -// https://github.com/gpujs/gpu.js/wiki/Encoder-details -vec4 encode32(float value) { - if (value == 0.0) return vec4(0, 0, 0, 0); - - float exponent; - float mantissa; - vec4 result; - float sgn; - - sgn = step(0.0, -value); - value = abs(value); - - exponent = floor(log2(value)); - - mantissa = value*pow(2.0, -exponent)-1.0; - exponent = exponent+127.0; - result = vec4(0,0,0,0); - - result.a = floor(exponent/2.0); - exponent = exponent - result.a*2.0; - result.a = result.a + 128.0*sgn; - - result.b = floor(mantissa * 128.0); - mantissa = mantissa - result.b / 128.0; - result.b = result.b + exponent*128.0; - - result.g = floor(mantissa*32768.0); - mantissa = mantissa - result.g/32768.0; - - result.r = floor(mantissa*8388608.0); - return result/255.0; -} -// Dragons end here - -int index; -ivec3 threadId; - -ivec3 indexTo3D(int idx, ivec3 texDim) { - int z = int(idx / (texDim.x * texDim.y)); - idx -= z * int(texDim.x * texDim.y); - int y = int(idx / texDim.x); - int x = int(integerMod(idx, texDim.x)); - return ivec3(x, y, z); -} - -float get32(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { - int index = x + texDim.x * (y + texDim.y * z); - int w = texSize.x; - vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; - vec4 texel = texture2D(tex, st / vec2(texSize)); - return decode32(texel); -} - -float get16(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { - int index = x + texDim.x * (y + texDim.y * z); - int w = texSize.x * 2; - vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; - vec4 texel = texture2D(tex, st / vec2(texSize.x * 2, texSize.y)); - return decode16(texel, index); -} - -float get8(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { - int index = x + texDim.x * (y + texDim.y * z); - int w = texSize.x * 4; - vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; - vec4 texel = texture2D(tex, st / vec2(texSize.x * 4, texSize.y)); - return decode8(texel, index); -} - -float getMemoryOptimized32(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { - int index = x + texDim.x * (y + texDim.y * z); - int channel = integerMod(index, 4); - index = index / 4; - int w = texSize.x; - vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; - vec4 texel = texture2D(tex, st / vec2(texSize)); - if (channel == 0) return texel.r; - if (channel == 1) return texel.g; - if (channel == 2) return texel.b; - if (channel == 3) return texel.a; - return 0.0; -} - -vec4 getImage2D(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { - int index = x + texDim.x * (y + texDim.y * z); - int w = texSize.x; - vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; - return texture2D(tex, st / vec2(texSize)); -} - -float getFloatFromSampler2D(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { - vec4 result = getImage2D(tex, texSize, texDim, z, y, x); - return result[0]; -} - -vec2 getVec2FromSampler2D(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { - vec4 result = getImage2D(tex, texSize, texDim, z, y, x); - return vec2(result[0], result[1]); -} - -vec2 getMemoryOptimizedVec2(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { - int index = x + (texDim.x * (y + (texDim.y * z))); - int channel = integerMod(index, 2); - index = index / 2; - int w = texSize.x; - vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; - vec4 texel = texture2D(tex, st / vec2(texSize)); - if (channel == 0) return vec2(texel.r, texel.g); - if (channel == 1) return vec2(texel.b, texel.a); - return vec2(0.0, 0.0); -} - -vec3 getVec3FromSampler2D(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { - vec4 result = getImage2D(tex, texSize, texDim, z, y, x); - return vec3(result[0], result[1], result[2]); -} - -vec3 getMemoryOptimizedVec3(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { - int fieldIndex = 3 * (x + texDim.x * (y + texDim.y * z)); - int vectorIndex = fieldIndex / 4; - int vectorOffset = fieldIndex - vectorIndex * 4; - int readY = vectorIndex / texSize.x; - int readX = vectorIndex - readY * texSize.x; - vec4 tex1 = texture2D(tex, (vec2(readX, readY) + 0.5) / vec2(texSize)); - - if (vectorOffset == 0) { - return tex1.xyz; - } else if (vectorOffset == 1) { - return tex1.yzw; - } else { - readX++; - if (readX >= texSize.x) { - readX = 0; - readY++; - } - vec4 tex2 = texture2D(tex, vec2(readX, readY) / vec2(texSize)); - if (vectorOffset == 2) { - return vec3(tex1.z, tex1.w, tex2.x); - } else { - return vec3(tex1.w, tex2.x, tex2.y); - } - } -} - -vec4 getVec4FromSampler2D(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { - return getImage2D(tex, texSize, texDim, z, y, x); -} - -vec4 getMemoryOptimizedVec4(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { - int index = x + texDim.x * (y + texDim.y * z); - int channel = integerMod(index, 2); - int w = texSize.x; - vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; - vec4 texel = texture2D(tex, st / vec2(texSize)); - return vec4(texel.r, texel.g, texel.b, texel.a); -} - -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); -} - -void color(sampler2D image) { - actualColor = texture2D(image, vTexCoord); -} - -__INJECTED_NATIVE__; -__MAIN_CONSTANTS__; -__MAIN_ARGUMENTS__; -__KERNEL__; - -void main(void) { - index = int(vTexCoord.s * float(uTexSize.x)) + int(vTexCoord.t * float(uTexSize.y)) * uTexSize.x; - __MAIN_RESULT__; -}`; - -module.exports = { - fragmentShader -}; \ No newline at end of file +// language=GLSL +export const fragmentShader = `__HEADER__; +__FLOAT_TACTIC_DECLARATION__; +__INT_TACTIC_DECLARATION__; +__SAMPLER_2D_TACTIC_DECLARATION__; + +const int LOOP_MAX = __LOOP_MAX__; + +__PLUGINS__; +__CONSTANTS__; + +varying vec2 vTexCoord; + +vec4 round(vec4 x) { + return floor(x + 0.5); +} + +float round(float x) { + return floor(x + 0.5); +} + +const int BIT_COUNT = 32; +int modi(int x, int y) { + return x - y * (x / y); +} + +int bitwiseOr(int a, int b) { + int result = 0; + int n = 1; + + for (int i = 0; i < BIT_COUNT; i++) { + if ((modi(a, 2) == 1) || (modi(b, 2) == 1)) { + result += n; + } + a = a / 2; + b = b / 2; + n = n * 2; + if(!(a > 0 || b > 0)) { + break; + } + } + return result; +} +int bitwiseXOR(int a, int b) { + int result = 0; + int n = 1; + + for (int i = 0; i < BIT_COUNT; i++) { + if ((modi(a, 2) == 1) != (modi(b, 2) == 1)) { + result += n; + } + a = a / 2; + b = b / 2; + n = n * 2; + if(!(a > 0 || b > 0)) { + break; + } + } + return result; +} +int bitwiseAnd(int a, int b) { + int result = 0; + int n = 1; + for (int i = 0; i < BIT_COUNT; i++) { + if ((modi(a, 2) == 1) && (modi(b, 2) == 1)) { + result += n; + } + a = a / 2; + b = b / 2; + n = n * 2; + if(!(a > 0 && b > 0)) { + break; + } + } + return result; +} +int bitwiseNot(int a) { + int result = 0; + int n = 1; + + for (int i = 0; i < BIT_COUNT; i++) { + if (modi(a, 2) == 0) { + result += n; + } + a = a / 2; + n = n * 2; + } + return result; +} +int bitwiseZeroFillLeftShift(int n, int shift) { + int maxBytes = BIT_COUNT; + for (int i = 0; i < BIT_COUNT; i++) { + if (maxBytes >= n) { + break; + } + maxBytes *= 2; + } + for (int i = 0; i < BIT_COUNT; i++) { + if (i >= shift) { + break; + } + n *= 2; + } + + int result = 0; + int byteVal = 1; + for (int i = 0; i < BIT_COUNT; i++) { + if (i >= maxBytes) break; + if (modi(n, 2) > 0) { result += byteVal; } + n = int(n / 2); + byteVal *= 2; + } + return result; +} + +int bitwiseSignedRightShift(int num, int shifts) { + return int(floor(float(num) / pow(2.0, float(shifts)))); +} + +int bitwiseZeroFillRightShift(int n, int shift) { + int maxBytes = BIT_COUNT; + for (int i = 0; i < BIT_COUNT; i++) { + if (maxBytes >= n) { + break; + } + maxBytes *= 2; + } + for (int i = 0; i < BIT_COUNT; i++) { + if (i >= shift) { + break; + } + n /= 2; + } + int result = 0; + int byteVal = 1; + for (int i = 0; i < BIT_COUNT; i++) { + if (i >= maxBytes) break; + if (modi(n, 2) > 0) { result += byteVal; } + n = int(n / 2); + byteVal *= 2; + } + return result; +} + +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); +} + +float integerMod(float x, float y) { + float res = floor(mod(x, y)); + return res * (res > floor(y) - 1.0 ? 0.0 : 1.0); +} + +int integerMod(int x, int y) { + return x - (y * int(x / y)); +} + +__DIVIDE_WITH_INTEGER_CHECK__; + +// 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 +float decode32(vec4 texel) { + __DECODE32_ENDIANNESS__; + texel *= 255.0; + vec2 gte128; + gte128.x = texel.b >= 128.0 ? 1.0 : 0.0; + gte128.y = texel.a >= 128.0 ? 1.0 : 0.0; + float exponent = 2.0 * texel.a - 127.0 + dot(gte128, MAGIC_VEC); + float res = exp2(round(exponent)); + texel.b = texel.b - 128.0 * gte128.x; + res = dot(texel, SCALE_FACTOR) * exp2(round(exponent-23.0)) + res; + res *= gte128.y * -2.0 + 1.0; + return res; +} + +float decode16(vec4 texel, int index) { + int channel = integerMod(index, 2); + if (channel == 0) return texel.r * 255.0 + texel.g * 65280.0; + if (channel == 1) return texel.b * 255.0 + texel.a * 65280.0; + return 0.0; +} + +float decode8(vec4 texel, int index) { + int channel = integerMod(index, 4); + if (channel == 0) return texel.r * 255.0; + if (channel == 1) return texel.g * 255.0; + if (channel == 2) return texel.b * 255.0; + if (channel == 3) return texel.a * 255.0; + return 0.0; +} + +vec4 legacyEncode32(float f) { + float F = abs(f); + float sign = f < 0.0 ? 1.0 : 0.0; + float exponent = floor(log2(F)); + float mantissa = (exp2(-exponent) * F); + // exponent += floor(log2(mantissa)); + vec4 texel = vec4(F * exp2(23.0-exponent)) * SCALE_FACTOR_INV; + texel.rg = integerMod(texel.rg, 256.0); + texel.b = integerMod(texel.b, 128.0); + texel.a = exponent*0.5 + 63.5; + texel.ba += vec2(integerMod(exponent+127.0, 2.0), sign) * 128.0; + texel = floor(texel); + texel *= 0.003921569; // 1/255 + __ENCODE32_ENDIANNESS__; + return texel; +} + +// https://github.com/gpujs/gpu.js/wiki/Encoder-details +vec4 encode32(float value) { + if (value == 0.0) return vec4(0, 0, 0, 0); + + float exponent; + float mantissa; + vec4 result; + float sgn; + + sgn = step(0.0, -value); + value = abs(value); + + exponent = floor(log2(value)); + + mantissa = value*pow(2.0, -exponent)-1.0; + exponent = exponent+127.0; + result = vec4(0,0,0,0); + + result.a = floor(exponent/2.0); + exponent = exponent - result.a*2.0; + result.a = result.a + 128.0*sgn; + + result.b = floor(mantissa * 128.0); + mantissa = mantissa - result.b / 128.0; + result.b = result.b + exponent*128.0; + + result.g = floor(mantissa*32768.0); + mantissa = mantissa - result.g/32768.0; + + result.r = floor(mantissa*8388608.0); + return result/255.0; +} +// Dragons end here + +int index; +ivec3 threadId; + +ivec3 indexTo3D(int idx, ivec3 texDim) { + int z = int(idx / (texDim.x * texDim.y)); + idx -= z * int(texDim.x * texDim.y); + int y = int(idx / texDim.x); + int x = int(integerMod(idx, texDim.x)); + return ivec3(x, y, z); +} + +float get32(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { + int index = x + texDim.x * (y + texDim.y * z); + int w = texSize.x; + vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; + vec4 texel = texture2D(tex, st / vec2(texSize)); + return decode32(texel); +} + +float get16(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { + int index = x + texDim.x * (y + texDim.y * z); + int w = texSize.x * 2; + vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; + vec4 texel = texture2D(tex, st / vec2(texSize.x * 2, texSize.y)); + return decode16(texel, index); +} + +float get8(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { + int index = x + texDim.x * (y + texDim.y * z); + int w = texSize.x * 4; + vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; + vec4 texel = texture2D(tex, st / vec2(texSize.x * 4, texSize.y)); + return decode8(texel, index); +} + +float getMemoryOptimized32(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { + int index = x + texDim.x * (y + texDim.y * z); + int channel = integerMod(index, 4); + index = index / 4; + int w = texSize.x; + vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; + vec4 texel = texture2D(tex, st / vec2(texSize)); + if (channel == 0) return texel.r; + if (channel == 1) return texel.g; + if (channel == 2) return texel.b; + if (channel == 3) return texel.a; + return 0.0; +} + +vec4 getImage2D(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { + int index = x + texDim.x * (y + texDim.y * z); + int w = texSize.x; + vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; + return texture2D(tex, st / vec2(texSize)); +} + +float getFloatFromSampler2D(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { + vec4 result = getImage2D(tex, texSize, texDim, z, y, x); + return result[0]; +} + +vec2 getVec2FromSampler2D(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { + vec4 result = getImage2D(tex, texSize, texDim, z, y, x); + return vec2(result[0], result[1]); +} + +vec2 getMemoryOptimizedVec2(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { + int index = x + (texDim.x * (y + (texDim.y * z))); + int channel = integerMod(index, 2); + index = index / 2; + int w = texSize.x; + vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; + vec4 texel = texture2D(tex, st / vec2(texSize)); + if (channel == 0) return vec2(texel.r, texel.g); + if (channel == 1) return vec2(texel.b, texel.a); + return vec2(0.0, 0.0); +} + +vec3 getVec3FromSampler2D(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { + vec4 result = getImage2D(tex, texSize, texDim, z, y, x); + return vec3(result[0], result[1], result[2]); +} + +vec3 getMemoryOptimizedVec3(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { + int fieldIndex = 3 * (x + texDim.x * (y + texDim.y * z)); + int vectorIndex = fieldIndex / 4; + int vectorOffset = fieldIndex - vectorIndex * 4; + int readY = vectorIndex / texSize.x; + int readX = vectorIndex - readY * texSize.x; + vec4 tex1 = texture2D(tex, (vec2(readX, readY) + 0.5) / vec2(texSize)); + + if (vectorOffset == 0) { + return tex1.xyz; + } else if (vectorOffset == 1) { + return tex1.yzw; + } else { + readX++; + if (readX >= texSize.x) { + readX = 0; + readY++; + } + vec4 tex2 = texture2D(tex, vec2(readX, readY) / vec2(texSize)); + if (vectorOffset == 2) { + return vec3(tex1.z, tex1.w, tex2.x); + } else { + return vec3(tex1.w, tex2.x, tex2.y); + } + } +} + +vec4 getVec4FromSampler2D(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { + return getImage2D(tex, texSize, texDim, z, y, x); +} + +vec4 getMemoryOptimizedVec4(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { + int index = x + texDim.x * (y + texDim.y * z); + int channel = integerMod(index, 2); + int w = texSize.x; + vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; + vec4 texel = texture2D(tex, st / vec2(texSize)); + return vec4(texel.r, texel.g, texel.b, texel.a); +} + +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); +} + +void color(sampler2D image) { + actualColor = texture2D(image, vTexCoord); +} + +__INJECTED_NATIVE__; +__MAIN_CONSTANTS__; +__MAIN_ARGUMENTS__; +__KERNEL__; + +void main(void) { + index = int(vTexCoord.s * float(uTexSize.x)) + int(vTexCoord.t * float(uTexSize.y)) * uTexSize.x; + __MAIN_RESULT__; +}`; diff --git a/src/backend/web-gl/function-node.js b/src/backend/web-gl/function-node.js index bac93d1b..79e5b8a6 100644 --- a/src/backend/web-gl/function-node.js +++ b/src/backend/web-gl/function-node.js @@ -1,1537 +1,1530 @@ -const { utils } = require('../../utils'); -const { FunctionNode } = require('../function-node'); -// Closure capture for the ast function, prevent collision with existing AST functions -// The prefixes to use -const jsMathPrefix = 'Math.'; -const localPrefix = 'this.'; - -/** - * @desc [INTERNAL] Takes in a function node, and does all the AST voodoo required to toString its respective WebGL code - * @extends FunctionNode - * @returns the converted WebGL function string - */ -class WebGLFunctionNode extends FunctionNode { - constructor(source, settings) { - super(source, settings); - if (settings && settings.hasOwnProperty('fixIntegerDivisionAccuracy')) { - this.fixIntegerDivisionAccuracy = settings.fixIntegerDivisionAccuracy; - } - } - - /** - * @desc Parses the abstract syntax tree for to its *named function* - * @param {Object} ast - the AST object to parse - * @param {Array} retArr - return array string - * @returns {Array} the append retArr - */ - astFunction(ast, retArr) { - // Setup function return type and name - if (this.isRootKernel) { - retArr.push('void'); - } else { - // looking up return type, this is a little expensive, and can be avoided if returnType is set - let lastReturn = null; - if (!this.returnType) { - const lastReturn = this.findLastReturn(); - if (lastReturn) { - this.returnType = this.getType(ast.body); - if (this.returnType === 'LiteralInteger') { - this.returnType = 'Number'; - } - } - } - - const { returnType } = this; - if (!returnType) { - retArr.push('void'); - } else { - const type = typeMap[returnType]; - if (!type) { - throw new Error(`unknown type ${returnType}`); - } - retArr.push(type); - } - } - retArr.push(' '); - retArr.push(this.name); - retArr.push('('); - - if (!this.isRootKernel) { - // Arguments handling - for (let i = 0; i < this.argumentNames.length; ++i) { - const argumentName = this.argumentNames[i]; - - if (i > 0) { - retArr.push(', '); - } - let argumentType = this.argumentTypes[this.argumentNames.indexOf(argumentName)]; - // The type is too loose ended, here we descide to solidify a type, lets go with float - if (!argumentType) { - throw this.astErrorOutput(`Unknown argument ${argumentName} type`, ast); - } - if (argumentType === 'LiteralInteger') { - this.argumentTypes[i] = argumentType = 'Number'; - } - const type = typeMap[argumentType]; - if (!type) { - throw this.astErrorOutput('Unexpected expression', ast); - } - - if (type === 'sampler2D' || type === 'sampler2DArray') { - // mash needed arguments together, since now we have end to end inference - retArr.push(`${type} user_${argumentName},ivec2 user_${argumentName}Size,ivec3 user_${argumentName}Dim`); - } else { - retArr.push(`${type} user_${argumentName}`); - } - } - } - - // Function opening - retArr.push(') {\n'); - - // Body statement iteration - for (let i = 0; i < ast.body.body.length; ++i) { - this.astGeneric(ast.body.body[i], retArr); - retArr.push('\n'); - } - - // Function closing - retArr.push('}\n'); - return retArr; - } - - /** - * @desc Parses the abstract syntax tree for to *return* statement - * @param {Object} ast - the AST object to parse - * @param {Array} retArr - return array string - * @returns {Array} the append retArr - */ - astReturnStatement(ast, retArr) { - if (!ast.argument) throw this.astErrorOutput('Unexpected return statement', ast); - this.pushState('skip-literal-correction'); - const type = this.getType(ast.argument); - this.popState('skip-literal-correction'); - - const result = []; - - if (!this.returnType) { - if (type === 'LiteralInteger' || type === 'Integer') { - this.returnType = 'Number'; - } else { - this.returnType = type; - } - } - - switch (this.returnType) { - case 'LiteralInteger': - case 'Number': - case 'Float': - switch (type) { - case 'Integer': - result.push('float('); - this.astGeneric(ast.argument, result); - result.push(')'); - break; - case 'LiteralInteger': - this.castLiteralToFloat(ast.argument, result); - - // Running astGeneric forces the LiteralInteger to pick a type, and here, if we are returning a float, yet - // the LiteralInteger has picked to be an integer because of constraints on it we cast it to float. - if (this.getType(ast) === 'Integer') { - result.unshift('float('); - result.push(')'); - } - break; - default: - this.astGeneric(ast.argument, result); - } - break; - case 'Integer': - switch (type) { - case 'Float': - case 'Number': - this.castValueToInteger(ast.argument, result); - break; - case 'LiteralInteger': - this.castLiteralToInteger(ast.argument, result); - break; - default: - this.astGeneric(ast.argument, result); - } - break; - case 'Array(4)': - case 'Array(3)': - case 'Array(2)': - case 'Input': - this.astGeneric(ast.argument, result); - break; - default: - throw this.astErrorOutput(`unhandled return type ${this.returnType}`, ast); - } - - if (this.isRootKernel) { - retArr.push(`kernelResult = ${ result.join('') };`); - retArr.push('return;'); - } else if (this.isSubKernel) { - retArr.push(`subKernelResult_${ this.name } = ${ result.join('') };`); - retArr.push(`return subKernelResult_${ this.name };`); - } else { - retArr.push(`return ${ result.join('') };`); - } - return retArr; - } - - /** - * @desc Parses the abstract syntax tree for *literal value* - * - * @param {Object} ast - the AST object to parse - * @param {Array} retArr - return array string - * - * @returns {Array} the append retArr - */ - astLiteral(ast, retArr) { - // Reject non numeric literals - if (isNaN(ast.value)) { - throw this.astErrorOutput( - 'Non-numeric literal not supported : ' + ast.value, - ast - ); - } - - const key = `${ast.start},${ast.end}`; - if (Number.isInteger(ast.value)) { - if (this.isState('in-for-loop-init') || this.isState('casting-to-integer') || this.isState('building-integer')) { - this.literalTypes[key] = 'Integer'; - retArr.push(`${ast.value}`); - } else if (this.isState('casting-to-float') || this.isState('building-float')) { - this.literalTypes[key] = 'Number'; - retArr.push(`${ast.value}.0`); - } else { - this.literalTypes[key] = 'Number'; - retArr.push(`${ast.value}.0`); - } - } else if (this.isState('casting-to-integer') || this.isState('building-integer')) { - this.literalTypes[key] = 'Integer'; - retArr.push(Math.round(ast.value)); - } else { - this.literalTypes[key] = 'Number'; - retArr.push(`${ast.value}`); - } - return retArr; - } - - /** - * @desc Parses the abstract syntax tree for *binary* expression - * @param {Object} ast - the AST object to parse - * @param {Array} retArr - return array string - * @returns {Array} the append retArr - */ - astBinaryExpression(ast, retArr) { - if (this.checkAndUpconvertOperator(ast, retArr)) { - return retArr; - } - - if (this.fixIntegerDivisionAccuracy && ast.operator === '/') { - retArr.push('div_with_int_check('); - this.pushState('building-float'); - switch (this.getType(ast.left)) { - case 'Integer': - this.castValueToFloat(ast.left, retArr); - break; - case 'LiteralInteger': - this.castLiteralToFloat(ast.left, retArr); - break; - default: - this.astGeneric(ast.left, retArr); - } - retArr.push(', '); - switch (this.getType(ast.right)) { - case 'Integer': - this.castValueToFloat(ast.right, retArr); - break; - case 'LiteralInteger': - this.castLiteralToFloat(ast.right, retArr); - break; - default: - this.astGeneric(ast.right, retArr); - } - this.popState('building-float'); - retArr.push(')'); - return retArr; - } - - retArr.push('('); - const leftType = this.getType(ast.left) || 'Number'; - const rightType = this.getType(ast.right) || 'Number'; - if (!leftType || !rightType) { - throw this.astErrorOutput(`Unhandled binary expression`, ast); - } - const key = leftType + ' & ' + rightType; - switch (key) { - case 'Integer & Integer': - this.pushState('building-integer'); - this.astGeneric(ast.left, retArr); - retArr.push(operatorMap[ast.operator] || ast.operator); - this.astGeneric(ast.right, retArr); - this.popState('building-integer'); - break; - case 'Number & Float': - case 'Float & Number': - case 'Float & Float': - case 'Number & Number': - this.pushState('building-float'); - this.astGeneric(ast.left, retArr); - retArr.push(operatorMap[ast.operator] || ast.operator); - this.astGeneric(ast.right, retArr); - this.popState('building-float'); - break; - case 'LiteralInteger & LiteralInteger': - if (this.isState('casting-to-integer') || this.isState('building-integer')) { - this.pushState('building-integer'); - this.astGeneric(ast.left, retArr); - retArr.push(operatorMap[ast.operator] || ast.operator); - this.astGeneric(ast.right, retArr); - this.popState('building-integer'); - } else { - this.pushState('building-float'); - this.castLiteralToFloat(ast.left, retArr); - retArr.push(operatorMap[ast.operator] || ast.operator); - this.castLiteralToFloat(ast.right, retArr); - this.popState('building-float'); - } - break; - - case 'Integer & Float': - case 'Integer & Number': - if (ast.operator === '>' || ast.operator === '<' && ast.right.type === 'Literal') { - // if right value is actually a float, don't loose that information, cast left to right rather than the usual right to left - if (!Number.isInteger(ast.right.value)) { - this.pushState('building-float'); - this.castValueToFloat(ast.left, retArr); - retArr.push(operatorMap[ast.operator] || ast.operator); - this.astGeneric(ast.right, retArr); - this.popState('building-float'); - break; - } - } - this.pushState('building-integer'); - this.astGeneric(ast.left, retArr); - retArr.push(operatorMap[ast.operator] || ast.operator); - this.pushState('casting-to-integer'); - if (ast.right.type === 'Literal') { - const literalResult = []; - this.astGeneric(ast.right, literalResult); - const literalType = this.getType(ast.right); - if (literalType === 'Integer') { - retArr.push(literalResult.join('')); - } else { - throw this.astErrorOutput(`Unhandled binary expression with literal`, ast); - } - } else { - retArr.push('int('); - this.astGeneric(ast.right, retArr); - retArr.push(')'); - } - this.popState('casting-to-integer'); - this.popState('building-integer'); - break; - case 'Integer & LiteralInteger': - this.pushState('building-integer'); - this.astGeneric(ast.left, retArr); - retArr.push(operatorMap[ast.operator] || ast.operator); - this.castLiteralToInteger(ast.right, retArr); - this.popState('building-integer'); - break; - - case 'Number & Integer': - this.pushState('building-float'); - this.astGeneric(ast.left, retArr); - retArr.push(operatorMap[ast.operator] || ast.operator); - this.castValueToFloat(ast.right, retArr); - this.popState('building-float'); - break; - case 'Float & LiteralInteger': - case 'Number & LiteralInteger': - if (this.isState('in-for-loop-test')) { - this.pushState('building-integer'); - retArr.push('int('); - this.astGeneric(ast.left, retArr); - retArr.push(')'); - retArr.push(operatorMap[ast.operator] || ast.operator); - this.castLiteralToInteger(ast.right, retArr); - this.popState('building-integer'); - } else { - this.pushState('building-float'); - this.astGeneric(ast.left, retArr); - retArr.push(operatorMap[ast.operator] || ast.operator); - this.castLiteralToFloat(ast.right, retArr); - this.popState('building-float'); - } - break; - case 'LiteralInteger & Float': - case 'LiteralInteger & Number': - if (this.isState('in-for-loop-test') || this.isState('in-for-loop-init') || this.isState('casting-to-integer')) { - this.pushState('building-integer'); - this.castLiteralToInteger(ast.left, retArr); - retArr.push(operatorMap[ast.operator] || ast.operator); - this.castValueToInteger(ast.right, retArr); - this.popState('building-integer'); - } else { - this.pushState('building-float'); - this.astGeneric(ast.left, retArr); - retArr.push(operatorMap[ast.operator] || ast.operator); - this.pushState('casting-to-float'); - this.astGeneric(ast.right, retArr); - this.popState('casting-to-float'); - this.popState('building-float'); - } - break; - case 'LiteralInteger & Integer': - this.pushState('building-integer'); - this.castLiteralToInteger(ast.left, retArr); - retArr.push(operatorMap[ast.operator] || ast.operator); - this.astGeneric(ast.right, retArr); - this.popState('building-integer'); - break; - - case 'Boolean & Boolean': - this.pushState('building-boolean'); - this.astGeneric(ast.left, retArr); - retArr.push(operatorMap[ast.operator] || ast.operator); - this.astGeneric(ast.right, retArr); - this.popState('building-boolean'); - break; - - case 'Float & Integer': - this.pushState('building-float'); - this.astGeneric(ast.left, retArr); - retArr.push(operatorMap[ast.operator] || ast.operator); - this.castValueToFloat(ast.right, retArr); - this.popState('building-float'); - break; - - default: - throw this.astErrorOutput(`Unhandled binary expression between ${key}`, ast); - } - retArr.push(')'); - - return retArr; - } - - checkAndUpconvertOperator(ast, retArr) { - const bitwiseResult = this.checkAndUpconvertBitwiseOperators(ast, retArr); - if (bitwiseResult) { - return bitwiseResult; - } - const upconvertableOperators = { - '%': 'mod', - '**': 'pow', - }; - const foundOperator = upconvertableOperators[ast.operator]; - if (!foundOperator) return null; - retArr.push(foundOperator); - retArr.push('('); - switch (this.getType(ast.left)) { - case 'Integer': - this.castValueToFloat(ast.left, retArr); - break; - case 'LiteralInteger': - this.castLiteralToFloat(ast.left, retArr); - break; - default: - this.astGeneric(ast.left, retArr); - } - retArr.push(','); - switch (this.getType(ast.right)) { - case 'Integer': - this.castValueToFloat(ast.right, retArr); - break; - case 'LiteralInteger': - this.castLiteralToFloat(ast.right, retArr); - break; - default: - this.astGeneric(ast.right, retArr); - } - retArr.push(')'); - return retArr; - } - - checkAndUpconvertBitwiseOperators(ast, retArr) { - const upconvertableOperators = { - '&': 'bitwiseAnd', - '|': 'bitwiseOr', - '^': 'bitwiseXOR', - '<<': 'bitwiseZeroFillLeftShift', - '>>': 'bitwiseSignedRightShift', - '>>>': 'bitwiseZeroFillRightShift', - }; - const foundOperator = upconvertableOperators[ast.operator]; - if (!foundOperator) return null; - retArr.push(foundOperator); - retArr.push('('); - const leftType = this.getType(ast.left); - switch (leftType) { - case 'Number': - case 'Float': - this.castValueToInteger(ast.left, retArr); - break; - case 'LiteralInteger': - this.castLiteralToInteger(ast.left, retArr); - break; - default: - this.astGeneric(ast.left, retArr); - } - retArr.push(','); - const rightType = this.getType(ast.right); - switch (rightType) { - case 'Number': - case 'Float': - this.castValueToInteger(ast.right, retArr); - break; - case 'LiteralInteger': - this.castLiteralToInteger(ast.right, retArr); - break; - default: - this.astGeneric(ast.right, retArr); - } - retArr.push(')'); - return retArr; - } - - checkAndUpconvertBitwiseUnary(ast, retArr) { - const upconvertableOperators = { - '~': 'bitwiseNot', - }; - const foundOperator = upconvertableOperators[ast.operator]; - if (!foundOperator) return null; - retArr.push(foundOperator); - retArr.push('('); - switch (this.getType(ast.argument)) { - case 'Number': - case 'Float': - this.castValueToInteger(ast.argument, retArr); - break; - case 'LiteralInteger': - this.castLiteralToInteger(ast.argument, retArr); - break; - default: - this.astGeneric(ast.argument, retArr); - } - retArr.push(')'); - return retArr; - } - - /** - * - * @param {Object} ast - * @param {Array} retArr - * @return {String[]} - */ - castLiteralToInteger(ast, retArr) { - this.pushState('casting-to-integer'); - this.astGeneric(ast, retArr); - this.popState('casting-to-integer'); - return retArr; - } - - /** - * - * @param {Object} ast - * @param {Array} retArr - * @return {String[]} - */ - castLiteralToFloat(ast, retArr) { - this.pushState('casting-to-float'); - this.astGeneric(ast, retArr); - this.popState('casting-to-float'); - return retArr; - } - - /** - * - * @param {Object} ast - * @param {Array} retArr - * @return {String[]} - */ - castValueToInteger(ast, retArr) { - this.pushState('casting-to-integer'); - retArr.push('int('); - this.astGeneric(ast, retArr); - retArr.push(')'); - this.popState('casting-to-integer'); - return retArr; - } - - /** - * - * @param {Object} ast - * @param {Array} retArr - * @return {String[]} - */ - castValueToFloat(ast, retArr) { - this.pushState('casting-to-float'); - retArr.push('float('); - this.astGeneric(ast, retArr); - retArr.push(')'); - this.popState('casting-to-float'); - return retArr; - } - - /** - * @desc Parses the abstract syntax tree for *identifier* expression - * @param {Object} idtNode - An ast Node - * @param {Array} retArr - return array string - * @returns {Array} the append retArr - */ - astIdentifierExpression(idtNode, retArr) { - if (idtNode.type !== 'Identifier') { - throw this.astErrorOutput('IdentifierExpression - not an Identifier', idtNode); - } - - const type = this.getType(idtNode); - - if (idtNode.name === 'Infinity') { - // https://stackoverflow.com/a/47543127/1324039 - retArr.push('3.402823466e+38'); - } else if (type === 'Boolean') { - if (this.argumentNames.indexOf(idtNode.name) > -1) { - retArr.push(`bool(user_${idtNode.name})`); - } else { - retArr.push(`user_${idtNode.name}`); - } - } else { - retArr.push(`user_${idtNode.name}`); - } - - return retArr; - } - - /** - * @desc Parses the abstract syntax tree for *for-loop* expression - * @param {Object} forNode - An ast Node - * @param {Array} retArr - return array string - * @returns {Array} the parsed webgl string - */ - 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); - const { declarations } = forNode.init; - for (let i = 0; i < declarations.length; i++) { - if (declarations[i].init && declarations[i].init.type !== 'Literal') { - isSafe = false; - } - } - if (isSafe) { - 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.pushState('in-for-loop-test'); - this.astGeneric(forNode.test, testArr); - this.popState('in-for-loop-test'); - } 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'); - } - - // have all parts, now make them safe - 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 (int ${iVariableName}=0;${iVariableName}This builds the shaders and runs them on the GPU, - * the outputs the result back as float(enabled by default) and Texture.
- * - * @prop {Object} textureCache - webGl Texture cache - * @prop {Object} programUniformLocationCache - Location of program variables in memory - * @prop {Object} framebuffer - Webgl frameBuffer - * @prop {Object} buffer - WebGL buffer - * @prop {Object} program - The webGl Program - * @prop {Object} functionBuilder - Function Builder instance bound to this Kernel - * @prop {Boolean} pipeline - Set output type to FAST mode (GPU to GPU via Textures), instead of float - * @prop {String} endianness - Endian information like Little-endian, Big-endian. - * @prop {Array} argumentTypes - Types of parameters sent to the Kernel - * @prop {String} compiledFragmentShader - Compiled fragment shader string - * @prop {String} compiledVertexShader - Compiled Vertical shader string - * @extends GLKernel - */ -class WebGLKernel extends GLKernel { - static get isSupported() { - if (isSupported !== null) { - return isSupported; - } - this.setupFeatureChecks(); - isSupported = this.isContextMatch(testContext); - return isSupported; - } - - static setupFeatureChecks() { - if (typeof document !== 'undefined') { - testCanvas = document.createElement('canvas'); - } else if (typeof OffscreenCanvas !== 'undefined') { - testCanvas = new OffscreenCanvas(0, 0); - } - if (!testCanvas) return; - testContext = testCanvas.getContext('webgl') || testCanvas.getContext('experimental-webgl'); - if (!testContext || !testContext.getExtension) return; - testExtensions = { - OES_texture_float: testContext.getExtension('OES_texture_float'), - OES_texture_float_linear: testContext.getExtension('OES_texture_float_linear'), - OES_element_index_uint: testContext.getExtension('OES_element_index_uint'), - WEBGL_draw_buffers: testContext.getExtension('WEBGL_draw_buffers'), - }; - features = this.getFeatures(); - } - - static isContextMatch(context) { - if (typeof WebGLRenderingContext !== 'undefined') { - return context instanceof WebGLRenderingContext; - } - return false; - } - - static getFeatures() { - const isDrawBuffers = this.getIsDrawBuffers(); - return Object.freeze({ - isFloatRead: this.getIsFloatRead(), - isIntegerDivisionAccurate: this.getIsIntegerDivisionAccurate(), - isTextureFloat: this.getIsTextureFloat(), - isDrawBuffers, - kernelMap: isDrawBuffers, - channelCount: this.getChannelCount(), - maxTextureSize: this.getMaxTextureSize(), - }); - } - - static getIsTextureFloat() { - return Boolean(testExtensions.OES_texture_float); - } - - static getIsDrawBuffers() { - return Boolean(testExtensions.WEBGL_draw_buffers); - } - - static getChannelCount() { - return testExtensions.WEBGL_draw_buffers ? - testContext.getParameter(testExtensions.WEBGL_draw_buffers.MAX_DRAW_BUFFERS_WEBGL) : - 1; - } - - static getMaxTextureSize() { - return testContext.getParameter(testContext.MAX_TEXTURE_SIZE); - } - - static lookupKernelValueType(type, dynamic, precision, value) { - return lookupKernelValueType(type, dynamic, precision, value); - } - - static get testCanvas() { - return testCanvas; - } - - static get testContext() { - return testContext; - } - - static get features() { - return features; - } - - static get fragmentShader() { - return fragmentShader; - } - - static get vertexShader() { - return vertexShader; - } - - /** - * - * @param {String} source - * @param {IKernelSettings} settings - */ - constructor(source, settings) { - super(source, settings); - this.program = null; - this.pipeline = settings.pipeline; - this.endianness = utils.systemEndianness(); - this.extensions = {}; - this.subKernelOutputTextures = null; - this.kernelArguments = null; - this.argumentTextureCount = 0; - this.constantTextureCount = 0; - this.compiledFragmentShader = null; - this.compiledVertexShader = null; - this.fragShader = null; - this.vertShader = null; - this.drawBuffersMap = null; - this.outputTexture = null; - - /** - * - * @type {Int32Array|null} - */ - this.maxTexSize = null; - this.switchingKernels = false; - this.onRequestSwitchKernel = null; - - this.mergeSettings(source.settings || settings); - - /** - * The thread dimensions, x, y and z - * @type {Array|null} - */ - this.threadDim = null; - this.framebuffer = null; - this.buffer = null; - this.textureCache = {}; - this.programUniformLocationCache = {}; - this.uniform1fCache = {}; - this.uniform1iCache = {}; - this.uniform2fCache = {}; - this.uniform2fvCache = {}; - this.uniform2ivCache = {}; - this.uniform3fvCache = {}; - this.uniform3ivCache = {}; - this.uniform4fvCache = {}; - this.uniform4ivCache = {}; - } - - initCanvas() { - if (typeof document !== 'undefined') { - const canvas = document.createElement('canvas'); - // Default width and height, to fix webgl issue in safari - canvas.width = 2; - canvas.height = 2; - return canvas; - } else if (typeof OffscreenCanvas !== 'undefined') { - return new OffscreenCanvas(0, 0); - } - } - - initContext() { - const settings = { - alpha: false, - depth: false, - antialias: false - }; - return this.canvas.getContext('webgl', settings) || this.canvas.getContext('experimental-webgl', settings); - } - - initPlugins(settings) { - // default plugins - const pluginsToUse = []; - const { source } = this; - if (typeof source === 'string') { - for (let i = 0; i < plugins.length; i++) { - const plugin = plugins[i]; - if (source.match(plugin.functionMatch)) { - pluginsToUse.push(plugin); - } - } - } else if (typeof source === 'object') { - // `source` is from object, json - if (settings.pluginNames) { //TODO: in context of JSON support, pluginNames may not exist here - for (let i = 0; i < plugins.length; i++) { - const plugin = plugins[i]; - const usePlugin = settings.pluginNames.some(pluginName => pluginName === plugin.name); - if (usePlugin) { - pluginsToUse.push(plugin); - } - } - } - } - return pluginsToUse; - } - - initExtensions() { - this.extensions = { - OES_texture_float: this.context.getExtension('OES_texture_float'), - OES_texture_float_linear: this.context.getExtension('OES_texture_float_linear'), - OES_element_index_uint: this.context.getExtension('OES_element_index_uint'), - WEBGL_draw_buffers: this.context.getExtension('WEBGL_draw_buffers'), - WEBGL_color_buffer_float: this.context.getExtension('WEBGL_color_buffer_float'), - }; - } - - /** - * @desc Validate settings related to Kernel, such as dimensions size, and auto output support. - * @param {IArguments} args - */ - validateSettings(args) { - if (!this.validate) { - this.texSize = utils.getKernelTextureSize({ - optimizeFloatMemory: this.optimizeFloatMemory, - precision: this.precision, - }, this.output); - return; - } - - const { features } = this.constructor; - - if (this.optimizeFloatMemory === true && !features.isTextureFloat) { - throw new Error('Float textures are not supported'); - } else if (this.precision === 'single' && !features.isFloatRead) { - throw new Error('Single precision not supported'); - } else if (!this.graphical && this.precision === null && features.isTextureFloat) { - this.precision = features.isFloatRead ? 'single' : 'unsigned'; - } - - if (this.subKernels && this.subKernels.length > 0 && !this.extensions.WEBGL_draw_buffers) { - throw new Error('could not instantiate draw buffers extension'); - } - - if (this.fixIntegerDivisionAccuracy === null) { - this.fixIntegerDivisionAccuracy = !features.isIntegerDivisionAccurate; - } else if (this.fixIntegerDivisionAccuracy && features.isIntegerDivisionAccurate) { - this.fixIntegerDivisionAccuracy = false; - } - - this.checkOutput(); - - 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'); - } - - if (this.precision === 'precision') { - this.precision = 'unsigned'; - console.warn('Cannot use graphical mode and single precision at the same time'); - } - - this.texSize = utils.clone(this.output); - return; - } else if (this.precision === null && features.isTextureFloat) { - this.precision = 'single'; - } - - this.texSize = utils.getKernelTextureSize({ - optimizeFloatMemory: this.optimizeFloatMemory, - precision: this.precision, - }, this.output); - - this.checkTextureSize(); - } - - updateMaxTexSize() { - const { texSize, canvas } = this; - if (this.maxTexSize === null) { - let canvasIndex = canvases.indexOf(canvas); - if (canvasIndex === -1) { - canvasIndex = canvases.length; - canvases.push(canvas); - maxTexSizes[canvasIndex] = [texSize[0], texSize[1]]; - } - this.maxTexSize = maxTexSizes[canvasIndex]; - } - if (this.maxTexSize[0] < texSize[0]) { - this.maxTexSize[0] = texSize[0]; - } - if (this.maxTexSize[1] < texSize[1]) { - this.maxTexSize[1] = texSize[1]; - } - } - - // TODO: move channel checks to new place - _oldtranslateSource() { - const functionBuilder = FunctionBuilder.fromKernel(this, WebGLFunctionNode, { - fixIntegerDivisionAccuracy: this.fixIntegerDivisionAccuracy - }); - - // need this line to automatically get returnType - const translatedSource = functionBuilder.getPrototypeString('kernel'); - - if (!this.returnType) { - this.returnType = functionBuilder.getKernelResultType(); - } - - let requiredChannels = 0; - const returnTypes = functionBuilder.getReturnTypes(); - for (let i = 0; i < returnTypes.length; i++) { - switch (returnTypes[i]) { - case 'Float': - case 'Number': - case 'Integer': - requiredChannels++; - break; - case 'Array(2)': - requiredChannels += 2; - break; - case 'Array(3)': - requiredChannels += 3; - break; - case 'Array(4)': - requiredChannels += 4; - break; - } - } - - if (features && requiredChannels > features.channelCount) { - throw new Error('Too many channels!'); - } - - return this.translatedSource = translatedSource; - } - - setupArguments(args) { - this.kernelArguments = []; - this.argumentTextureCount = 0; - const needsArgumentTypes = this.argumentTypes === null; - // TODO: remove - if (needsArgumentTypes) { - this.argumentTypes = []; - } - this.argumentSizes = []; - this.argumentBitRatios = []; - // TODO: end remove - - if (args.length < this.argumentNames.length) { - throw new Error('not enough arguments for kernel'); - } else if (args.length > this.argumentNames.length) { - throw new Error('too many arguments for kernel'); - } - - const { context: gl } = this; - let textureIndexes = 0; - for (let index = 0; index < args.length; index++) { - const value = args[index]; - const name = this.argumentNames[index]; - let type; - if (needsArgumentTypes) { - type = utils.getVariableType(value, this.strictIntegers); - this.argumentTypes.push(type); - } else { - type = this.argumentTypes[index]; - } - const KernelValue = this.constructor.lookupKernelValueType(type, this.dynamicArguments ? 'dynamic' : 'static', this.precision, args[index]); - if (KernelValue === null) { - return this.requestFallback(args); - } - const kernelArgument = new KernelValue(value, { - name, - type, - tactic: this.tactic, - origin: 'user', - context: gl, - checkContext: this.checkContext, - kernel: this, - strictIntegers: this.strictIntegers, - onRequestTexture: () => { - return this.context.createTexture(); - }, - onRequestIndex: () => { - return textureIndexes++; - }, - onUpdateValueMismatch: () => { - this.switchingKernels = true; - }, - onRequestContextHandle: () => { - return gl.TEXTURE0 + this.constantTextureCount + this.argumentTextureCount++; - } - }); - this.kernelArguments.push(kernelArgument); - this.argumentSizes.push(kernelArgument.textureSize); - this.argumentBitRatios[index] = kernelArgument.bitRatio; - } - } - - setupConstants(args) { - const { context: gl } = this; - this.kernelConstants = []; - this.forceUploadKernelConstants = []; - let needsConstantTypes = this.constantTypes === null; - if (needsConstantTypes) { - this.constantTypes = {}; - } - this.constantBitRatios = {}; - let textureIndexes = 0; - for (const name in this.constants) { - const value = this.constants[name]; - let type; - if (needsConstantTypes) { - type = utils.getVariableType(value, this.strictIntegers); - this.constantTypes[name] = type; - } else { - type = this.constantTypes[name]; - } - const KernelValue = this.constructor.lookupKernelValueType(type, 'static', this.precision, value); - if (KernelValue === null) { - return this.requestFallback(args); - } - const kernelValue = new KernelValue(value, { - name, - type, - tactic: this.tactic, - origin: 'constants', - context: this.context, - checkContext: this.checkContext, - kernel: this, - strictIntegers: this.strictIntegers, - onRequestTexture: () => { - return this.context.createTexture(); - }, - onRequestIndex: () => { - return textureIndexes++; - }, - onRequestContextHandle: () => { - return gl.TEXTURE0 + this.constantTextureCount++; - } - }); - this.constantBitRatios[name] = kernelValue.bitRatio; - this.kernelConstants.push(kernelValue); - if (kernelValue.forceUploadEachRun) { - this.forceUploadKernelConstants.push(kernelValue); - } - } - } - - build() { - this.initExtensions(); - this.validateSettings(arguments); - this.setupConstants(arguments); - if (this.fallbackRequested) return; - this.setupArguments(arguments); - if (this.fallbackRequested) return; - this.updateMaxTexSize(); - this.translateSource(); - const failureResult = this.pickRenderStrategy(arguments); - if (failureResult) { - return failureResult; - } - const { texSize, context: gl, canvas } = this; - gl.enable(gl.SCISSOR_TEST); - if (this.pipeline && this.precision === 'single') { - gl.viewport(0, 0, this.maxTexSize[0], this.maxTexSize[1]); - canvas.width = this.maxTexSize[0]; - canvas.height = this.maxTexSize[1]; - } else { - gl.viewport(0, 0, this.maxTexSize[0], this.maxTexSize[1]); - canvas.width = this.maxTexSize[0]; - canvas.height = this.maxTexSize[1]; - } - const threadDim = this.threadDim = Array.from(this.output); - while (threadDim.length < 3) { - threadDim.push(1); - } - - const compiledVertexShader = this.getVertexShader(arguments); - const vertShader = gl.createShader(gl.VERTEX_SHADER); - gl.shaderSource(vertShader, compiledVertexShader); - gl.compileShader(vertShader); - this.vertShader = vertShader; - - const compiledFragmentShader = this.getFragmentShader(arguments); - const fragShader = gl.createShader(gl.FRAGMENT_SHADER); - gl.shaderSource(fragShader, compiledFragmentShader); - gl.compileShader(fragShader); - this.fragShader = fragShader; - - if (this.debug) { - console.log('GLSL Shader Output:'); - console.log(compiledFragmentShader); - } - - if (!gl.getShaderParameter(vertShader, gl.COMPILE_STATUS)) { - throw new Error('Error compiling vertex shader: ' + gl.getShaderInfoLog(vertShader)); - } - if (!gl.getShaderParameter(fragShader, gl.COMPILE_STATUS)) { - throw new Error('Error compiling fragment shader: ' + gl.getShaderInfoLog(fragShader)); - } - - const program = this.program = gl.createProgram(); - gl.attachShader(program, vertShader); - gl.attachShader(program, fragShader); - gl.linkProgram(program); - this.framebuffer = gl.createFramebuffer(); - this.framebuffer.width = texSize[0]; - this.framebuffer.height = texSize[1]; - - const vertices = new Float32Array([-1, -1, - 1, -1, -1, 1, - 1, 1 - ]); - const texCoords = new Float32Array([ - 0, 0, - 1, 0, - 0, 1, - 1, 1 - ]); - - const texCoordOffset = vertices.byteLength; - - let buffer = this.buffer; - if (!buffer) { - buffer = this.buffer = gl.createBuffer(); - 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); - - const aPosLoc = gl.getAttribLocation(this.program, 'aPos'); - gl.enableVertexAttribArray(aPosLoc); - gl.vertexAttribPointer(aPosLoc, 2, gl.FLOAT, false, 0, 0); - const aTexCoordLoc = gl.getAttribLocation(this.program, 'aTexCoord'); - gl.enableVertexAttribArray(aTexCoordLoc); - gl.vertexAttribPointer(aTexCoordLoc, 2, gl.FLOAT, false, 0, texCoordOffset); - gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); - - let i = 0; - gl.useProgram(this.program); - for (let p in this.constants) { - this.kernelConstants[i++].updateValue(this.constants[p]); - } - - if (!this.immutable) { - this._setupOutputTexture(); - if ( - this.subKernels !== null && - this.subKernels.length > 0 - ) { - this._setupSubOutputTextures(); - } - } - } - - translateSource() { - const functionBuilder = FunctionBuilder.fromKernel(this, WebGLFunctionNode, { - fixIntegerDivisionAccuracy: this.fixIntegerDivisionAccuracy - }); - this.translatedSource = functionBuilder.getPrototypeString('kernel'); - if (!this.graphical && !this.returnType) { - this.returnType = functionBuilder.getKernelResultType(); - } - - if (this.subKernels && this.subKernels.length > 0) { - for (let i = 0; i < this.subKernels.length; i++) { - const subKernel = this.subKernels[i]; - if (!subKernel.returnType) { - subKernel.returnType = functionBuilder.getSubKernelResultType(i); - } - } - } - } - - run() { - const { kernelArguments, forceUploadKernelConstants } = this; - const texSize = this.texSize; - const gl = this.context; - - gl.useProgram(this.program); - gl.scissor(0, 0, texSize[0], texSize[1]); - - if (this.dynamicOutput) { - this.setUniform3iv('uOutputDim', new Int32Array(this.threadDim)); - this.setUniform2iv('uTexSize', texSize); - } - - this.setUniform2f('ratio', texSize[0] / this.maxTexSize[0], texSize[1] / this.maxTexSize[1]); - - this.switchingKernels = false; - for (let i = 0; i < forceUploadKernelConstants.length; i++) { - const constant = forceUploadKernelConstants[i]; - constant.updateValue(this.constants[constant.name]); - if (this.switchingKernels) return; - } - for (let i = 0; i < kernelArguments.length; i++) { - kernelArguments[i].updateValue(arguments[i]); - if (this.switchingKernels) return; - } - - if (this.plugins) { - for (let i = 0; i < this.plugins.length; i++) { - const plugin = this.plugins[i]; - if (plugin.onBeforeRun) { - plugin.onBeforeRun(this); - } - } - } - - if (this.graphical) { - if (this.pipeline) { - gl.bindRenderbuffer(gl.RENDERBUFFER, null); - gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); - if (!this.outputTexture || this.immutable) { - this._setupOutputTexture(); - } - gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); - return new this.TextureConstructor({ - texture: this.outputTexture, - size: texSize, - dimensions: this.threadDim, - output: this.output, - context: this.context, - }); - } - gl.bindRenderbuffer(gl.RENDERBUFFER, null); - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); - return; - } - - gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); - if (this.immutable) { - this._setupOutputTexture(); - } - - if (this.subKernels !== null) { - if (this.immutable) { - this._setupSubOutputTextures(); - } - this.extensions.WEBGL_draw_buffers.drawBuffersWEBGL(this.drawBuffersMap); - } - - gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); - } - - /** - * @desc This return defined outputTexture, which is setup in .build(), or if immutable, is defined in .run() - * @returns {Object} Output Texture Cache - */ - getOutputTexture() { - return this.outputTexture; - } - - /** - * @desc Setup and replace output texture - */ - _setupOutputTexture() { - const gl = this.context; - const texSize = this.texSize; - const texture = this.outputTexture = this.context.createTexture(); - gl.activeTexture(gl.TEXTURE0 + this.constantTextureCount + this.argumentTextureCount); - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - // if (this.precision === 'single') { - // gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize[0], texSize[1], 0, gl.RGBA, gl.FLOAT, null); - // } else { - // gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize[0], texSize[1], 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - // } - if (this.precision === 'single') { - if (this.pipeline) { - // TODO: investigate if webgl1 can handle gl.RED usage in gl.texImage2D, otherwise, simplify the below - switch (this.returnType) { - case 'Number': - case 'Float': - case 'Integer': - if (this.optimizeFloatMemory) { - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize[0], texSize[1], 0, gl.RGBA, gl.FLOAT, null); - } else { - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize[0], texSize[1], 0, gl.RGBA, gl.FLOAT, null); - } - break; - case 'Array(2)': - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize[0], texSize[1], 0, gl.RGBA, gl.FLOAT, null); - break; - case 'Array(3)': - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize[0], texSize[1], 0, gl.RGBA, gl.FLOAT, null); - break; - case 'Array(4)': - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize[0], texSize[1], 0, gl.RGBA, gl.FLOAT, null); - break; - default: - if (!this.graphical) { - throw new Error('Unhandled return type'); - } - } - } else { - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize[0], texSize[1], 0, gl.RGBA, gl.FLOAT, null); - } - } else { - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize[0], texSize[1], 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); - } - - /** - * @desc Setup and replace sub-output textures - */ - _setupSubOutputTextures() { - const gl = this.context; - const texSize = this.texSize; - this.drawBuffersMap = [gl.COLOR_ATTACHMENT0]; - this.subKernelOutputTextures = []; - for (let i = 0; i < this.subKernels.length; i++) { - const texture = this.context.createTexture(); - this.subKernelOutputTextures.push(texture); - this.drawBuffersMap.push(gl.COLOR_ATTACHMENT0 + i + 1); - gl.activeTexture(gl.TEXTURE0 + this.constantTextureCount + this.argumentTextureCount + i); - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - if (this.precision === 'single') { - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize[0], texSize[1], 0, gl.RGBA, gl.FLOAT, null); - } else { - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize[0], texSize[1], 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i + 1, gl.TEXTURE_2D, texture, 0); - } - } - - /** - * @desc Returns the Texture Cache of the supplied parameter (can be kernel, sub-kernel or argument) - * @param {String} name - Name of the subkernel, argument, or kernel. - * @returns {Object} Texture cache - */ - getTextureCache(name) { - if (this.textureCache.hasOwnProperty(name)) { - return this.textureCache[name]; - } - return this.textureCache[name] = this.context.createTexture(); - } - - /** - * @desc removes a texture from the kernel's cache - * @param {String} name - Name of texture - */ - detachTextureCache(name) { - delete this.textureCache[name]; - } - - setUniform1f(name, value) { - if (this.uniform1fCache.hasOwnProperty(name)) { - const cache = this.uniform1fCache[name]; - if (value === cache) { - return; - } - } - this.uniform1fCache[name] = value; - const loc = this.getUniformLocation(name); - this.context.uniform1f(loc, value); - } - - setUniform1i(name, value) { - if (this.uniform1iCache.hasOwnProperty(name)) { - const cache = this.uniform1iCache[name]; - if (value === cache) { - return; - } - } - this.uniform1iCache[name] = value; - const loc = this.getUniformLocation(name); - this.context.uniform1i(loc, value); - } - - setUniform2f(name, value1, value2) { - if (this.uniform2fCache.hasOwnProperty(name)) { - const cache = this.uniform2fCache[name]; - if ( - value1 === cache[0] && - value2 === cache[1] - ) { - return; - } - } - this.uniform2fCache[name] = [value1, value2]; - const loc = this.getUniformLocation(name); - this.context.uniform2f(loc, value1, value2); - } - - setUniform2fv(name, value) { - if (this.uniform2fvCache.hasOwnProperty(name)) { - const cache = this.uniform2fvCache[name]; - if ( - value[0] === cache[0] && - value[1] === cache[1] - ) { - return; - } - } - this.uniform2fvCache[name] = value; - const loc = this.getUniformLocation(name); - this.context.uniform2fv(loc, value); - } - - setUniform2iv(name, value) { - if (this.uniform2ivCache.hasOwnProperty(name)) { - const cache = this.uniform2ivCache[name]; - if ( - value[0] === cache[0] && - value[1] === cache[1] - ) { - return; - } - } - this.uniform2ivCache[name] = value; - const loc = this.getUniformLocation(name); - this.context.uniform2iv(loc, value); - } - - setUniform3fv(name, value) { - if (this.uniform3fvCache.hasOwnProperty(name)) { - const cache = this.uniform3fvCache[name]; - if ( - value[0] === cache[0] && - value[1] === cache[1] && - value[2] === cache[2] - ) { - return; - } - } - this.uniform3fvCache[name] = value; - const loc = this.getUniformLocation(name); - this.context.uniform3fv(loc, value); - } - - setUniform3iv(name, value) { - if (this.uniform3ivCache.hasOwnProperty(name)) { - const cache = this.uniform3ivCache[name]; - if ( - value[0] === cache[0] && - value[1] === cache[1] && - value[2] === cache[2] - ) { - return; - } - } - this.uniform3ivCache[name] = value; - const loc = this.getUniformLocation(name); - this.context.uniform3iv(loc, value); - } - - setUniform3fv(name, value) { - if (this.uniform3fvCache.hasOwnProperty(name)) { - const cache = this.uniform3fvCache[name]; - if ( - value[0] === cache[0] && - value[1] === cache[1] && - value[2] === cache[2] - ) { - return; - } - } - this.uniform3fvCache[name] = value; - const loc = this.getUniformLocation(name); - this.context.uniform3fv(loc, value); - } - - setUniform4iv(name, value) { - if (this.uniform4ivCache.hasOwnProperty(name)) { - const cache = this.uniform4ivCache[name]; - if ( - value[0] === cache[0] && - value[1] === cache[1] && - value[2] === cache[2] && - value[3] === cache[3] - ) { - return; - } - } - this.uniform4ivCache[name] = value; - const loc = this.getUniformLocation(name); - this.context.uniform4iv(loc, value); - } - - setUniform4fv(name, value) { - if (this.uniform4fvCache.hasOwnProperty(name)) { - const cache = this.uniform4fvCache[name]; - if ( - value[0] === cache[0] && - value[1] === cache[1] && - value[2] === cache[2] && - value[3] === cache[3] - ) { - return; - } - } - this.uniform4fvCache[name] = value; - const loc = this.getUniformLocation(name); - this.context.uniform4fv(loc, value); - } - - /** - * @desc Return WebGlUniformLocation for various variables - * related to webGl program, such as user-defined variables, - * as well as, dimension sizes, etc. - */ - getUniformLocation(name) { - if (this.programUniformLocationCache.hasOwnProperty(name)) { - return this.programUniformLocationCache[name]; - } - return this.programUniformLocationCache[name] = this.context.getUniformLocation(this.program, name); - } - - /** - * @desc Generate Shader artifacts for the kernel program. - * The final object contains HEADER, KERNEL, MAIN_RESULT, and others. - * - * @param {Array} args - The actual parameters sent to the Kernel - * @returns {Object} An object containing the Shader Artifacts(CONSTANTS, HEADER, KERNEL, etc.) - */ - _getFragShaderArtifactMap(args) { - return { - HEADER: this._getHeaderString(), - LOOP_MAX: this._getLoopMaxString(), - PLUGINS: this._getPluginsString(), - CONSTANTS: this._getConstantsString(), - DECODE32_ENDIANNESS: this._getDecode32EndiannessString(), - ENCODE32_ENDIANNESS: this._getEncode32EndiannessString(), - DIVIDE_WITH_INTEGER_CHECK: this._getDivideWithIntegerCheckString(), - INJECTED_NATIVE: this._getInjectedNative(), - MAIN_CONSTANTS: this._getMainConstantsString(), - MAIN_ARGUMENTS: this._getMainArgumentsString(args), - KERNEL: this.getKernelString(), - MAIN_RESULT: this.getMainResultString(), - FLOAT_TACTIC_DECLARATION: this.getFloatTacticDeclaration(), - INT_TACTIC_DECLARATION: this.getIntTacticDeclaration(), - SAMPLER_2D_TACTIC_DECLARATION: this.getSampler2DTacticDeclaration(), - SAMPLER_2D_ARRAY_TACTIC_DECLARATION: this.getSampler2DArrayTacticDeclaration(), - }; - } - - /** - * @desc Generate Shader artifacts for the kernel program. - * The final object contains HEADER, KERNEL, MAIN_RESULT, and others. - * - * @param {Array} args - The actual parameters sent to the Kernel - * @returns {Object} An object containing the Shader Artifacts(CONSTANTS, HEADER, KERNEL, etc.) - */ - _getVertShaderArtifactMap(args) { - return { - FLOAT_TACTIC_DECLARATION: this.getFloatTacticDeclaration(), - INT_TACTIC_DECLARATION: this.getIntTacticDeclaration(), - SAMPLER_2D_TACTIC_DECLARATION: this.getSampler2DTacticDeclaration(), - SAMPLER_2D_ARRAY_TACTIC_DECLARATION: this.getSampler2DArrayTacticDeclaration(), - }; - } - - /** - * @desc Get the header string for the program. - * This returns an empty string if no sub-kernels are defined. - * - * @returns {String} result - */ - _getHeaderString() { - return ( - this.subKernels !== null ? - '#extension GL_EXT_draw_buffers : require\n' : - '' - ); - } - - /** - * @desc Get the maximum loop size String. - * @returns {String} result - */ - _getLoopMaxString() { - return ( - this.loopMaxIterations ? - ` ${parseInt(this.loopMaxIterations)};\n` : - ' 1000;\n' - ); - } - - _getPluginsString() { - if (!this.plugins) return '\n'; - return this.plugins.map(plugin => plugin.source && this.source.match(plugin.functionMatch) ? plugin.source : '').join('\n'); - } - - /** - * @desc Generate transpiled glsl Strings for constant parameters sent to a kernel - * @returns {String} result - */ - _getConstantsString() { - const result = []; - const { threadDim, texSize } = this; - if (this.dynamicOutput) { - result.push( - 'uniform ivec3 uOutputDim', - 'uniform ivec2 uTexSize' - ); - } else { - result.push( - `ivec3 uOutputDim = ivec3(${threadDim[0]}, ${threadDim[1]}, ${threadDim[2]})`, - `ivec2 uTexSize = ivec2(${texSize[0]}, ${texSize[1]})` - ); - } - return utils.linesToString(result); - } - - /** - * @desc Get texture coordinate string for the program - * @returns {String} result - */ - _getTextureCoordinate() { - const subKernels = this.subKernels; - if (subKernels === null || subKernels.length < 1) { - return 'varying vec2 vTexCoord;\n'; - } else { - return 'out vec2 vTexCoord;\n'; - } - } - - /** - * @desc Get Decode32 endianness string for little-endian and big-endian - * @returns {String} result - */ - _getDecode32EndiannessString() { - return ( - this.endianness === 'LE' ? - '' : - ' texel.rgba = texel.abgr;\n' - ); - } - - /** - * @desc Get Encode32 endianness string for little-endian and big-endian - * @returns {String} result - */ - _getEncode32EndiannessString() { - return ( - this.endianness === 'LE' ? - '' : - ' texel.rgba = texel.abgr;\n' - ); - } - - /** - * @desc if fixIntegerDivisionAccuracy provide method to replace / - * @returns {String} result - */ - _getDivideWithIntegerCheckString() { - return this.fixIntegerDivisionAccuracy ? - `float div_with_int_check(float x, float y) { - if (floor(x) == x && floor(y) == y && integerMod(x, y) == 0.0) { - return float(int(x)/int(y)); - } - return x / y; -}` : - ''; - } - - /** - * @desc Generate transpiled glsl Strings for user-defined parameters sent to a kernel - * @param {Array} args - The actual parameters sent to the Kernel - * @returns {String} result - */ - _getMainArgumentsString(args) { - const results = []; - const { argumentNames } = this; - for (let i = 0; i < argumentNames.length; i++) { - results.push(this.kernelArguments[i].getSource(args[i])); - } - return results.join(''); - } - - _getInjectedNative() { - return this.injectedNative || ''; - } - - _getMainConstantsString() { - const result = []; - const { constants } = this; - if (constants) { - let i = 0; - for (const name in constants) { - result.push(this.kernelConstants[i++].getSource(this.constants[name])); - } - } - return result.join(''); - } - - /** - * @desc Get Kernel program string (in *glsl*) for a kernel. - * @returns {String} result - */ - getKernelString() { - let kernelResultDeclaration; - switch (this.returnType) { - case 'Array(2)': - kernelResultDeclaration = 'vec2 kernelResult'; - break; - case 'Array(3)': - kernelResultDeclaration = 'vec3 kernelResult'; - break; - case 'Array(4)': - kernelResultDeclaration = 'vec4 kernelResult'; - break; - case 'LiteralInteger': - case 'Float': - case 'Number': - case 'Integer': - kernelResultDeclaration = 'float kernelResult'; - break; - default: - if (this.graphical) { - kernelResultDeclaration = 'float kernelResult'; - } else { - throw new Error(`unrecognized output type "${ this.returnType }"`); - } - } - - const result = []; - const subKernels = this.subKernels; - if (subKernels !== null) { - result.push( - kernelResultDeclaration - ); - switch (this.returnType) { - case 'Number': - case 'Float': - case 'Integer': - for (let i = 0; i < subKernels.length; i++) { - const subKernel = subKernels[i]; - result.push( - subKernel.returnType === 'Integer' ? - `int subKernelResult_${ subKernel.name } = 0` : - `float subKernelResult_${ subKernel.name } = 0.0` - ); - } - break; - case 'Array(2)': - for (let i = 0; i < subKernels.length; i++) { - result.push( - `vec2 subKernelResult_${ subKernels[i].name }` - ); - } - break; - case 'Array(3)': - for (let i = 0; i < subKernels.length; i++) { - result.push( - `vec3 subKernelResult_${ subKernels[i].name }` - ); - } - break; - case 'Array(4)': - for (let i = 0; i < subKernels.length; i++) { - result.push( - `vec4 subKernelResult_${ subKernels[i].name }` - ); - } - break; - } - } else { - result.push( - kernelResultDeclaration - ); - } - - return utils.linesToString(result) + this.translatedSource; - } - - getMainResultGraphical() { - return utils.linesToString([ - ' threadId = indexTo3D(index, uOutputDim)', - ' kernel()', - ' gl_FragColor = actualColor', - ]); - } - - getMainResultPackedPixels() { - switch (this.returnType) { - case 'LiteralInteger': - case 'Number': - case 'Integer': - case 'Float': - return this.getMainResultKernelPackedPixels() + - this.getMainResultSubKernelPackedPixels(); - default: - throw new Error(`packed output only usable with Numbers, "${this.returnType}" specified`); - } - } - - /** - * @return {String} - */ - getMainResultKernelPackedPixels() { - return utils.linesToString([ - ' threadId = indexTo3D(index, uOutputDim)', - ' kernel()', - ` gl_FragData[0] = ${this.useLegacyEncoder ? 'legacyEncode32' : 'encode32'}(kernelResult)` - ]); - } - - /** - * @return {String} - */ - getMainResultSubKernelPackedPixels() { - const result = []; - if (!this.subKernels) return ''; - for (let i = 0; i < this.subKernels.length; i++) { - const subKernel = this.subKernels[i]; - if (subKernel.returnType === 'Integer') { - result.push( - ` gl_FragData[${i + 1}] = ${this.useLegacyEncoder ? 'legacyEncode32' : 'encode32'}(float(subKernelResult_${this.subKernels[i].name}))` - ); - } else { - result.push( - ` gl_FragData[${i + 1}] = ${this.useLegacyEncoder ? 'legacyEncode32' : 'encode32'}(subKernelResult_${this.subKernels[i].name})` - ); - } - } - return utils.linesToString(result); - } - - getMainResultMemoryOptimizedFloats() { - const result = [ - ' index *= 4', - ]; - - switch (this.returnType) { - case 'Number': - case 'Integer': - case 'Float': - const channels = ['r', 'g', 'b', 'a']; - for (let i = 0; i < channels.length; i++) { - const channel = channels[i]; - this.getMainResultKernelMemoryOptimizedFloats(result, channel); - this.getMainResultSubKernelMemoryOptimizedFloats(result, channel); - if (i + 1 < channels.length) { - result.push(' index += 1'); - } - } - break; - default: - throw new Error(`optimized output only usable with Numbers, ${this.returnType} specified`); - } - - return utils.linesToString(result); - } - - getMainResultKernelMemoryOptimizedFloats(result, channel) { - result.push( - ' threadId = indexTo3D(index, uOutputDim)', - ' kernel()', - ` gl_FragData[0].${channel} = kernelResult`, - ); - } - - getMainResultSubKernelMemoryOptimizedFloats(result, channel) { - if (!this.subKernels) return result; - for (let i = 0; i < this.subKernels.length; i++) { - const subKernel = this.subKernels[i]; - if (subKernel.returnType === 'Integer') { - result.push( - ` gl_FragData[${i + 1}].${channel} = float(subKernelResult_${this.subKernels[i].name})`, - ); - } else { - result.push( - ` gl_FragData[${i + 1}].${channel} = subKernelResult_${this.subKernels[i].name}`, - ); - } - } - } - - getMainResultKernelNumberTexture() { - return [ - ' threadId = indexTo3D(index, uOutputDim)', - ' kernel()', - ' gl_FragData[0][0] = kernelResult', - ]; - } - - getMainResultSubKernelNumberTexture() { - const result = []; - if (!this.subKernels) return result; - for (let i = 0; i < this.subKernels.length; ++i) { - const subKernel = this.subKernels[i]; - if (subKernel.returnType === 'Integer') { - result.push( - ` gl_FragData[${i + 1}][0] = float(subKernelResult_${subKernel.name})`, - ); - } else { - result.push( - ` gl_FragData[${i + 1}][0] = subKernelResult_${subKernel.name}`, - ); - } - } - return result; - } - - getMainResultKernelArray2Texture() { - return [ - ' threadId = indexTo3D(index, uOutputDim)', - ' kernel()', - ' gl_FragData[0][0] = kernelResult[0]', - ' gl_FragData[0][1] = kernelResult[1]', - ]; - } - - getMainResultSubKernelArray2Texture() { - const result = []; - if (!this.subKernels) return result; - for (let i = 0; i < this.subKernels.length; ++i) { - result.push( - ` gl_FragData[${i + 1}][0] = subKernelResult_${this.subKernels[i].name}[0]`, - ` gl_FragData[${i + 1}][1] = subKernelResult_${this.subKernels[i].name}[1]`, - ); - } - return result; - } - - getMainResultKernelArray3Texture() { - return [ - ' threadId = indexTo3D(index, uOutputDim)', - ' kernel()', - ' gl_FragData[0][0] = kernelResult[0]', - ' gl_FragData[0][1] = kernelResult[1]', - ' gl_FragData[0][2] = kernelResult[2]', - ]; - } - - getMainResultSubKernelArray3Texture() { - const result = []; - if (!this.subKernels) return result; - for (let i = 0; i < this.subKernels.length; ++i) { - result.push( - ` gl_FragData[${i + 1}][0] = subKernelResult_${this.subKernels[i].name}[0]`, - ` gl_FragData[${i + 1}][1] = subKernelResult_${this.subKernels[i].name}[1]`, - ` gl_FragData[${i + 1}][2] = subKernelResult_${this.subKernels[i].name}[2]`, - ); - } - return result; - } - - getMainResultKernelArray4Texture() { - return [ - ' threadId = indexTo3D(index, uOutputDim)', - ' kernel()', - ' gl_FragData[0] = kernelResult', - ]; - } - - getMainResultSubKernelArray4Texture() { - const result = []; - if (!this.subKernels) return result; - switch (this.returnType) { - case 'Number': - case 'Float': - case 'Integer': - for (let i = 0; i < this.subKernels.length; ++i) { - const subKernel = this.subKernels[i]; - if (subKernel.returnType === 'Integer') { - result.push( - ` gl_FragData[${i + 1}] = float(subKernelResult_${this.subKernels[i].name})`, - ); - } else { - result.push( - ` gl_FragData[${i + 1}] = subKernelResult_${this.subKernels[i].name}`, - ); - } - } - break; - case 'Array(2)': - for (let i = 0; i < this.subKernels.length; ++i) { - result.push( - ` gl_FragData[${i + 1}][0] = subKernelResult_${this.subKernels[i].name}[0]`, - ` gl_FragData[${i + 1}][1] = subKernelResult_${this.subKernels[i].name}[1]`, - ); - } - break; - case 'Array(3)': - for (let i = 0; i < this.subKernels.length; ++i) { - result.push( - ` gl_FragData[${i + 1}][0] = subKernelResult_${this.subKernels[i].name}[0]`, - ` gl_FragData[${i + 1}][1] = subKernelResult_${this.subKernels[i].name}[1]`, - ` gl_FragData[${i + 1}][2] = subKernelResult_${this.subKernels[i].name}[2]`, - ); - } - break; - case 'Array(4)': - for (let i = 0; i < this.subKernels.length; ++i) { - result.push( - ` gl_FragData[${i + 1}][0] = subKernelResult_${this.subKernels[i].name}[0]`, - ` gl_FragData[${i + 1}][1] = subKernelResult_${this.subKernels[i].name}[1]`, - ` gl_FragData[${i + 1}][2] = subKernelResult_${this.subKernels[i].name}[2]`, - ` gl_FragData[${i + 1}][3] = subKernelResult_${this.subKernels[i].name}[3]`, - ); - } - break; - } - - return result; - } - - /** - * @param {String} src - Shader string - * @param {Object} map - Variables/Constants associated with shader - */ - replaceArtifacts(src, map) { - return src.replace(/[ ]*__([A-Z]+[0-9]*([_]?[A-Z]*[0-9]?)*)__;\n/g, (match, artifact) => { - if (map.hasOwnProperty(artifact)) { - return map[artifact]; - } - throw `unhandled artifact ${artifact}`; - }); - } - - /** - * @desc Get the fragment shader String. - * If the String hasn't been compiled yet, - * then this method compiles it as well - * - * @param {Array} args - The actual parameters sent to the Kernel - * @returns {string} Fragment Shader string - */ - getFragmentShader(args) { - if (this.compiledFragmentShader !== null) { - return this.compiledFragmentShader; - } - return this.compiledFragmentShader = this.replaceArtifacts(this.constructor.fragmentShader, this._getFragShaderArtifactMap(args)); - } - - /** - * @desc Get the vertical shader String - * @param {Array|IArguments} args - The actual parameters sent to the Kernel - * @returns {string} Vertical Shader string - */ - getVertexShader(args) { - if (this.compiledVertexShader !== null) { - return this.compiledVertexShader; - } - return this.compiledVertexShader = this.replaceArtifacts(this.constructor.vertexShader, this._getVertShaderArtifactMap(args)); - } - - /** - * @desc Returns the *pre-compiled* Kernel as a JS Object String, that can be reused. - */ - toString() { - const setupContextString = utils.linesToString([ - `const gl = context`, - ]); - return glKernelString(this.constructor, arguments, this, setupContextString); - } - - destroy(removeCanvasReferences) { - if (this.outputTexture) { - this.context.deleteTexture(this.outputTexture); - } - if (this.buffer) { - this.context.deleteBuffer(this.buffer); - } - if (this.framebuffer) { - this.context.deleteFramebuffer(this.framebuffer); - } - if (this.vertShader) { - this.context.deleteShader(this.vertShader); - } - if (this.fragShader) { - this.context.deleteShader(this.fragShader); - } - if (this.program) { - this.context.deleteProgram(this.program); - } - - const keys = Object.keys(this.textureCache); - - for (let i = 0; i < keys.length; i++) { - const name = keys[i]; - this.context.deleteTexture(this.textureCache[name]); - } - - if (this.subKernelOutputTextures) { - for (let i = 0; i < this.subKernelOutputTextures.length; i++) { - this.context.deleteTexture(this.subKernelOutputTextures[i]); - } - } - if (removeCanvasReferences) { - const idx = canvases.indexOf(this.canvas); - if (idx >= 0) { - canvases[idx] = null; - maxTexSizes[idx] = null; - } - } - this.destroyExtensions(); - delete this.context; - delete this.canvas; - } - - destroyExtensions() { - this.extensions.OES_texture_float = null; - this.extensions.OES_texture_float_linear = null; - this.extensions.OES_element_index_uint = null; - this.extensions.WEBGL_draw_buffers = null; - } - - static destroyContext(context) { - const extension = context.getExtension('WEBGL_lose_context'); - if (extension) { - extension.loseContext(); - } - } - - toJSON() { - const json = super.toJSON(); - json.functionNodes = FunctionBuilder.fromKernel(this, WebGLFunctionNode).toJSON(); - return json; - } -} - -module.exports = { - WebGLKernel -}; \ No newline at end of file +import { GLKernel } from '../gl/kernel'; +import { FunctionBuilder } from '../function-builder'; +import { WebGLFunctionNode } from './function-node'; +import { utils } from '../../utils'; +import triangleNoise from '../../plugins/triangle-noise'; +import { fragmentShader } from './fragment-shader'; +import { vertexShader } from './vertex-shader'; +import { glKernelString } from '../gl/kernel-string'; +import { lookupKernelValueType } from './kernel-value-maps'; + +let isSupported = null; +let testCanvas = null; +let testContext = null; +let testExtensions = null; +let features = null; + +const plugins = [triangleNoise]; +const canvases = []; +const maxTexSizes = {}; + +/** + * @desc Kernel Implementation for WebGL. + * + * This builds the shaders and runs them on the GPU, then outputs the result + * back as float (enabled by default) and Texture. + * + * @prop {Object} textureCache - webGl Texture cache + * @prop {Object} programUniformLocationCache - Location of program variables in memory + * @prop {Object} framebuffer - Webgl frameBuffer + * @prop {Object} buffer - WebGL buffer + * @prop {Object} program - The webGl Program + * @prop {Object} functionBuilder - Function Builder instance bound to this Kernel + * @prop {Boolean} pipeline - Set output type to FAST mode (GPU to GPU via Textures), instead of float + * @prop {String} endianness - Endian information like Little-endian, Big-endian. + * @prop {Array} argumentTypes - Types of parameters sent to the Kernel + * @prop {String} compiledFragmentShader - Compiled fragment shader string + * @prop {String} compiledVertexShader - Compiled Vertical shader string + * @extends GLKernel + */ +export class WebGLKernel extends GLKernel { + static get isSupported() { + if (isSupported !== null) { + return isSupported; + } + this.setupFeatureChecks(); + isSupported = this.isContextMatch(testContext); + return isSupported; + } + + static setupFeatureChecks() { + if (typeof document !== 'undefined') { + testCanvas = document.createElement('canvas'); + } else if (typeof OffscreenCanvas !== 'undefined') { + testCanvas = new OffscreenCanvas(0, 0); + } + if (!testCanvas) return; + testContext = testCanvas.getContext('webgl') || testCanvas.getContext('experimental-webgl'); + if (!testContext || !testContext.getExtension) return; + testExtensions = { + OES_texture_float: testContext.getExtension('OES_texture_float'), + OES_texture_float_linear: testContext.getExtension('OES_texture_float_linear'), + OES_element_index_uint: testContext.getExtension('OES_element_index_uint'), + WEBGL_draw_buffers: testContext.getExtension('WEBGL_draw_buffers'), + }; + features = this.getFeatures(); + } + + static isContextMatch(context) { + if (typeof WebGLRenderingContext !== 'undefined') { + return context instanceof WebGLRenderingContext; + } + return false; + } + + static getFeatures() { + const isDrawBuffers = this.getIsDrawBuffers(); + return Object.freeze({ + isFloatRead: this.getIsFloatRead(), + isIntegerDivisionAccurate: this.getIsIntegerDivisionAccurate(), + isTextureFloat: this.getIsTextureFloat(), + isDrawBuffers, + kernelMap: isDrawBuffers, + channelCount: this.getChannelCount(), + }); + } + + static getIsTextureFloat() { + return Boolean(testExtensions.OES_texture_float); + } + + static getIsDrawBuffers() { + return Boolean(testExtensions.WEBGL_draw_buffers); + } + + static getChannelCount() { + return testExtensions.WEBGL_draw_buffers ? + testContext.getParameter(testExtensions.WEBGL_draw_buffers.MAX_DRAW_BUFFERS_WEBGL) : + 1; + } + + static lookupKernelValueType(type, dynamic, precision, value) { + return lookupKernelValueType(type, dynamic, precision, value); + } + + static get testCanvas() { + return testCanvas; + } + + static get testContext() { + return testContext; + } + + static get features() { + return features; + } + + static get fragmentShader() { + return fragmentShader; + } + + static get vertexShader() { + return vertexShader; + } + + /** + * + * @param {String} source + * @param {IKernelSettings} settings + */ + constructor(source, settings) { + super(source, settings); + this.program = null; + this.pipeline = settings.pipeline; + this.endianness = utils.systemEndianness(); + this.extensions = {}; + this.subKernelOutputTextures = null; + this.kernelArguments = null; + this.argumentTextureCount = 0; + this.constantTextureCount = 0; + this.compiledFragmentShader = null; + this.compiledVertexShader = null; + this.fragShader = null; + this.vertShader = null; + this.drawBuffersMap = null; + this.outputTexture = null; + + /** + * + * @type {Int32Array|null} + */ + this.maxTexSize = null; + this.switchingKernels = false; + this.onRequestSwitchKernel = null; + + this.mergeSettings(source.settings || settings); + + /** + * The thread dimensions, x, y and z + * @type {Array|null} + */ + this.threadDim = null; + this.framebuffer = null; + this.buffer = null; + this.textureCache = {}; + this.programUniformLocationCache = {}; + this.uniform1fCache = {}; + this.uniform1iCache = {}; + this.uniform2fCache = {}; + this.uniform2fvCache = {}; + this.uniform2ivCache = {}; + this.uniform3fvCache = {}; + this.uniform3ivCache = {}; + this.uniform4fvCache = {}; + this.uniform4ivCache = {}; + } + + initCanvas() { + if (typeof document !== 'undefined') { + const canvas = document.createElement('canvas'); + // Default width and height, to fix webgl issue in safari + canvas.width = 2; + canvas.height = 2; + return canvas; + } else if (typeof OffscreenCanvas !== 'undefined') { + return new OffscreenCanvas(0, 0); + } + } + + initContext() { + const settings = { + alpha: false, + depth: false, + antialias: false + }; + return this.canvas.getContext('webgl', settings) || this.canvas.getContext('experimental-webgl', settings); + } + + initPlugins(settings) { + // default plugins + const pluginsToUse = []; + const { source } = this; + if (typeof source === 'string') { + for (let i = 0; i < plugins.length; i++) { + const plugin = plugins[i]; + if (source.match(plugin.functionMatch)) { + pluginsToUse.push(plugin); + } + } + } else if (typeof source === 'object') { + // `source` is from object, json + if (settings.pluginNames) { //TODO: in context of JSON support, pluginNames may not exist here + for (let i = 0; i < plugins.length; i++) { + const plugin = plugins[i]; + const usePlugin = settings.pluginNames.some(pluginName => pluginName === plugin.name); + if (usePlugin) { + pluginsToUse.push(plugin); + } + } + } + } + return pluginsToUse; + } + + initExtensions() { + this.extensions = { + OES_texture_float: this.context.getExtension('OES_texture_float'), + OES_texture_float_linear: this.context.getExtension('OES_texture_float_linear'), + OES_element_index_uint: this.context.getExtension('OES_element_index_uint'), + WEBGL_draw_buffers: this.context.getExtension('WEBGL_draw_buffers'), + WEBGL_color_buffer_float: this.context.getExtension('WEBGL_color_buffer_float'), + }; + } + + /** + * @desc Validate settings related to Kernel, such as dimensions size, and auto output support. + * @param {IArguments} args + */ + validateSettings(args) { + if (!this.validate) { + this.texSize = utils.getKernelTextureSize({ + optimizeFloatMemory: this.optimizeFloatMemory, + precision: this.precision, + }, this.output); + return; + } + + const { features } = this.constructor; + if (this.optimizeFloatMemory === true && !features.isTextureFloat) { + throw new Error('Float textures are not supported'); + } else if (this.precision === 'single' && !features.isFloatRead) { + throw new Error('Single precision not supported'); + } else if (!this.graphical && this.precision === null && features.isTextureFloat) { + this.precision = features.isFloatRead ? 'single' : 'unsigned'; + } + + if (this.subKernels && this.subKernels.length > 0 && !this.extensions.WEBGL_draw_buffers) { + throw new Error('could not instantiate draw buffers extension'); + } + + if (this.fixIntegerDivisionAccuracy === null) { + this.fixIntegerDivisionAccuracy = !features.isIntegerDivisionAccurate; + } else if (this.fixIntegerDivisionAccuracy && features.isIntegerDivisionAccurate) { + this.fixIntegerDivisionAccuracy = false; + } + + this.checkOutput(); + + 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'); + } + + if (this.precision === 'precision') { + this.precision = 'unsigned'; + console.warn('Cannot use graphical mode and single precision at the same time'); + } + + this.texSize = utils.clone(this.output); + return; + } else if (this.precision === null && features.isTextureFloat) { + this.precision = 'single'; + } + + this.texSize = utils.getKernelTextureSize({ + optimizeFloatMemory: this.optimizeFloatMemory, + precision: this.precision, + }, this.output); + + this.checkTextureSize(); + } + + updateMaxTexSize() { + const { texSize, canvas } = this; + if (this.maxTexSize === null) { + let canvasIndex = canvases.indexOf(canvas); + if (canvasIndex === -1) { + canvasIndex = canvases.length; + canvases.push(canvas); + maxTexSizes[canvasIndex] = [texSize[0], texSize[1]]; + } + this.maxTexSize = maxTexSizes[canvasIndex]; + } + if (this.maxTexSize[0] < texSize[0]) { + this.maxTexSize[0] = texSize[0]; + } + if (this.maxTexSize[1] < texSize[1]) { + this.maxTexSize[1] = texSize[1]; + } + } + + // TODO: move channel checks to new place + _oldtranslateSource() { + const functionBuilder = FunctionBuilder.fromKernel(this, WebGLFunctionNode, { + fixIntegerDivisionAccuracy: this.fixIntegerDivisionAccuracy + }); + + // need this line to automatically get returnType + const translatedSource = functionBuilder.getPrototypeString('kernel'); + + if (!this.returnType) { + this.returnType = functionBuilder.getKernelResultType(); + } + + let requiredChannels = 0; + const returnTypes = functionBuilder.getReturnTypes(); + for (let i = 0; i < returnTypes.length; i++) { + switch (returnTypes[i]) { + case 'Float': + case 'Number': + case 'Integer': + requiredChannels++; + break; + case 'Array(2)': + requiredChannels += 2; + break; + case 'Array(3)': + requiredChannels += 3; + break; + case 'Array(4)': + requiredChannels += 4; + break; + } + } + + if (features && requiredChannels > features.channelCount) { + throw new Error('Too many channels!'); + } + + return this.translatedSource = translatedSource; + } + + setupArguments(args) { + this.kernelArguments = []; + this.argumentTextureCount = 0; + const needsArgumentTypes = this.argumentTypes === null; + // TODO: remove + if (needsArgumentTypes) { + this.argumentTypes = []; + } + this.argumentSizes = []; + this.argumentBitRatios = []; + // TODO: end remove + + if (args.length < this.argumentNames.length) { + throw new Error('not enough arguments for kernel'); + } else if (args.length > this.argumentNames.length) { + throw new Error('too many arguments for kernel'); + } + + const { context: gl } = this; + let textureIndexes = 0; + for (let index = 0; index < args.length; index++) { + const value = args[index]; + const name = this.argumentNames[index]; + let type; + if (needsArgumentTypes) { + type = utils.getVariableType(value, this.strictIntegers); + this.argumentTypes.push(type); + } else { + type = this.argumentTypes[index]; + } + const KernelValue = this.constructor.lookupKernelValueType(type, this.dynamicArguments ? 'dynamic' : 'static', this.precision, args[index]); + if (KernelValue === null) { + return this.requestFallback(args); + } + const kernelArgument = new KernelValue(value, { + name, + type, + tactic: this.tactic, + origin: 'user', + context: gl, + checkContext: this.checkContext, + kernel: this, + strictIntegers: this.strictIntegers, + onRequestTexture: () => { + return this.context.createTexture(); + }, + onRequestIndex: () => { + return textureIndexes++; + }, + onUpdateValueMismatch: () => { + this.switchingKernels = true; + }, + onRequestContextHandle: () => { + return gl.TEXTURE0 + this.constantTextureCount + this.argumentTextureCount++; + } + }); + this.kernelArguments.push(kernelArgument); + this.argumentSizes.push(kernelArgument.textureSize); + this.argumentBitRatios[index] = kernelArgument.bitRatio; + } + } + + setupConstants(args) { + const { context: gl } = this; + this.kernelConstants = []; + this.forceUploadKernelConstants = []; + let needsConstantTypes = this.constantTypes === null; + if (needsConstantTypes) { + this.constantTypes = {}; + } + this.constantBitRatios = {}; + let textureIndexes = 0; + for (const name in this.constants) { + const value = this.constants[name]; + let type; + if (needsConstantTypes) { + type = utils.getVariableType(value, this.strictIntegers); + this.constantTypes[name] = type; + } else { + type = this.constantTypes[name]; + } + const KernelValue = this.constructor.lookupKernelValueType(type, 'static', this.precision, value); + if (KernelValue === null) { + return this.requestFallback(args); + } + const kernelValue = new KernelValue(value, { + name, + type, + tactic: this.tactic, + origin: 'constants', + context: this.context, + checkContext: this.checkContext, + kernel: this, + strictIntegers: this.strictIntegers, + onRequestTexture: () => { + return this.context.createTexture(); + }, + onRequestIndex: () => { + return textureIndexes++; + }, + onRequestContextHandle: () => { + return gl.TEXTURE0 + this.constantTextureCount++; + } + }); + this.constantBitRatios[name] = kernelValue.bitRatio; + this.kernelConstants.push(kernelValue); + if (kernelValue.forceUploadEachRun) { + this.forceUploadKernelConstants.push(kernelValue); + } + } + } + + build() { + this.initExtensions(); + this.validateSettings(arguments); + this.setupConstants(arguments); + if (this.fallbackRequested) return; + this.setupArguments(arguments); + if (this.fallbackRequested) return; + this.updateMaxTexSize(); + this.translateSource(); + const failureResult = this.pickRenderStrategy(arguments); + if (failureResult) { + return failureResult; + } + const { texSize, context: gl, canvas } = this; + gl.enable(gl.SCISSOR_TEST); + if (this.pipeline && this.precision === 'single') { + gl.viewport(0, 0, this.maxTexSize[0], this.maxTexSize[1]); + canvas.width = this.maxTexSize[0]; + canvas.height = this.maxTexSize[1]; + } else { + gl.viewport(0, 0, this.maxTexSize[0], this.maxTexSize[1]); + canvas.width = this.maxTexSize[0]; + canvas.height = this.maxTexSize[1]; + } + const threadDim = this.threadDim = Array.from(this.output); + while (threadDim.length < 3) { + threadDim.push(1); + } + + const compiledVertexShader = this.getVertexShader(arguments); + const vertShader = gl.createShader(gl.VERTEX_SHADER); + gl.shaderSource(vertShader, compiledVertexShader); + gl.compileShader(vertShader); + this.vertShader = vertShader; + + const compiledFragmentShader = this.getFragmentShader(arguments); + const fragShader = gl.createShader(gl.FRAGMENT_SHADER); + gl.shaderSource(fragShader, compiledFragmentShader); + gl.compileShader(fragShader); + this.fragShader = fragShader; + + if (this.debug) { + console.log('GLSL Shader Output:'); + console.log(compiledFragmentShader); + } + + if (!gl.getShaderParameter(vertShader, gl.COMPILE_STATUS)) { + throw new Error('Error compiling vertex shader: ' + gl.getShaderInfoLog(vertShader)); + } + if (!gl.getShaderParameter(fragShader, gl.COMPILE_STATUS)) { + throw new Error('Error compiling fragment shader: ' + gl.getShaderInfoLog(fragShader)); + } + + const program = this.program = gl.createProgram(); + gl.attachShader(program, vertShader); + gl.attachShader(program, fragShader); + gl.linkProgram(program); + this.framebuffer = gl.createFramebuffer(); + this.framebuffer.width = texSize[0]; + this.framebuffer.height = texSize[1]; + + const vertices = new Float32Array([-1, -1, + 1, -1, -1, 1, + 1, 1 + ]); + const texCoords = new Float32Array([ + 0, 0, + 1, 0, + 0, 1, + 1, 1 + ]); + + const texCoordOffset = vertices.byteLength; + + let buffer = this.buffer; + if (!buffer) { + buffer = this.buffer = gl.createBuffer(); + 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); + + const aPosLoc = gl.getAttribLocation(this.program, 'aPos'); + gl.enableVertexAttribArray(aPosLoc); + gl.vertexAttribPointer(aPosLoc, 2, gl.FLOAT, false, 0, 0); + const aTexCoordLoc = gl.getAttribLocation(this.program, 'aTexCoord'); + gl.enableVertexAttribArray(aTexCoordLoc); + gl.vertexAttribPointer(aTexCoordLoc, 2, gl.FLOAT, false, 0, texCoordOffset); + gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); + + let i = 0; + gl.useProgram(this.program); + for (let p in this.constants) { + this.kernelConstants[i++].updateValue(this.constants[p]); + } + + if (!this.immutable) { + this._setupOutputTexture(); + if ( + this.subKernels !== null && + this.subKernels.length > 0 + ) { + this._setupSubOutputTextures(); + } + } + } + + translateSource() { + const functionBuilder = FunctionBuilder.fromKernel(this, WebGLFunctionNode, { + fixIntegerDivisionAccuracy: this.fixIntegerDivisionAccuracy + }); + this.translatedSource = functionBuilder.getPrototypeString('kernel'); + if (!this.graphical && !this.returnType) { + this.returnType = functionBuilder.getKernelResultType(); + } + + if (this.subKernels && this.subKernels.length > 0) { + for (let i = 0; i < this.subKernels.length; i++) { + const subKernel = this.subKernels[i]; + if (!subKernel.returnType) { + subKernel.returnType = functionBuilder.getSubKernelResultType(i); + } + } + } + } + + run() { + const { kernelArguments, forceUploadKernelConstants } = this; + const texSize = this.texSize; + const gl = this.context; + + gl.useProgram(this.program); + gl.scissor(0, 0, texSize[0], texSize[1]); + + if (this.dynamicOutput) { + this.setUniform3iv('uOutputDim', new Int32Array(this.threadDim)); + this.setUniform2iv('uTexSize', texSize); + } + + this.setUniform2f('ratio', texSize[0] / this.maxTexSize[0], texSize[1] / this.maxTexSize[1]); + + this.switchingKernels = false; + for (let i = 0; i < forceUploadKernelConstants.length; i++) { + const constant = forceUploadKernelConstants[i]; + constant.updateValue(this.constants[constant.name]); + if (this.switchingKernels) return; + } + for (let i = 0; i < kernelArguments.length; i++) { + kernelArguments[i].updateValue(arguments[i]); + if (this.switchingKernels) return; + } + + if (this.plugins) { + for (let i = 0; i < this.plugins.length; i++) { + const plugin = this.plugins[i]; + if (plugin.onBeforeRun) { + plugin.onBeforeRun(this); + } + } + } + + if (this.graphical) { + if (this.pipeline) { + gl.bindRenderbuffer(gl.RENDERBUFFER, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); + if (!this.outputTexture || this.immutable) { + this._setupOutputTexture(); + } + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + return new this.TextureConstructor({ + texture: this.outputTexture, + size: texSize, + dimensions: this.threadDim, + output: this.output, + context: this.context, + }); + } + gl.bindRenderbuffer(gl.RENDERBUFFER, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + return; + } + + gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); + if (this.immutable) { + this._setupOutputTexture(); + } + + if (this.subKernels !== null) { + if (this.immutable) { + this._setupSubOutputTextures(); + } + this.extensions.WEBGL_draw_buffers.drawBuffersWEBGL(this.drawBuffersMap); + } + + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + } + + /** + * @desc This return defined outputTexture, which is setup in .build(), or if immutable, is defined in .run() + * @returns {Object} Output Texture Cache + */ + getOutputTexture() { + return this.outputTexture; + } + + /** + * @desc Setup and replace output texture + */ + _setupOutputTexture() { + const gl = this.context; + const texSize = this.texSize; + const texture = this.outputTexture = this.context.createTexture(); + gl.activeTexture(gl.TEXTURE0 + this.constantTextureCount + this.argumentTextureCount); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + // if (this.precision === 'single') { + // gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize[0], texSize[1], 0, gl.RGBA, gl.FLOAT, null); + // } else { + // gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize[0], texSize[1], 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + // } + if (this.precision === 'single') { + if (this.pipeline) { + // TODO: investigate if webgl1 can handle gl.RED usage in gl.texImage2D, otherwise, simplify the below + switch (this.returnType) { + case 'Number': + case 'Float': + case 'Integer': + if (this.optimizeFloatMemory) { + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize[0], texSize[1], 0, gl.RGBA, gl.FLOAT, null); + } else { + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize[0], texSize[1], 0, gl.RGBA, gl.FLOAT, null); + } + break; + case 'Array(2)': + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize[0], texSize[1], 0, gl.RGBA, gl.FLOAT, null); + break; + case 'Array(3)': + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize[0], texSize[1], 0, gl.RGBA, gl.FLOAT, null); + break; + case 'Array(4)': + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize[0], texSize[1], 0, gl.RGBA, gl.FLOAT, null); + break; + default: + if (!this.graphical) { + throw new Error('Unhandled return type'); + } + } + } else { + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize[0], texSize[1], 0, gl.RGBA, gl.FLOAT, null); + } + } else { + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize[0], texSize[1], 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + } + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); + } + + /** + * @desc Setup and replace sub-output textures + */ + _setupSubOutputTextures() { + const gl = this.context; + const texSize = this.texSize; + this.drawBuffersMap = [gl.COLOR_ATTACHMENT0]; + this.subKernelOutputTextures = []; + for (let i = 0; i < this.subKernels.length; i++) { + const texture = this.context.createTexture(); + this.subKernelOutputTextures.push(texture); + this.drawBuffersMap.push(gl.COLOR_ATTACHMENT0 + i + 1); + gl.activeTexture(gl.TEXTURE0 + this.constantTextureCount + this.argumentTextureCount + i); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + if (this.precision === 'single') { + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize[0], texSize[1], 0, gl.RGBA, gl.FLOAT, null); + } else { + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize[0], texSize[1], 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + } + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i + 1, gl.TEXTURE_2D, texture, 0); + } + } + + /** + * @desc Returns the Texture Cache of the supplied parameter (can be kernel, sub-kernel or argument) + * @param {String} name - Name of the subkernel, argument, or kernel. + * @returns {Object} Texture cache + */ + getTextureCache(name) { + if (this.textureCache.hasOwnProperty(name)) { + return this.textureCache[name]; + } + return this.textureCache[name] = this.context.createTexture(); + } + + /** + * @desc removes a texture from the kernel's cache + * @param {String} name - Name of texture + */ + detachTextureCache(name) { + delete this.textureCache[name]; + } + + setUniform1f(name, value) { + if (this.uniform1fCache.hasOwnProperty(name)) { + const cache = this.uniform1fCache[name]; + if (value === cache) { + return; + } + } + this.uniform1fCache[name] = value; + const loc = this.getUniformLocation(name); + this.context.uniform1f(loc, value); + } + + setUniform1i(name, value) { + if (this.uniform1iCache.hasOwnProperty(name)) { + const cache = this.uniform1iCache[name]; + if (value === cache) { + return; + } + } + this.uniform1iCache[name] = value; + const loc = this.getUniformLocation(name); + this.context.uniform1i(loc, value); + } + + setUniform2f(name, value1, value2) { + if (this.uniform2fCache.hasOwnProperty(name)) { + const cache = this.uniform2fCache[name]; + if ( + value1 === cache[0] && + value2 === cache[1] + ) { + return; + } + } + this.uniform2fCache[name] = [value1, value2]; + const loc = this.getUniformLocation(name); + this.context.uniform2f(loc, value1, value2); + } + + setUniform2fv(name, value) { + if (this.uniform2fvCache.hasOwnProperty(name)) { + const cache = this.uniform2fvCache[name]; + if ( + value[0] === cache[0] && + value[1] === cache[1] + ) { + return; + } + } + this.uniform2fvCache[name] = value; + const loc = this.getUniformLocation(name); + this.context.uniform2fv(loc, value); + } + + setUniform2iv(name, value) { + if (this.uniform2ivCache.hasOwnProperty(name)) { + const cache = this.uniform2ivCache[name]; + if ( + value[0] === cache[0] && + value[1] === cache[1] + ) { + return; + } + } + this.uniform2ivCache[name] = value; + const loc = this.getUniformLocation(name); + this.context.uniform2iv(loc, value); + } + + setUniform3fv(name, value) { + if (this.uniform3fvCache.hasOwnProperty(name)) { + const cache = this.uniform3fvCache[name]; + if ( + value[0] === cache[0] && + value[1] === cache[1] && + value[2] === cache[2] + ) { + return; + } + } + this.uniform3fvCache[name] = value; + const loc = this.getUniformLocation(name); + this.context.uniform3fv(loc, value); + } + + setUniform3iv(name, value) { + if (this.uniform3ivCache.hasOwnProperty(name)) { + const cache = this.uniform3ivCache[name]; + if ( + value[0] === cache[0] && + value[1] === cache[1] && + value[2] === cache[2] + ) { + return; + } + } + this.uniform3ivCache[name] = value; + const loc = this.getUniformLocation(name); + this.context.uniform3iv(loc, value); + } + + setUniform3fv(name, value) { + if (this.uniform3fvCache.hasOwnProperty(name)) { + const cache = this.uniform3fvCache[name]; + if ( + value[0] === cache[0] && + value[1] === cache[1] && + value[2] === cache[2] + ) { + return; + } + } + this.uniform3fvCache[name] = value; + const loc = this.getUniformLocation(name); + this.context.uniform3fv(loc, value); + } + + setUniform4iv(name, value) { + if (this.uniform4ivCache.hasOwnProperty(name)) { + const cache = this.uniform4ivCache[name]; + if ( + value[0] === cache[0] && + value[1] === cache[1] && + value[2] === cache[2] && + value[3] === cache[3] + ) { + return; + } + } + this.uniform4ivCache[name] = value; + const loc = this.getUniformLocation(name); + this.context.uniform4iv(loc, value); + } + + setUniform4fv(name, value) { + if (this.uniform4fvCache.hasOwnProperty(name)) { + const cache = this.uniform4fvCache[name]; + if ( + value[0] === cache[0] && + value[1] === cache[1] && + value[2] === cache[2] && + value[3] === cache[3] + ) { + return; + } + } + this.uniform4fvCache[name] = value; + const loc = this.getUniformLocation(name); + this.context.uniform4fv(loc, value); + } + + /** + * @desc Return WebGlUniformLocation for various variables + * related to webGl program, such as user-defined variables, + * as well as, dimension sizes, etc. + */ + getUniformLocation(name) { + if (this.programUniformLocationCache.hasOwnProperty(name)) { + return this.programUniformLocationCache[name]; + } + return this.programUniformLocationCache[name] = this.context.getUniformLocation(this.program, name); + } + + /** + * @desc Generate Shader artifacts for the kernel program. + * The final object contains HEADER, KERNEL, MAIN_RESULT, and others. + * + * @param {Array} args - The actual parameters sent to the Kernel + * @returns {Object} An object containing the Shader Artifacts(CONSTANTS, HEADER, KERNEL, etc.) + */ + _getFragShaderArtifactMap(args) { + return { + HEADER: this._getHeaderString(), + LOOP_MAX: this._getLoopMaxString(), + PLUGINS: this._getPluginsString(), + CONSTANTS: this._getConstantsString(), + DECODE32_ENDIANNESS: this._getDecode32EndiannessString(), + ENCODE32_ENDIANNESS: this._getEncode32EndiannessString(), + DIVIDE_WITH_INTEGER_CHECK: this._getDivideWithIntegerCheckString(), + INJECTED_NATIVE: this._getInjectedNative(), + MAIN_CONSTANTS: this._getMainConstantsString(), + MAIN_ARGUMENTS: this._getMainArgumentsString(args), + KERNEL: this.getKernelString(), + MAIN_RESULT: this.getMainResultString(), + FLOAT_TACTIC_DECLARATION: this.getFloatTacticDeclaration(), + INT_TACTIC_DECLARATION: this.getIntTacticDeclaration(), + SAMPLER_2D_TACTIC_DECLARATION: this.getSampler2DTacticDeclaration(), + SAMPLER_2D_ARRAY_TACTIC_DECLARATION: this.getSampler2DArrayTacticDeclaration(), + }; + } + + /** + * @desc Generate Shader artifacts for the kernel program. + * The final object contains HEADER, KERNEL, MAIN_RESULT, and others. + * + * @param {Array} args - The actual parameters sent to the Kernel + * @returns {Object} An object containing the Shader Artifacts(CONSTANTS, HEADER, KERNEL, etc.) + */ + _getVertShaderArtifactMap(args) { + return { + FLOAT_TACTIC_DECLARATION: this.getFloatTacticDeclaration(), + INT_TACTIC_DECLARATION: this.getIntTacticDeclaration(), + SAMPLER_2D_TACTIC_DECLARATION: this.getSampler2DTacticDeclaration(), + SAMPLER_2D_ARRAY_TACTIC_DECLARATION: this.getSampler2DArrayTacticDeclaration(), + }; + } + + /** + * @desc Get the header string for the program. + * This returns an empty string if no sub-kernels are defined. + * + * @returns {String} result + */ + _getHeaderString() { + return ( + this.subKernels !== null ? + '#extension GL_EXT_draw_buffers : require\n' : + '' + ); + } + + /** + * @desc Get the maximum loop size String. + * @returns {String} result + */ + _getLoopMaxString() { + return ( + this.loopMaxIterations ? + ` ${parseInt(this.loopMaxIterations)};\n` : + ' 1000;\n' + ); + } + + _getPluginsString() { + if (!this.plugins) return '\n'; + return this.plugins.map(plugin => plugin.source && this.source.match(plugin.functionMatch) ? plugin.source : '').join('\n'); + } + + /** + * @desc Generate transpiled glsl Strings for constant parameters sent to a kernel + * @returns {String} result + */ + _getConstantsString() { + const result = []; + const { threadDim, texSize } = this; + if (this.dynamicOutput) { + result.push( + 'uniform ivec3 uOutputDim', + 'uniform ivec2 uTexSize' + ); + } else { + result.push( + `ivec3 uOutputDim = ivec3(${threadDim[0]}, ${threadDim[1]}, ${threadDim[2]})`, + `ivec2 uTexSize = ivec2(${texSize[0]}, ${texSize[1]})` + ); + } + return utils.linesToString(result); + } + + /** + * @desc Get texture coordinate string for the program + * @returns {String} result + */ + _getTextureCoordinate() { + const subKernels = this.subKernels; + if (subKernels === null || subKernels.length < 1) { + return 'varying vec2 vTexCoord;\n'; + } else { + return 'out vec2 vTexCoord;\n'; + } + } + + /** + * @desc Get Decode32 endianness string for little-endian and big-endian + * @returns {String} result + */ + _getDecode32EndiannessString() { + return ( + this.endianness === 'LE' ? + '' : + ' texel.rgba = texel.abgr;\n' + ); + } + + /** + * @desc Get Encode32 endianness string for little-endian and big-endian + * @returns {String} result + */ + _getEncode32EndiannessString() { + return ( + this.endianness === 'LE' ? + '' : + ' texel.rgba = texel.abgr;\n' + ); + } + + /** + * @desc if fixIntegerDivisionAccuracy provide method to replace / + * @returns {String} result + */ + _getDivideWithIntegerCheckString() { + return this.fixIntegerDivisionAccuracy ? + `float div_with_int_check(float x, float y) { + if (floor(x) == x && floor(y) == y && integerMod(x, y) == 0.0) { + return float(int(x)/int(y)); + } + return x / y; +}` : + ''; + } + + /** + * @desc Generate transpiled glsl Strings for user-defined parameters sent to a kernel + * @param {Array} args - The actual parameters sent to the Kernel + * @returns {String} result + */ + _getMainArgumentsString(args) { + const results = []; + const { argumentNames } = this; + for (let i = 0; i < argumentNames.length; i++) { + results.push(this.kernelArguments[i].getSource(args[i])); + } + return results.join(''); + } + + _getInjectedNative() { + return this.injectedNative || ''; + } + + _getMainConstantsString() { + const result = []; + const { constants } = this; + if (constants) { + let i = 0; + for (const name in constants) { + result.push(this.kernelConstants[i++].getSource(this.constants[name])); + } + } + return result.join(''); + } + + /** + * @desc Get Kernel program string (in *glsl*) for a kernel. + * @returns {String} result + */ + getKernelString() { + let kernelResultDeclaration; + switch (this.returnType) { + case 'Array(2)': + kernelResultDeclaration = 'vec2 kernelResult'; + break; + case 'Array(3)': + kernelResultDeclaration = 'vec3 kernelResult'; + break; + case 'Array(4)': + kernelResultDeclaration = 'vec4 kernelResult'; + break; + case 'LiteralInteger': + case 'Float': + case 'Number': + case 'Integer': + kernelResultDeclaration = 'float kernelResult'; + break; + default: + if (this.graphical) { + kernelResultDeclaration = 'float kernelResult'; + } else { + throw new Error(`unrecognized output type "${ this.returnType }"`); + } + } + + const result = []; + const subKernels = this.subKernels; + if (subKernels !== null) { + result.push( + kernelResultDeclaration + ); + switch (this.returnType) { + case 'Number': + case 'Float': + case 'Integer': + for (let i = 0; i < subKernels.length; i++) { + const subKernel = subKernels[i]; + result.push( + subKernel.returnType === 'Integer' ? + `int subKernelResult_${ subKernel.name } = 0` : + `float subKernelResult_${ subKernel.name } = 0.0` + ); + } + break; + case 'Array(2)': + for (let i = 0; i < subKernels.length; i++) { + result.push( + `vec2 subKernelResult_${ subKernels[i].name }` + ); + } + break; + case 'Array(3)': + for (let i = 0; i < subKernels.length; i++) { + result.push( + `vec3 subKernelResult_${ subKernels[i].name }` + ); + } + break; + case 'Array(4)': + for (let i = 0; i < subKernels.length; i++) { + result.push( + `vec4 subKernelResult_${ subKernels[i].name }` + ); + } + break; + } + } else { + result.push( + kernelResultDeclaration + ); + } + + return utils.linesToString(result) + this.translatedSource; + } + + getMainResultGraphical() { + return utils.linesToString([ + ' threadId = indexTo3D(index, uOutputDim)', + ' kernel()', + ' gl_FragColor = actualColor', + ]); + } + + getMainResultPackedPixels() { + switch (this.returnType) { + case 'LiteralInteger': + case 'Number': + case 'Integer': + case 'Float': + return this.getMainResultKernelPackedPixels() + + this.getMainResultSubKernelPackedPixels(); + default: + throw new Error(`packed output only usable with Numbers, "${this.returnType}" specified`); + } + } + + /** + * @return {String} + */ + getMainResultKernelPackedPixels() { + return utils.linesToString([ + ' threadId = indexTo3D(index, uOutputDim)', + ' kernel()', + ` gl_FragData[0] = ${this.useLegacyEncoder ? 'legacyEncode32' : 'encode32'}(kernelResult)` + ]); + } + + /** + * @return {String} + */ + getMainResultSubKernelPackedPixels() { + const result = []; + if (!this.subKernels) return ''; + for (let i = 0; i < this.subKernels.length; i++) { + const subKernel = this.subKernels[i]; + if (subKernel.returnType === 'Integer') { + result.push( + ` gl_FragData[${i + 1}] = ${this.useLegacyEncoder ? 'legacyEncode32' : 'encode32'}(float(subKernelResult_${this.subKernels[i].name}))` + ); + } else { + result.push( + ` gl_FragData[${i + 1}] = ${this.useLegacyEncoder ? 'legacyEncode32' : 'encode32'}(subKernelResult_${this.subKernels[i].name})` + ); + } + } + return utils.linesToString(result); + } + + getMainResultMemoryOptimizedFloats() { + const result = [ + ' index *= 4', + ]; + + switch (this.returnType) { + case 'Number': + case 'Integer': + case 'Float': + const channels = ['r', 'g', 'b', 'a']; + for (let i = 0; i < channels.length; i++) { + const channel = channels[i]; + this.getMainResultKernelMemoryOptimizedFloats(result, channel); + this.getMainResultSubKernelMemoryOptimizedFloats(result, channel); + if (i + 1 < channels.length) { + result.push(' index += 1'); + } + } + break; + default: + throw new Error(`optimized output only usable with Numbers, ${this.returnType} specified`); + } + + return utils.linesToString(result); + } + + getMainResultKernelMemoryOptimizedFloats(result, channel) { + result.push( + ' threadId = indexTo3D(index, uOutputDim)', + ' kernel()', + ` gl_FragData[0].${channel} = kernelResult`, + ); + } + + getMainResultSubKernelMemoryOptimizedFloats(result, channel) { + if (!this.subKernels) return result; + for (let i = 0; i < this.subKernels.length; i++) { + const subKernel = this.subKernels[i]; + if (subKernel.returnType === 'Integer') { + result.push( + ` gl_FragData[${i + 1}].${channel} = float(subKernelResult_${this.subKernels[i].name})`, + ); + } else { + result.push( + ` gl_FragData[${i + 1}].${channel} = subKernelResult_${this.subKernels[i].name}`, + ); + } + } + } + + getMainResultKernelNumberTexture() { + return [ + ' threadId = indexTo3D(index, uOutputDim)', + ' kernel()', + ' gl_FragData[0][0] = kernelResult', + ]; + } + + getMainResultSubKernelNumberTexture() { + const result = []; + if (!this.subKernels) return result; + for (let i = 0; i < this.subKernels.length; ++i) { + const subKernel = this.subKernels[i]; + if (subKernel.returnType === 'Integer') { + result.push( + ` gl_FragData[${i + 1}][0] = float(subKernelResult_${subKernel.name})`, + ); + } else { + result.push( + ` gl_FragData[${i + 1}][0] = subKernelResult_${subKernel.name}`, + ); + } + } + return result; + } + + getMainResultKernelArray2Texture() { + return [ + ' threadId = indexTo3D(index, uOutputDim)', + ' kernel()', + ' gl_FragData[0][0] = kernelResult[0]', + ' gl_FragData[0][1] = kernelResult[1]', + ]; + } + + getMainResultSubKernelArray2Texture() { + const result = []; + if (!this.subKernels) return result; + for (let i = 0; i < this.subKernels.length; ++i) { + result.push( + ` gl_FragData[${i + 1}][0] = subKernelResult_${this.subKernels[i].name}[0]`, + ` gl_FragData[${i + 1}][1] = subKernelResult_${this.subKernels[i].name}[1]`, + ); + } + return result; + } + + getMainResultKernelArray3Texture() { + return [ + ' threadId = indexTo3D(index, uOutputDim)', + ' kernel()', + ' gl_FragData[0][0] = kernelResult[0]', + ' gl_FragData[0][1] = kernelResult[1]', + ' gl_FragData[0][2] = kernelResult[2]', + ]; + } + + getMainResultSubKernelArray3Texture() { + const result = []; + if (!this.subKernels) return result; + for (let i = 0; i < this.subKernels.length; ++i) { + result.push( + ` gl_FragData[${i + 1}][0] = subKernelResult_${this.subKernels[i].name}[0]`, + ` gl_FragData[${i + 1}][1] = subKernelResult_${this.subKernels[i].name}[1]`, + ` gl_FragData[${i + 1}][2] = subKernelResult_${this.subKernels[i].name}[2]`, + ); + } + return result; + } + + getMainResultKernelArray4Texture() { + return [ + ' threadId = indexTo3D(index, uOutputDim)', + ' kernel()', + ' gl_FragData[0] = kernelResult', + ]; + } + + getMainResultSubKernelArray4Texture() { + const result = []; + if (!this.subKernels) return result; + switch (this.returnType) { + case 'Number': + case 'Float': + case 'Integer': + for (let i = 0; i < this.subKernels.length; ++i) { + const subKernel = this.subKernels[i]; + if (subKernel.returnType === 'Integer') { + result.push( + ` gl_FragData[${i + 1}] = float(subKernelResult_${this.subKernels[i].name})`, + ); + } else { + result.push( + ` gl_FragData[${i + 1}] = subKernelResult_${this.subKernels[i].name}`, + ); + } + } + break; + case 'Array(2)': + for (let i = 0; i < this.subKernels.length; ++i) { + result.push( + ` gl_FragData[${i + 1}][0] = subKernelResult_${this.subKernels[i].name}[0]`, + ` gl_FragData[${i + 1}][1] = subKernelResult_${this.subKernels[i].name}[1]`, + ); + } + break; + case 'Array(3)': + for (let i = 0; i < this.subKernels.length; ++i) { + result.push( + ` gl_FragData[${i + 1}][0] = subKernelResult_${this.subKernels[i].name}[0]`, + ` gl_FragData[${i + 1}][1] = subKernelResult_${this.subKernels[i].name}[1]`, + ` gl_FragData[${i + 1}][2] = subKernelResult_${this.subKernels[i].name}[2]`, + ); + } + break; + case 'Array(4)': + for (let i = 0; i < this.subKernels.length; ++i) { + result.push( + ` gl_FragData[${i + 1}][0] = subKernelResult_${this.subKernels[i].name}[0]`, + ` gl_FragData[${i + 1}][1] = subKernelResult_${this.subKernels[i].name}[1]`, + ` gl_FragData[${i + 1}][2] = subKernelResult_${this.subKernels[i].name}[2]`, + ` gl_FragData[${i + 1}][3] = subKernelResult_${this.subKernels[i].name}[3]`, + ); + } + break; + } + + return result; + } + + /** + * @param {String} src - Shader string + * @param {Object} map - Variables/Constants associated with shader + */ + replaceArtifacts(src, map) { + return src.replace(/[ ]*__([A-Z]+[0-9]*([_]?[A-Z]*[0-9]?)*)__;\n/g, (match, artifact) => { + if (map.hasOwnProperty(artifact)) { + return map[artifact]; + } + throw `unhandled artifact ${artifact}`; + }); + } + + /** + * @desc Get the fragment shader String. + * If the String hasn't been compiled yet, + * then this method compiles it as well + * + * @param {Array} args - The actual parameters sent to the Kernel + * @returns {string} Fragment Shader string + */ + getFragmentShader(args) { + if (this.compiledFragmentShader !== null) { + return this.compiledFragmentShader; + } + return this.compiledFragmentShader = this.replaceArtifacts(this.constructor.fragmentShader, this._getFragShaderArtifactMap(args)); + } + + /** + * @desc Get the vertical shader String + * @param {Array|IArguments} args - The actual parameters sent to the Kernel + * @returns {string} Vertical Shader string + */ + getVertexShader(args) { + if (this.compiledVertexShader !== null) { + return this.compiledVertexShader; + } + return this.compiledVertexShader = this.replaceArtifacts(this.constructor.vertexShader, this._getVertShaderArtifactMap(args)); + } + + /** + * @desc Returns the *pre-compiled* Kernel as a JS Object String, that can be reused. + */ + toString() { + const setupContextString = utils.linesToString([ + `const gl = context`, + ]); + return glKernelString(this.constructor, arguments, this, setupContextString); + } + + destroy(removeCanvasReferences) { + if (this.outputTexture) { + this.context.deleteTexture(this.outputTexture); + } + if (this.buffer) { + this.context.deleteBuffer(this.buffer); + } + if (this.framebuffer) { + this.context.deleteFramebuffer(this.framebuffer); + } + if (this.vertShader) { + this.context.deleteShader(this.vertShader); + } + if (this.fragShader) { + this.context.deleteShader(this.fragShader); + } + if (this.program) { + this.context.deleteProgram(this.program); + } + + const keys = Object.keys(this.textureCache); + + for (let i = 0; i < keys.length; i++) { + const name = keys[i]; + this.context.deleteTexture(this.textureCache[name]); + } + + if (this.subKernelOutputTextures) { + for (let i = 0; i < this.subKernelOutputTextures.length; i++) { + this.context.deleteTexture(this.subKernelOutputTextures[i]); + } + } + if (removeCanvasReferences) { + const idx = canvases.indexOf(this.canvas); + if (idx >= 0) { + canvases[idx] = null; + maxTexSizes[idx] = null; + } + } + this.destroyExtensions(); + delete this.context; + delete this.canvas; + } + + destroyExtensions() { + this.extensions.OES_texture_float = null; + this.extensions.OES_texture_float_linear = null; + this.extensions.OES_element_index_uint = null; + this.extensions.WEBGL_draw_buffers = null; + } + + static destroyContext(context) { + const extension = context.getExtension('WEBGL_lose_context'); + if (extension) { + extension.loseContext(); + } + } + + toJSON() { + const json = super.toJSON(); + json.functionNodes = FunctionBuilder.fromKernel(this, WebGLFunctionNode).toJSON(); + return json; + } +} diff --git a/src/backend/web-gl/vertex-shader.js b/src/backend/web-gl/vertex-shader.js index 88647d73..f4e7cf49 100644 --- a/src/backend/web-gl/vertex-shader.js +++ b/src/backend/web-gl/vertex-shader.js @@ -1,19 +1,15 @@ -// language=GLSL -const vertexShader = `__FLOAT_TACTIC_DECLARATION__; -__INT_TACTIC_DECLARATION__; -__SAMPLER_2D_TACTIC_DECLARATION__; - -attribute vec2 aPos; -attribute vec2 aTexCoord; - -varying vec2 vTexCoord; -uniform vec2 ratio; - -void main(void) { - gl_Position = vec4((aPos + vec2(1)) * ratio + vec2(-1), 0, 1); - vTexCoord = aTexCoord; -}`; - -module.exports = { - vertexShader -}; \ No newline at end of file +// language=GLSL +export const vertexShader = `__FLOAT_TACTIC_DECLARATION__; +__INT_TACTIC_DECLARATION__; +__SAMPLER_2D_TACTIC_DECLARATION__; + +attribute vec2 aPos; +attribute vec2 aTexCoord; + +varying vec2 vTexCoord; +uniform vec2 ratio; + +void main(void) { + gl_Position = vec4((aPos + vec2(1)) * ratio + vec2(-1), 0, 1); + vTexCoord = aTexCoord; +}`; diff --git a/src/backend/web-gl2/fragment-shader.js b/src/backend/web-gl2/fragment-shader.js index 8a31c1be..d50dcae3 100644 --- a/src/backend/web-gl2/fragment-shader.js +++ b/src/backend/web-gl2/fragment-shader.js @@ -1,395 +1,391 @@ -// language=GLSL -const fragmentShader = `#version 300 es -__HEADER__; -__FLOAT_TACTIC_DECLARATION__; -__INT_TACTIC_DECLARATION__; -__SAMPLER_2D_TACTIC_DECLARATION__; -__SAMPLER_2D_ARRAY_TACTIC_DECLARATION__; - -const int LOOP_MAX = __LOOP_MAX__; - -__PLUGINS__; -__CONSTANTS__; - -in vec2 vTexCoord; - -const int BIT_COUNT = 32; -int modi(int x, int y) { - return x - y * (x / y); -} - -int bitwiseOr(int a, int b) { - int result = 0; - int n = 1; - - for (int i = 0; i < BIT_COUNT; i++) { - if ((modi(a, 2) == 1) || (modi(b, 2) == 1)) { - result += n; - } - a = a / 2; - b = b / 2; - n = n * 2; - if(!(a > 0 || b > 0)) { - break; - } - } - return result; -} -int bitwiseXOR(int a, int b) { - int result = 0; - int n = 1; - - for (int i = 0; i < BIT_COUNT; i++) { - if ((modi(a, 2) == 1) != (modi(b, 2) == 1)) { - result += n; - } - a = a / 2; - b = b / 2; - n = n * 2; - if(!(a > 0 || b > 0)) { - break; - } - } - return result; -} -int bitwiseAnd(int a, int b) { - int result = 0; - int n = 1; - for (int i = 0; i < BIT_COUNT; i++) { - if ((modi(a, 2) == 1) && (modi(b, 2) == 1)) { - result += n; - } - a = a / 2; - b = b / 2; - n = n * 2; - if(!(a > 0 && b > 0)) { - break; - } - } - return result; -} -int bitwiseNot(int a) { - int result = 0; - int n = 1; - - for (int i = 0; i < BIT_COUNT; i++) { - if (modi(a, 2) == 0) { - result += n; - } - a = a / 2; - n = n * 2; - } - return result; -} -int bitwiseZeroFillLeftShift(int n, int shift) { - int maxBytes = BIT_COUNT; - for (int i = 0; i < BIT_COUNT; i++) { - if (maxBytes >= n) { - break; - } - maxBytes *= 2; - } - for (int i = 0; i < BIT_COUNT; i++) { - if (i >= shift) { - break; - } - n *= 2; - } - - int result = 0; - int byteVal = 1; - for (int i = 0; i < BIT_COUNT; i++) { - if (i >= maxBytes) break; - if (modi(n, 2) > 0) { result += byteVal; } - n = int(n / 2); - byteVal *= 2; - } - return result; -} - -int bitwiseSignedRightShift(int num, int shifts) { - return int(floor(float(num) / pow(2.0, float(shifts)))); -} - -int bitwiseZeroFillRightShift(int n, int shift) { - int maxBytes = BIT_COUNT; - for (int i = 0; i < BIT_COUNT; i++) { - if (maxBytes >= n) { - break; - } - maxBytes *= 2; - } - for (int i = 0; i < BIT_COUNT; i++) { - if (i >= shift) { - break; - } - n /= 2; - } - int result = 0; - int byteVal = 1; - for (int i = 0; i < BIT_COUNT; i++) { - if (i >= maxBytes) break; - if (modi(n, 2) > 0) { result += byteVal; } - n = int(n / 2); - byteVal *= 2; - } - return result; -} - -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); -} - -float integerMod(float x, float y) { - float res = floor(mod(x, y)); - return res * (res > floor(y) - 1.0 ? 0.0 : 1.0); -} - -int integerMod(int x, int y) { - return x - (y * int(x/y)); -} - -__DIVIDE_WITH_INTEGER_CHECK__; - -// 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 -float decode32(vec4 texel) { - __DECODE32_ENDIANNESS__; - texel *= 255.0; - vec2 gte128; - gte128.x = texel.b >= 128.0 ? 1.0 : 0.0; - gte128.y = texel.a >= 128.0 ? 1.0 : 0.0; - float exponent = 2.0 * texel.a - 127.0 + dot(gte128, MAGIC_VEC); - float res = exp2(round(exponent)); - texel.b = texel.b - 128.0 * gte128.x; - res = dot(texel, SCALE_FACTOR) * exp2(round(exponent-23.0)) + res; - res *= gte128.y * -2.0 + 1.0; - return res; -} - -float decode16(vec4 texel, int index) { - int channel = integerMod(index, 2); - return texel[channel*2] * 255.0 + texel[channel*2 + 1] * 65280.0; -} - -float decode8(vec4 texel, int index) { - int channel = integerMod(index, 4); - return texel[channel] * 255.0; -} - -vec4 legacyEncode32(float f) { - float F = abs(f); - float sign = f < 0.0 ? 1.0 : 0.0; - float exponent = floor(log2(F)); - float mantissa = (exp2(-exponent) * F); - // exponent += floor(log2(mantissa)); - vec4 texel = vec4(F * exp2(23.0-exponent)) * SCALE_FACTOR_INV; - texel.rg = integerMod(texel.rg, 256.0); - texel.b = integerMod(texel.b, 128.0); - texel.a = exponent*0.5 + 63.5; - texel.ba += vec2(integerMod(exponent+127.0, 2.0), sign) * 128.0; - texel = floor(texel); - texel *= 0.003921569; // 1/255 - __ENCODE32_ENDIANNESS__; - return texel; -} - -// https://github.com/gpujs/gpu.js/wiki/Encoder-details -vec4 encode32(float value) { - if (value == 0.0) return vec4(0, 0, 0, 0); - - float exponent; - float mantissa; - vec4 result; - float sgn; - - sgn = step(0.0, -value); - value = abs(value); - - exponent = floor(log2(value)); - - mantissa = value*pow(2.0, -exponent)-1.0; - exponent = exponent+127.0; - result = vec4(0,0,0,0); - - result.a = floor(exponent/2.0); - exponent = exponent - result.a*2.0; - result.a = result.a + 128.0*sgn; - - result.b = floor(mantissa * 128.0); - mantissa = mantissa - result.b / 128.0; - result.b = result.b + exponent*128.0; - - result.g = floor(mantissa*32768.0); - mantissa = mantissa - result.g/32768.0; - - result.r = floor(mantissa*8388608.0); - return result/255.0; -} -// Dragons end here - -int index; -ivec3 threadId; - -ivec3 indexTo3D(int idx, ivec3 texDim) { - int z = int(idx / (texDim.x * texDim.y)); - idx -= z * int(texDim.x * texDim.y); - int y = int(idx / texDim.x); - int x = int(integerMod(idx, texDim.x)); - return ivec3(x, y, z); -} - -float get32(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { - int index = x + texDim.x * (y + texDim.y * z); - int w = texSize.x; - vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; - vec4 texel = texture(tex, st / vec2(texSize)); - return decode32(texel); -} - -float get16(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { - int index = x + (texDim.x * (y + (texDim.y * z))); - int w = texSize.x * 2; - vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; - vec4 texel = texture(tex, st / vec2(texSize.x * 2, texSize.y)); - return decode16(texel, index); -} - -float get8(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { - int index = x + (texDim.x * (y + (texDim.y * z))); - int w = texSize.x * 4; - vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; - vec4 texel = texture(tex, st / vec2(texSize.x * 4, texSize.y)); - return decode8(texel, index); -} - -float getMemoryOptimized32(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { - int index = x + (texDim.x * (y + (texDim.y * z))); - int channel = integerMod(index, 4); - index = index / 4; - int w = texSize.x; - vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; - index = index / 4; - vec4 texel = texture(tex, st / vec2(texSize)); - return texel[channel]; -} - -vec4 getImage2D(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { - int index = x + texDim.x * (y + texDim.y * z); - int w = texSize.x; - vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; - return texture(tex, st / vec2(texSize)); -} - -vec4 getImage3D(sampler2DArray tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { - int index = x + texDim.x * (y + texDim.y * z); - int w = texSize.x; - vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; - return texture(tex, vec3(st / vec2(texSize), z)); -} - -float getFloatFromSampler2D(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { - vec4 result = getImage2D(tex, texSize, texDim, z, y, x); - return result[0]; -} - -vec2 getVec2FromSampler2D(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { - vec4 result = getImage2D(tex, texSize, texDim, z, y, x); - return vec2(result[0], result[1]); -} - -vec2 getMemoryOptimizedVec2(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { - int index = x + texDim.x * (y + texDim.y * z); - int channel = integerMod(index, 2); - index = index / 2; - int w = texSize.x; - vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; - vec4 texel = texture(tex, st / vec2(texSize)); - if (channel == 0) return vec2(texel.r, texel.g); - if (channel == 1) return vec2(texel.b, texel.a); - return vec2(0.0, 0.0); -} - -vec3 getVec3FromSampler2D(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { - vec4 result = getImage2D(tex, texSize, texDim, z, y, x); - return vec3(result[0], result[1], result[2]); -} - -vec3 getMemoryOptimizedVec3(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { - int fieldIndex = 3 * (x + texDim.x * (y + texDim.y * z)); - int vectorIndex = fieldIndex / 4; - int vectorOffset = fieldIndex - vectorIndex * 4; - int readY = vectorIndex / texSize.x; - int readX = vectorIndex - readY * texSize.x; - vec4 tex1 = texture(tex, (vec2(readX, readY) + 0.5) / vec2(texSize)); - - if (vectorOffset == 0) { - return tex1.xyz; - } else if (vectorOffset == 1) { - return tex1.yzw; - } else { - readX++; - if (readX >= texSize.x) { - readX = 0; - readY++; - } - vec4 tex2 = texture(tex, vec2(readX, readY) / vec2(texSize)); - if (vectorOffset == 2) { - return vec3(tex1.z, tex1.w, tex2.x); - } else { - return vec3(tex1.w, tex2.x, tex2.y); - } - } -} - -vec4 getVec4FromSampler2D(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { - return getImage2D(tex, texSize, texDim, z, y, x); -} - -vec4 getMemoryOptimizedVec4(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { - int index = x + texDim.x * (y + texDim.y * z); - int channel = integerMod(index, 2); - int w = texSize.x; - vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; - vec4 texel = texture(tex, st / vec2(texSize)); - return vec4(texel.r, texel.g, texel.b, texel.a); -} - -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); -} - -__INJECTED_NATIVE__; -__MAIN_CONSTANTS__; -__MAIN_ARGUMENTS__; -__KERNEL__; - -void main(void) { - index = int(vTexCoord.s * float(uTexSize.x)) + int(vTexCoord.t * float(uTexSize.y)) * uTexSize.x; - __MAIN_RESULT__; -}`; - -module.exports = { - fragmentShader -}; \ No newline at end of file +// language=GLSL +export const fragmentShader = `#version 300 es +__HEADER__; +__FLOAT_TACTIC_DECLARATION__; +__INT_TACTIC_DECLARATION__; +__SAMPLER_2D_TACTIC_DECLARATION__; +__SAMPLER_2D_ARRAY_TACTIC_DECLARATION__; + +const int LOOP_MAX = __LOOP_MAX__; + +__PLUGINS__; +__CONSTANTS__; + +in vec2 vTexCoord; + +const int BIT_COUNT = 32; +int modi(int x, int y) { + return x - y * (x / y); +} + +int bitwiseOr(int a, int b) { + int result = 0; + int n = 1; + + for (int i = 0; i < BIT_COUNT; i++) { + if ((modi(a, 2) == 1) || (modi(b, 2) == 1)) { + result += n; + } + a = a / 2; + b = b / 2; + n = n * 2; + if(!(a > 0 || b > 0)) { + break; + } + } + return result; +} +int bitwiseXOR(int a, int b) { + int result = 0; + int n = 1; + + for (int i = 0; i < BIT_COUNT; i++) { + if ((modi(a, 2) == 1) != (modi(b, 2) == 1)) { + result += n; + } + a = a / 2; + b = b / 2; + n = n * 2; + if(!(a > 0 || b > 0)) { + break; + } + } + return result; +} +int bitwiseAnd(int a, int b) { + int result = 0; + int n = 1; + for (int i = 0; i < BIT_COUNT; i++) { + if ((modi(a, 2) == 1) && (modi(b, 2) == 1)) { + result += n; + } + a = a / 2; + b = b / 2; + n = n * 2; + if(!(a > 0 && b > 0)) { + break; + } + } + return result; +} +int bitwiseNot(int a) { + int result = 0; + int n = 1; + + for (int i = 0; i < BIT_COUNT; i++) { + if (modi(a, 2) == 0) { + result += n; + } + a = a / 2; + n = n * 2; + } + return result; +} +int bitwiseZeroFillLeftShift(int n, int shift) { + int maxBytes = BIT_COUNT; + for (int i = 0; i < BIT_COUNT; i++) { + if (maxBytes >= n) { + break; + } + maxBytes *= 2; + } + for (int i = 0; i < BIT_COUNT; i++) { + if (i >= shift) { + break; + } + n *= 2; + } + + int result = 0; + int byteVal = 1; + for (int i = 0; i < BIT_COUNT; i++) { + if (i >= maxBytes) break; + if (modi(n, 2) > 0) { result += byteVal; } + n = int(n / 2); + byteVal *= 2; + } + return result; +} + +int bitwiseSignedRightShift(int num, int shifts) { + return int(floor(float(num) / pow(2.0, float(shifts)))); +} + +int bitwiseZeroFillRightShift(int n, int shift) { + int maxBytes = BIT_COUNT; + for (int i = 0; i < BIT_COUNT; i++) { + if (maxBytes >= n) { + break; + } + maxBytes *= 2; + } + for (int i = 0; i < BIT_COUNT; i++) { + if (i >= shift) { + break; + } + n /= 2; + } + int result = 0; + int byteVal = 1; + for (int i = 0; i < BIT_COUNT; i++) { + if (i >= maxBytes) break; + if (modi(n, 2) > 0) { result += byteVal; } + n = int(n / 2); + byteVal *= 2; + } + return result; +} + +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); +} + +float integerMod(float x, float y) { + float res = floor(mod(x, y)); + return res * (res > floor(y) - 1.0 ? 0.0 : 1.0); +} + +int integerMod(int x, int y) { + return x - (y * int(x/y)); +} + +__DIVIDE_WITH_INTEGER_CHECK__; + +// 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 +float decode32(vec4 texel) { + __DECODE32_ENDIANNESS__; + texel *= 255.0; + vec2 gte128; + gte128.x = texel.b >= 128.0 ? 1.0 : 0.0; + gte128.y = texel.a >= 128.0 ? 1.0 : 0.0; + float exponent = 2.0 * texel.a - 127.0 + dot(gte128, MAGIC_VEC); + float res = exp2(round(exponent)); + texel.b = texel.b - 128.0 * gte128.x; + res = dot(texel, SCALE_FACTOR) * exp2(round(exponent-23.0)) + res; + res *= gte128.y * -2.0 + 1.0; + return res; +} + +float decode16(vec4 texel, int index) { + int channel = integerMod(index, 2); + return texel[channel*2] * 255.0 + texel[channel*2 + 1] * 65280.0; +} + +float decode8(vec4 texel, int index) { + int channel = integerMod(index, 4); + return texel[channel] * 255.0; +} + +vec4 legacyEncode32(float f) { + float F = abs(f); + float sign = f < 0.0 ? 1.0 : 0.0; + float exponent = floor(log2(F)); + float mantissa = (exp2(-exponent) * F); + // exponent += floor(log2(mantissa)); + vec4 texel = vec4(F * exp2(23.0-exponent)) * SCALE_FACTOR_INV; + texel.rg = integerMod(texel.rg, 256.0); + texel.b = integerMod(texel.b, 128.0); + texel.a = exponent*0.5 + 63.5; + texel.ba += vec2(integerMod(exponent+127.0, 2.0), sign) * 128.0; + texel = floor(texel); + texel *= 0.003921569; // 1/255 + __ENCODE32_ENDIANNESS__; + return texel; +} + +// https://github.com/gpujs/gpu.js/wiki/Encoder-details +vec4 encode32(float value) { + if (value == 0.0) return vec4(0, 0, 0, 0); + + float exponent; + float mantissa; + vec4 result; + float sgn; + + sgn = step(0.0, -value); + value = abs(value); + + exponent = floor(log2(value)); + + mantissa = value*pow(2.0, -exponent)-1.0; + exponent = exponent+127.0; + result = vec4(0,0,0,0); + + result.a = floor(exponent/2.0); + exponent = exponent - result.a*2.0; + result.a = result.a + 128.0*sgn; + + result.b = floor(mantissa * 128.0); + mantissa = mantissa - result.b / 128.0; + result.b = result.b + exponent*128.0; + + result.g = floor(mantissa*32768.0); + mantissa = mantissa - result.g/32768.0; + + result.r = floor(mantissa*8388608.0); + return result/255.0; +} +// Dragons end here + +int index; +ivec3 threadId; + +ivec3 indexTo3D(int idx, ivec3 texDim) { + int z = int(idx / (texDim.x * texDim.y)); + idx -= z * int(texDim.x * texDim.y); + int y = int(idx / texDim.x); + int x = int(integerMod(idx, texDim.x)); + return ivec3(x, y, z); +} + +float get32(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { + int index = x + texDim.x * (y + texDim.y * z); + int w = texSize.x; + vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; + vec4 texel = texture(tex, st / vec2(texSize)); + return decode32(texel); +} + +float get16(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { + int index = x + (texDim.x * (y + (texDim.y * z))); + int w = texSize.x * 2; + vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; + vec4 texel = texture(tex, st / vec2(texSize.x * 2, texSize.y)); + return decode16(texel, index); +} + +float get8(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { + int index = x + (texDim.x * (y + (texDim.y * z))); + int w = texSize.x * 4; + vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; + vec4 texel = texture(tex, st / vec2(texSize.x * 4, texSize.y)); + return decode8(texel, index); +} + +float getMemoryOptimized32(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { + int index = x + (texDim.x * (y + (texDim.y * z))); + int channel = integerMod(index, 4); + index = index / 4; + int w = texSize.x; + vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; + index = index / 4; + vec4 texel = texture(tex, st / vec2(texSize)); + return texel[channel]; +} + +vec4 getImage2D(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { + int index = x + texDim.x * (y + texDim.y * z); + int w = texSize.x; + vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; + return texture(tex, st / vec2(texSize)); +} + +vec4 getImage3D(sampler2DArray tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { + int index = x + texDim.x * (y + texDim.y * z); + int w = texSize.x; + vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; + return texture(tex, vec3(st / vec2(texSize), z)); +} + +float getFloatFromSampler2D(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { + vec4 result = getImage2D(tex, texSize, texDim, z, y, x); + return result[0]; +} + +vec2 getVec2FromSampler2D(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { + vec4 result = getImage2D(tex, texSize, texDim, z, y, x); + return vec2(result[0], result[1]); +} + +vec2 getMemoryOptimizedVec2(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { + int index = x + texDim.x * (y + texDim.y * z); + int channel = integerMod(index, 2); + index = index / 2; + int w = texSize.x; + vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; + vec4 texel = texture(tex, st / vec2(texSize)); + if (channel == 0) return vec2(texel.r, texel.g); + if (channel == 1) return vec2(texel.b, texel.a); + return vec2(0.0, 0.0); +} + +vec3 getVec3FromSampler2D(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { + vec4 result = getImage2D(tex, texSize, texDim, z, y, x); + return vec3(result[0], result[1], result[2]); +} + +vec3 getMemoryOptimizedVec3(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { + int fieldIndex = 3 * (x + texDim.x * (y + texDim.y * z)); + int vectorIndex = fieldIndex / 4; + int vectorOffset = fieldIndex - vectorIndex * 4; + int readY = vectorIndex / texSize.x; + int readX = vectorIndex - readY * texSize.x; + vec4 tex1 = texture(tex, (vec2(readX, readY) + 0.5) / vec2(texSize)); + + if (vectorOffset == 0) { + return tex1.xyz; + } else if (vectorOffset == 1) { + return tex1.yzw; + } else { + readX++; + if (readX >= texSize.x) { + readX = 0; + readY++; + } + vec4 tex2 = texture(tex, vec2(readX, readY) / vec2(texSize)); + if (vectorOffset == 2) { + return vec3(tex1.z, tex1.w, tex2.x); + } else { + return vec3(tex1.w, tex2.x, tex2.y); + } + } +} + +vec4 getVec4FromSampler2D(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { + return getImage2D(tex, texSize, texDim, z, y, x); +} + +vec4 getMemoryOptimizedVec4(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { + int index = x + texDim.x * (y + texDim.y * z); + int channel = integerMod(index, 2); + int w = texSize.x; + vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; + vec4 texel = texture(tex, st / vec2(texSize)); + return vec4(texel.r, texel.g, texel.b, texel.a); +} + +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); +} + +__INJECTED_NATIVE__; +__MAIN_CONSTANTS__; +__MAIN_ARGUMENTS__; +__KERNEL__; + +void main(void) { + index = int(vTexCoord.s * float(uTexSize.x)) + int(vTexCoord.t * float(uTexSize.y)) * uTexSize.x; + __MAIN_RESULT__; +}`; diff --git a/src/backend/web-gl2/function-node.js b/src/backend/web-gl2/function-node.js index ccf35af4..7db91258 100644 --- a/src/backend/web-gl2/function-node.js +++ b/src/backend/web-gl2/function-node.js @@ -1,45 +1,41 @@ -const { WebGLFunctionNode } = require('../web-gl/function-node'); - -/** - * @class WebGL2FunctionNode - * @desc [INTERNAL] Takes in a function node, and does all the AST voodoo required to toString its respective webGL code. - * @extends WebGLFunctionNode - * @returns the converted webGL function string - */ -class WebGL2FunctionNode extends WebGLFunctionNode { - - /** - * @desc Parses the abstract syntax tree for *identifier* expression - * @param {Object} idtNode - An ast Node - * @param {Array} retArr - return array string - * @returns {Array} the append retArr - */ - astIdentifierExpression(idtNode, retArr) { - if (idtNode.type !== 'Identifier') { - throw this.astErrorOutput( - 'IdentifierExpression - not an Identifier', - idtNode - ); - } - - const type = this.getType(idtNode); - - if (idtNode.name === 'Infinity') { - retArr.push('intBitsToFloat(2139095039)'); - } else if (type === 'Boolean') { - if (this.argumentNames.indexOf(idtNode.name) > -1) { - retArr.push(`bool(user_${idtNode.name})`); - } else { - retArr.push(`user_${idtNode.name}`); - } - } else { - retArr.push(`user_${idtNode.name}`); - } - - return retArr; - } -} - -module.exports = { - WebGL2FunctionNode -}; \ No newline at end of file +import { WebGLFunctionNode } from '../web-gl/function-node'; + +/** + * @class WebGL2FunctionNode + * @desc [INTERNAL] Takes in a function node, and does all the AST voodoo required to toString its respective webGL code. + * @extends WebGLFunctionNode + * @returns the converted webGL function string + */ +export class WebGL2FunctionNode extends WebGLFunctionNode { + + /** + * @desc Parses the abstract syntax tree for *identifier* expression + * @param {Object} idtNode - An ast Node + * @param {Array} retArr - return array string + * @returns {Array} the append retArr + */ + astIdentifierExpression(idtNode, retArr) { + if (idtNode.type !== 'Identifier') { + throw this.astErrorOutput( + 'IdentifierExpression - not an Identifier', + idtNode + ); + } + + const type = this.getType(idtNode); + + if (idtNode.name === 'Infinity') { + retArr.push('intBitsToFloat(2139095039)'); + } else if (type === 'Boolean') { + if (this.argumentNames.indexOf(idtNode.name) > -1) { + retArr.push(`bool(user_${idtNode.name})`); + } else { + retArr.push(`user_${idtNode.name}`); + } + } else { + retArr.push(`user_${idtNode.name}`); + } + + return retArr; + } +} diff --git a/src/backend/web-gl2/kernel-value-maps.js b/src/backend/web-gl2/kernel-value-maps.js index 357fa824..67e8a856 100644 --- a/src/backend/web-gl2/kernel-value-maps.js +++ b/src/backend/web-gl2/kernel-value-maps.js @@ -1,189 +1,184 @@ -const { WebGL2KernelValueBoolean } = require('./kernel-value/boolean'); -const { WebGL2KernelValueFloat } = require('./kernel-value/float'); -const { WebGL2KernelValueInteger } = require('./kernel-value/integer'); - -const { WebGL2KernelValueHTMLImage } = require('./kernel-value/html-image'); -const { WebGL2KernelValueDynamicHTMLImage } = require('./kernel-value/dynamic-html-image'); - -const { WebGL2KernelValueHTMLImageArray } = require('./kernel-value/html-image-array'); -const { WebGL2KernelValueDynamicHTMLImageArray } = require('./kernel-value/dynamic-html-image-array'); - -const { WebGL2KernelValueHTMLVideo } = require('./kernel-value/html-video'); -const { WebGL2KernelValueDynamicHTMLVideo } = require('./kernel-value/dynamic-html-video'); - -const { WebGL2KernelValueSingleInput } = require('./kernel-value/single-input'); -const { WebGL2KernelValueDynamicSingleInput } = require('./kernel-value/dynamic-single-input'); - -const { WebGL2KernelValueUnsignedInput } = require('./kernel-value/unsigned-input'); -const { WebGL2KernelValueDynamicUnsignedInput } = require('./kernel-value/dynamic-unsigned-input'); - -const { WebGL2KernelValueMemoryOptimizedNumberTexture } = require('./kernel-value/memory-optimized-number-texture'); -const { WebGL2KernelValueDynamicMemoryOptimizedNumberTexture } = require('./kernel-value/dynamic-memory-optimized-number-texture'); - -const { WebGL2KernelValueNumberTexture } = require('./kernel-value/number-texture'); -const { WebGL2KernelValueDynamicNumberTexture } = require('./kernel-value/dynamic-number-texture'); - -const { WebGL2KernelValueSingleArray } = require('./kernel-value/single-array'); -const { WebGL2KernelValueDynamicSingleArray } = require('./kernel-value/dynamic-single-array'); - -const { WebGL2KernelValueSingleArray1DI } = require('./kernel-value/single-array1d-i'); -const { WebGL2KernelValueDynamicSingleArray1DI } = require('./kernel-value/dynamic-single-array1d-i'); - -const { WebGL2KernelValueSingleArray2DI } = require('./kernel-value/single-array2d-i'); -const { WebGL2KernelValueDynamicSingleArray2DI } = require('./kernel-value/dynamic-single-array2d-i'); - -const { WebGL2KernelValueSingleArray3DI } = require('./kernel-value/single-array3d-i'); -const { WebGL2KernelValueDynamicSingleArray3DI } = require('./kernel-value/dynamic-single-array3d-i'); - -const { WebGL2KernelValueSingleArray2 } = require('./kernel-value/single-array2'); -const { WebGL2KernelValueSingleArray3 } = require('./kernel-value/single-array3'); -const { WebGL2KernelValueSingleArray4 } = require('./kernel-value/single-array4'); - -const { WebGL2KernelValueUnsignedArray } = require('./kernel-value/unsigned-array'); -const { WebGL2KernelValueDynamicUnsignedArray } = require('./kernel-value/dynamic-unsigned-array'); - -const kernelValueMaps = { - unsigned: { - dynamic: { - 'Boolean': WebGL2KernelValueBoolean, - 'Integer': WebGL2KernelValueInteger, - 'Float': WebGL2KernelValueFloat, - 'Array': WebGL2KernelValueDynamicUnsignedArray, - 'Array(2)': false, - 'Array(3)': false, - 'Array(4)': false, - 'Array1D(2)': false, - 'Array1D(3)': false, - 'Array1D(4)': false, - 'Array2D(2)': false, - 'Array2D(3)': false, - 'Array2D(4)': false, - 'Array3D(2)': false, - 'Array3D(3)': false, - 'Array3D(4)': false, - 'Input': WebGL2KernelValueDynamicUnsignedInput, - 'NumberTexture': WebGL2KernelValueDynamicNumberTexture, - 'ArrayTexture(1)': WebGL2KernelValueDynamicNumberTexture, - 'ArrayTexture(2)': WebGL2KernelValueDynamicNumberTexture, - 'ArrayTexture(3)': WebGL2KernelValueDynamicNumberTexture, - 'ArrayTexture(4)': WebGL2KernelValueDynamicNumberTexture, - 'MemoryOptimizedNumberTexture': WebGL2KernelValueDynamicMemoryOptimizedNumberTexture, - 'HTMLImage': WebGL2KernelValueDynamicHTMLImage, - 'HTMLImageArray': WebGL2KernelValueDynamicHTMLImageArray, - 'HTMLVideo': WebGL2KernelValueDynamicHTMLVideo, - }, - static: { - 'Boolean': WebGL2KernelValueBoolean, - 'Float': WebGL2KernelValueFloat, - 'Integer': WebGL2KernelValueInteger, - 'Array': WebGL2KernelValueUnsignedArray, - 'Array(2)': false, - 'Array(3)': false, - 'Array(4)': false, - 'Array1D(2)': false, - 'Array1D(3)': false, - 'Array1D(4)': false, - 'Array2D(2)': false, - 'Array2D(3)': false, - 'Array2D(4)': false, - 'Array3D(2)': false, - 'Array3D(3)': false, - 'Array3D(4)': false, - 'Input': WebGL2KernelValueUnsignedInput, - 'NumberTexture': WebGL2KernelValueNumberTexture, - 'ArrayTexture(1)': WebGL2KernelValueNumberTexture, - 'ArrayTexture(2)': WebGL2KernelValueNumberTexture, - 'ArrayTexture(3)': WebGL2KernelValueNumberTexture, - 'ArrayTexture(4)': WebGL2KernelValueNumberTexture, - 'MemoryOptimizedNumberTexture': WebGL2KernelValueDynamicMemoryOptimizedNumberTexture, - 'HTMLImage': WebGL2KernelValueHTMLImage, - 'HTMLImageArray': WebGL2KernelValueHTMLImageArray, - 'HTMLVideo': WebGL2KernelValueHTMLVideo, - } - }, - single: { - dynamic: { - 'Boolean': WebGL2KernelValueBoolean, - 'Integer': WebGL2KernelValueInteger, - 'Float': WebGL2KernelValueFloat, - 'Array': WebGL2KernelValueDynamicSingleArray, - 'Array(2)': WebGL2KernelValueSingleArray2, - 'Array(3)': WebGL2KernelValueSingleArray3, - 'Array(4)': WebGL2KernelValueSingleArray4, - 'Array1D(2)': WebGL2KernelValueDynamicSingleArray1DI, - 'Array1D(3)': WebGL2KernelValueDynamicSingleArray1DI, - 'Array1D(4)': WebGL2KernelValueDynamicSingleArray1DI, - 'Array2D(2)': WebGL2KernelValueDynamicSingleArray2DI, - 'Array2D(3)': WebGL2KernelValueDynamicSingleArray2DI, - 'Array2D(4)': WebGL2KernelValueDynamicSingleArray2DI, - 'Array3D(2)': WebGL2KernelValueDynamicSingleArray3DI, - 'Array3D(3)': WebGL2KernelValueDynamicSingleArray3DI, - 'Array3D(4)': WebGL2KernelValueDynamicSingleArray3DI, - 'Input': WebGL2KernelValueDynamicSingleInput, - 'NumberTexture': WebGL2KernelValueDynamicNumberTexture, - 'ArrayTexture(1)': WebGL2KernelValueDynamicNumberTexture, - 'ArrayTexture(2)': WebGL2KernelValueDynamicNumberTexture, - 'ArrayTexture(3)': WebGL2KernelValueDynamicNumberTexture, - 'ArrayTexture(4)': WebGL2KernelValueDynamicNumberTexture, - 'MemoryOptimizedNumberTexture': WebGL2KernelValueDynamicMemoryOptimizedNumberTexture, - 'HTMLImage': WebGL2KernelValueDynamicHTMLImage, - 'HTMLImageArray': WebGL2KernelValueDynamicHTMLImageArray, - 'HTMLVideo': WebGL2KernelValueDynamicHTMLVideo, - }, - static: { - 'Boolean': WebGL2KernelValueBoolean, - 'Float': WebGL2KernelValueFloat, - 'Integer': WebGL2KernelValueInteger, - 'Array': WebGL2KernelValueSingleArray, - 'Array(2)': WebGL2KernelValueSingleArray2, - 'Array(3)': WebGL2KernelValueSingleArray3, - 'Array(4)': WebGL2KernelValueSingleArray4, - 'Array1D(2)': WebGL2KernelValueSingleArray1DI, - 'Array1D(3)': WebGL2KernelValueSingleArray1DI, - 'Array1D(4)': WebGL2KernelValueSingleArray1DI, - 'Array2D(2)': WebGL2KernelValueSingleArray2DI, - 'Array2D(3)': WebGL2KernelValueSingleArray2DI, - 'Array2D(4)': WebGL2KernelValueSingleArray2DI, - 'Array3D(2)': WebGL2KernelValueSingleArray3DI, - 'Array3D(3)': WebGL2KernelValueSingleArray3DI, - 'Array3D(4)': WebGL2KernelValueSingleArray3DI, - 'Input': WebGL2KernelValueSingleInput, - 'NumberTexture': WebGL2KernelValueNumberTexture, - 'ArrayTexture(1)': WebGL2KernelValueNumberTexture, - 'ArrayTexture(2)': WebGL2KernelValueNumberTexture, - 'ArrayTexture(3)': WebGL2KernelValueNumberTexture, - 'ArrayTexture(4)': WebGL2KernelValueNumberTexture, - 'MemoryOptimizedNumberTexture': WebGL2KernelValueMemoryOptimizedNumberTexture, - 'HTMLImage': WebGL2KernelValueHTMLImage, - 'HTMLImageArray': WebGL2KernelValueHTMLImageArray, - 'HTMLVideo': WebGL2KernelValueHTMLVideo, - } - }, -}; - -function lookupKernelValueType(type, dynamic, precision, value) { - if (!type) { - throw new Error('type missing'); - } - if (!dynamic) { - throw new Error('dynamic missing'); - } - if (!precision) { - throw new Error('precision missing'); - } - if (value.type) { - type = value.type; - } - const types = kernelValueMaps[precision][dynamic]; - if (types[type] === false) { - return null; - } else if (types[type] === undefined) { - throw new Error(`Could not find a KernelValue for ${ type }`); - } - return types[type]; -} - -module.exports = { - kernelValueMaps, - lookupKernelValueType -}; \ No newline at end of file +import { WebGL2KernelValueBoolean } from './kernel-value/boolean'; +import { WebGL2KernelValueFloat } from './kernel-value/float'; +import { WebGL2KernelValueInteger } from './kernel-value/integer'; + +import { WebGL2KernelValueHTMLImage } from './kernel-value/html-image'; +import { WebGL2KernelValueDynamicHTMLImage } from './kernel-value/dynamic-html-image'; + +import { WebGL2KernelValueHTMLImageArray } from './kernel-value/html-image-array'; +import { WebGL2KernelValueDynamicHTMLImageArray } from './kernel-value/dynamic-html-image-array'; + +import { WebGL2KernelValueHTMLVideo } from './kernel-value/html-video'; +import { WebGL2KernelValueDynamicHTMLVideo } from './kernel-value/dynamic-html-video'; + +import { WebGL2KernelValueSingleInput } from './kernel-value/single-input'; +import { WebGL2KernelValueDynamicSingleInput } from './kernel-value/dynamic-single-input'; + +import { WebGL2KernelValueUnsignedInput } from './kernel-value/unsigned-input'; +import { WebGL2KernelValueDynamicUnsignedInput } from './kernel-value/dynamic-unsigned-input'; + +import { WebGL2KernelValueMemoryOptimizedNumberTexture } from './kernel-value/memory-optimized-number-texture'; +import { WebGL2KernelValueDynamicMemoryOptimizedNumberTexture } from './kernel-value/dynamic-memory-optimized-number-texture'; + +import { WebGL2KernelValueNumberTexture } from './kernel-value/number-texture'; +import { WebGL2KernelValueDynamicNumberTexture } from './kernel-value/dynamic-number-texture'; + +import { WebGL2KernelValueSingleArray } from './kernel-value/single-array'; +import { WebGL2KernelValueDynamicSingleArray } from './kernel-value/dynamic-single-array'; + +import { WebGL2KernelValueSingleArray1DI } from './kernel-value/single-array1d-i'; +import { WebGL2KernelValueDynamicSingleArray1DI } from './kernel-value/dynamic-single-array1d-i'; + +import { WebGL2KernelValueSingleArray2DI } from './kernel-value/single-array2d-i'; +import { WebGL2KernelValueDynamicSingleArray2DI } from './kernel-value/dynamic-single-array2d-i'; + +import { WebGL2KernelValueSingleArray3DI } from './kernel-value/single-array3d-i'; +import { WebGL2KernelValueDynamicSingleArray3DI } from './kernel-value/dynamic-single-array3d-i'; + +import { WebGL2KernelValueSingleArray2 } from './kernel-value/single-array2'; +import { WebGL2KernelValueSingleArray3 } from './kernel-value/single-array3'; +import { WebGL2KernelValueSingleArray4 } from './kernel-value/single-array4'; + +import { WebGL2KernelValueUnsignedArray } from './kernel-value/unsigned-array'; +import { WebGL2KernelValueDynamicUnsignedArray } from './kernel-value/dynamic-unsigned-array'; + +export const kernelValueMaps = { + unsigned: { + dynamic: { + 'Boolean': WebGL2KernelValueBoolean, + 'Integer': WebGL2KernelValueInteger, + 'Float': WebGL2KernelValueFloat, + 'Array': WebGL2KernelValueDynamicUnsignedArray, + 'Array(2)': false, + 'Array(3)': false, + 'Array(4)': false, + 'Array1D(2)': false, + 'Array1D(3)': false, + 'Array1D(4)': false, + 'Array2D(2)': false, + 'Array2D(3)': false, + 'Array2D(4)': false, + 'Array3D(2)': false, + 'Array3D(3)': false, + 'Array3D(4)': false, + 'Input': WebGL2KernelValueDynamicUnsignedInput, + 'NumberTexture': WebGL2KernelValueDynamicNumberTexture, + 'ArrayTexture(1)': WebGL2KernelValueDynamicNumberTexture, + 'ArrayTexture(2)': WebGL2KernelValueDynamicNumberTexture, + 'ArrayTexture(3)': WebGL2KernelValueDynamicNumberTexture, + 'ArrayTexture(4)': WebGL2KernelValueDynamicNumberTexture, + 'MemoryOptimizedNumberTexture': WebGL2KernelValueDynamicMemoryOptimizedNumberTexture, + 'HTMLImage': WebGL2KernelValueDynamicHTMLImage, + 'HTMLImageArray': WebGL2KernelValueDynamicHTMLImageArray, + 'HTMLVideo': WebGL2KernelValueDynamicHTMLVideo, + }, + static: { + 'Boolean': WebGL2KernelValueBoolean, + 'Float': WebGL2KernelValueFloat, + 'Integer': WebGL2KernelValueInteger, + 'Array': WebGL2KernelValueUnsignedArray, + 'Array(2)': false, + 'Array(3)': false, + 'Array(4)': false, + 'Array1D(2)': false, + 'Array1D(3)': false, + 'Array1D(4)': false, + 'Array2D(2)': false, + 'Array2D(3)': false, + 'Array2D(4)': false, + 'Array3D(2)': false, + 'Array3D(3)': false, + 'Array3D(4)': false, + 'Input': WebGL2KernelValueUnsignedInput, + 'NumberTexture': WebGL2KernelValueNumberTexture, + 'ArrayTexture(1)': WebGL2KernelValueNumberTexture, + 'ArrayTexture(2)': WebGL2KernelValueNumberTexture, + 'ArrayTexture(3)': WebGL2KernelValueNumberTexture, + 'ArrayTexture(4)': WebGL2KernelValueNumberTexture, + 'MemoryOptimizedNumberTexture': WebGL2KernelValueDynamicMemoryOptimizedNumberTexture, + 'HTMLImage': WebGL2KernelValueHTMLImage, + 'HTMLImageArray': WebGL2KernelValueHTMLImageArray, + 'HTMLVideo': WebGL2KernelValueHTMLVideo, + } + }, + single: { + dynamic: { + 'Boolean': WebGL2KernelValueBoolean, + 'Integer': WebGL2KernelValueInteger, + 'Float': WebGL2KernelValueFloat, + 'Array': WebGL2KernelValueDynamicSingleArray, + 'Array(2)': WebGL2KernelValueSingleArray2, + 'Array(3)': WebGL2KernelValueSingleArray3, + 'Array(4)': WebGL2KernelValueSingleArray4, + 'Array1D(2)': WebGL2KernelValueDynamicSingleArray1DI, + 'Array1D(3)': WebGL2KernelValueDynamicSingleArray1DI, + 'Array1D(4)': WebGL2KernelValueDynamicSingleArray1DI, + 'Array2D(2)': WebGL2KernelValueDynamicSingleArray2DI, + 'Array2D(3)': WebGL2KernelValueDynamicSingleArray2DI, + 'Array2D(4)': WebGL2KernelValueDynamicSingleArray2DI, + 'Array3D(2)': WebGL2KernelValueDynamicSingleArray3DI, + 'Array3D(3)': WebGL2KernelValueDynamicSingleArray3DI, + 'Array3D(4)': WebGL2KernelValueDynamicSingleArray3DI, + 'Input': WebGL2KernelValueDynamicSingleInput, + 'NumberTexture': WebGL2KernelValueDynamicNumberTexture, + 'ArrayTexture(1)': WebGL2KernelValueDynamicNumberTexture, + 'ArrayTexture(2)': WebGL2KernelValueDynamicNumberTexture, + 'ArrayTexture(3)': WebGL2KernelValueDynamicNumberTexture, + 'ArrayTexture(4)': WebGL2KernelValueDynamicNumberTexture, + 'MemoryOptimizedNumberTexture': WebGL2KernelValueDynamicMemoryOptimizedNumberTexture, + 'HTMLImage': WebGL2KernelValueDynamicHTMLImage, + 'HTMLImageArray': WebGL2KernelValueDynamicHTMLImageArray, + 'HTMLVideo': WebGL2KernelValueDynamicHTMLVideo, + }, + static: { + 'Boolean': WebGL2KernelValueBoolean, + 'Float': WebGL2KernelValueFloat, + 'Integer': WebGL2KernelValueInteger, + 'Array': WebGL2KernelValueSingleArray, + 'Array(2)': WebGL2KernelValueSingleArray2, + 'Array(3)': WebGL2KernelValueSingleArray3, + 'Array(4)': WebGL2KernelValueSingleArray4, + 'Array1D(2)': WebGL2KernelValueSingleArray1DI, + 'Array1D(3)': WebGL2KernelValueSingleArray1DI, + 'Array1D(4)': WebGL2KernelValueSingleArray1DI, + 'Array2D(2)': WebGL2KernelValueSingleArray2DI, + 'Array2D(3)': WebGL2KernelValueSingleArray2DI, + 'Array2D(4)': WebGL2KernelValueSingleArray2DI, + 'Array3D(2)': WebGL2KernelValueSingleArray3DI, + 'Array3D(3)': WebGL2KernelValueSingleArray3DI, + 'Array3D(4)': WebGL2KernelValueSingleArray3DI, + 'Input': WebGL2KernelValueSingleInput, + 'NumberTexture': WebGL2KernelValueNumberTexture, + 'ArrayTexture(1)': WebGL2KernelValueNumberTexture, + 'ArrayTexture(2)': WebGL2KernelValueNumberTexture, + 'ArrayTexture(3)': WebGL2KernelValueNumberTexture, + 'ArrayTexture(4)': WebGL2KernelValueNumberTexture, + 'MemoryOptimizedNumberTexture': WebGL2KernelValueMemoryOptimizedNumberTexture, + 'HTMLImage': WebGL2KernelValueHTMLImage, + 'HTMLImageArray': WebGL2KernelValueHTMLImageArray, + 'HTMLVideo': WebGL2KernelValueHTMLVideo, + } + }, +}; + +export function lookupKernelValueType(type, dynamic, precision, value) { + if (!type) { + throw new Error('type missing'); + } + if (!dynamic) { + throw new Error('dynamic missing'); + } + if (!precision) { + throw new Error('precision missing'); + } + if (value.type) { + type = value.type; + } + const types = kernelValueMaps[precision][dynamic]; + if (types[type] === false) { + return null; + } else if (types[type] === undefined) { + throw new Error(`Could not find a KernelValue for ${ type }`); + } + return types[type]; +} diff --git a/src/backend/web-gl2/kernel-value/boolean.js b/src/backend/web-gl2/kernel-value/boolean.js index c91a61a9..3f765060 100644 --- a/src/backend/web-gl2/kernel-value/boolean.js +++ b/src/backend/web-gl2/kernel-value/boolean.js @@ -1,7 +1,3 @@ -const { WebGLKernelValueBoolean } = require('../../web-gl/kernel-value/boolean'); - -class WebGL2KernelValueBoolean extends WebGLKernelValueBoolean {} - -module.exports = { - WebGL2KernelValueBoolean -}; \ No newline at end of file +import { WebGLKernelValueBoolean } from '../../web-gl/kernel-value/boolean'; + +export class WebGL2KernelValueBoolean extends WebGLKernelValueBoolean {} diff --git a/src/backend/web-gl2/kernel-value/dynamic-html-image-array.js b/src/backend/web-gl2/kernel-value/dynamic-html-image-array.js index dab14341..c96ef1ce 100644 --- a/src/backend/web-gl2/kernel-value/dynamic-html-image-array.js +++ b/src/backend/web-gl2/kernel-value/dynamic-html-image-array.js @@ -1,26 +1,22 @@ -const { WebGL2KernelValueHTMLImageArray } = require('./html-image-array'); - -class WebGL2KernelValueDynamicHTMLImageArray extends WebGL2KernelValueHTMLImageArray { - getSource() { - const variablePrecision = this.getVariablePrecisionString(); - return utils.linesToString([ - `uniform ${ variablePrecision } sampler2DArray ${this.id}`, - `uniform ${ variablePrecision } ivec2 ${this.sizeId}`, - `uniform ${ variablePrecision } ivec3 ${this.dimensionsId}`, - ]); - } - - updateValue(images) { - const { width, height } = images[0]; - this.checkSize(width, height); - this.dimensions = [width, height, images.length]; - this.textureSize = [width, height]; - this.kernel.setUniform3iv(this.dimensionsId, this.dimensions); - this.kernel.setUniform2iv(this.sizeId, this.textureSize); - super.updateValue(images); - } -} - -module.exports = { - WebGL2KernelValueDynamicHTMLImageArray -}; \ No newline at end of file +import { WebGL2KernelValueHTMLImageArray } from './html-image-array'; + +export class WebGL2KernelValueDynamicHTMLImageArray extends WebGL2KernelValueHTMLImageArray { + getSource() { + const variablePrecision = this.getVariablePrecisionString(); + return utils.linesToString([ + `uniform ${ variablePrecision } sampler2DArray ${this.id}`, + `uniform ${ variablePrecision } ivec2 ${this.sizeId}`, + `uniform ${ variablePrecision } ivec3 ${this.dimensionsId}`, + ]); + } + + updateValue(images) { + const { width, height } = images[0]; + this.checkSize(width, height); + this.dimensions = [width, height, images.length]; + this.textureSize = [width, height]; + this.kernel.setUniform3iv(this.dimensionsId, this.dimensions); + this.kernel.setUniform2iv(this.sizeId, this.textureSize); + super.updateValue(images); + } +} diff --git a/src/backend/web-gl2/kernel-value/dynamic-html-image.js b/src/backend/web-gl2/kernel-value/dynamic-html-image.js index f1ba34eb..352bfe56 100644 --- a/src/backend/web-gl2/kernel-value/dynamic-html-image.js +++ b/src/backend/web-gl2/kernel-value/dynamic-html-image.js @@ -1,17 +1,13 @@ -const { utils } = require('../../../utils'); -const { WebGLKernelValueDynamicHTMLImage } = require('../../web-gl/kernel-value/dynamic-html-image'); - -class WebGL2KernelValueDynamicHTMLImage extends WebGLKernelValueDynamicHTMLImage { - getSource() { - const variablePrecision = this.getVariablePrecisionString(); - return utils.linesToString([ - `uniform ${ variablePrecision } sampler2D ${this.id}`, - `uniform ${ variablePrecision } ivec2 ${this.sizeId}`, - `uniform ${ variablePrecision } ivec3 ${this.dimensionsId}`, - ]); - } -} - -module.exports = { - WebGL2KernelValueDynamicHTMLImage -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { WebGLKernelValueDynamicHTMLImage } from '../../web-gl/kernel-value/dynamic-html-image'; + +export class WebGL2KernelValueDynamicHTMLImage extends WebGLKernelValueDynamicHTMLImage { + getSource() { + const variablePrecision = this.getVariablePrecisionString(); + return utils.linesToString([ + `uniform ${ variablePrecision } sampler2D ${this.id}`, + `uniform ${ variablePrecision } ivec2 ${this.sizeId}`, + `uniform ${ variablePrecision } ivec3 ${this.dimensionsId}`, + ]); + } +} diff --git a/src/backend/web-gl2/kernel-value/dynamic-html-video.js b/src/backend/web-gl2/kernel-value/dynamic-html-video.js index 00f723d1..81b617f6 100644 --- a/src/backend/web-gl2/kernel-value/dynamic-html-video.js +++ b/src/backend/web-gl2/kernel-value/dynamic-html-video.js @@ -1,8 +1,3 @@ -const { utils } = require('../../../utils'); -const { WebGL2KernelValueDynamicHTMLImage } = require('./dynamic-html-image'); - -class WebGL2KernelValueDynamicHTMLVideo extends WebGL2KernelValueDynamicHTMLImage {} - -module.exports = { - WebGL2KernelValueDynamicHTMLVideo -}; \ No newline at end of file +import { WebGL2KernelValueDynamicHTMLImage } from './dynamic-html-image'; + +export class WebGL2KernelValueDynamicHTMLVideo extends WebGL2KernelValueDynamicHTMLImage {} diff --git a/src/backend/web-gl2/kernel-value/dynamic-memory-optimized-number-texture.js b/src/backend/web-gl2/kernel-value/dynamic-memory-optimized-number-texture.js index 5e3cff4b..e0f5d50e 100644 --- a/src/backend/web-gl2/kernel-value/dynamic-memory-optimized-number-texture.js +++ b/src/backend/web-gl2/kernel-value/dynamic-memory-optimized-number-texture.js @@ -1,16 +1,12 @@ -const { utils } = require('../../../utils'); -const { WebGLKernelValueDynamicMemoryOptimizedNumberTexture } = require('../../web-gl/kernel-value/dynamic-memory-optimized-number-texture'); - -class WebGL2KernelValueDynamicMemoryOptimizedNumberTexture extends WebGLKernelValueDynamicMemoryOptimizedNumberTexture { - getSource() { - return utils.linesToString([ - `uniform sampler2D ${this.id}`, - `uniform ivec2 ${this.sizeId}`, - `uniform ivec3 ${this.dimensionsId}`, - ]); - } -} - -module.exports = { - WebGL2KernelValueDynamicMemoryOptimizedNumberTexture -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { WebGLKernelValueDynamicMemoryOptimizedNumberTexture } from '../../web-gl/kernel-value/dynamic-memory-optimized-number-texture'; + +export class WebGL2KernelValueDynamicMemoryOptimizedNumberTexture extends WebGLKernelValueDynamicMemoryOptimizedNumberTexture { + getSource() { + return utils.linesToString([ + `uniform sampler2D ${this.id}`, + `uniform ivec2 ${this.sizeId}`, + `uniform ivec3 ${this.dimensionsId}`, + ]); + } +} diff --git a/src/backend/web-gl2/kernel-value/dynamic-number-texture.js b/src/backend/web-gl2/kernel-value/dynamic-number-texture.js index e3820a56..443b7425 100644 --- a/src/backend/web-gl2/kernel-value/dynamic-number-texture.js +++ b/src/backend/web-gl2/kernel-value/dynamic-number-texture.js @@ -1,17 +1,13 @@ -const { utils } = require('../../../utils'); -const { WebGLKernelValueDynamicNumberTexture } = require('../../web-gl/kernel-value/dynamic-number-texture'); - -class WebGL2KernelValueDynamicNumberTexture extends WebGLKernelValueDynamicNumberTexture { - getSource() { - const variablePrecision = this.getVariablePrecisionString(); - return utils.linesToString([ - `uniform ${ variablePrecision } sampler2D ${this.id}`, - `uniform ${ variablePrecision } ivec2 ${this.sizeId}`, - `uniform ${ variablePrecision } ivec3 ${this.dimensionsId}`, - ]); - } -} - -module.exports = { - WebGL2KernelValueDynamicNumberTexture -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { WebGLKernelValueDynamicNumberTexture } from '../../web-gl/kernel-value/dynamic-number-texture'; + +export class WebGL2KernelValueDynamicNumberTexture extends WebGLKernelValueDynamicNumberTexture { + getSource() { + const variablePrecision = this.getVariablePrecisionString(); + return utils.linesToString([ + `uniform ${ variablePrecision } sampler2D ${this.id}`, + `uniform ${ variablePrecision } ivec2 ${this.sizeId}`, + `uniform ${ variablePrecision } ivec3 ${this.dimensionsId}`, + ]); + } +} diff --git a/src/backend/web-gl2/kernel-value/dynamic-single-array.js b/src/backend/web-gl2/kernel-value/dynamic-single-array.js index 3356cdbf..9374fc2a 100644 --- a/src/backend/web-gl2/kernel-value/dynamic-single-array.js +++ b/src/backend/web-gl2/kernel-value/dynamic-single-array.js @@ -1,28 +1,24 @@ -const { utils } = require('../../../utils'); -const { WebGL2KernelValueSingleArray } = require('../../web-gl2/kernel-value/single-array'); - -class WebGL2KernelValueDynamicSingleArray extends WebGL2KernelValueSingleArray { - getSource() { - const variablePrecision = this.getVariablePrecisionString(); - return utils.linesToString([ - `uniform ${ variablePrecision } sampler2D ${this.id}`, - `uniform ${ variablePrecision } ivec2 ${this.sizeId}`, - `uniform ${ variablePrecision } ivec3 ${this.dimensionsId}`, - ]); - } - - updateValue(value) { - this.dimensions = utils.getDimensions(value, true); - this.textureSize = utils.getMemoryOptimizedFloatTextureSize(this.dimensions, this.bitRatio); - this.uploadArrayLength = this.textureSize[0] * this.textureSize[1] * this.bitRatio; - this.checkSize(this.textureSize[0] * this.bitRatio, this.textureSize[1] * this.bitRatio); - this.uploadValue = new Float32Array(this.uploadArrayLength); - this.kernel.setUniform3iv(this.dimensionsId, this.dimensions); - this.kernel.setUniform2iv(this.sizeId, this.textureSize); - super.updateValue(value); - } -} - -module.exports = { - WebGL2KernelValueDynamicSingleArray -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { WebGL2KernelValueSingleArray } from '../../web-gl2/kernel-value/single-array'; + +export class WebGL2KernelValueDynamicSingleArray extends WebGL2KernelValueSingleArray { + getSource() { + const variablePrecision = this.getVariablePrecisionString(); + return utils.linesToString([ + `uniform ${ variablePrecision } sampler2D ${this.id}`, + `uniform ${ variablePrecision } ivec2 ${this.sizeId}`, + `uniform ${ variablePrecision } ivec3 ${this.dimensionsId}`, + ]); + } + + updateValue(value) { + this.dimensions = utils.getDimensions(value, true); + this.textureSize = utils.getMemoryOptimizedFloatTextureSize(this.dimensions, this.bitRatio); + this.uploadArrayLength = this.textureSize[0] * this.textureSize[1] * this.bitRatio; + this.checkSize(this.textureSize[0] * this.bitRatio, this.textureSize[1] * this.bitRatio); + this.uploadValue = new Float32Array(this.uploadArrayLength); + this.kernel.setUniform3iv(this.dimensionsId, this.dimensions); + this.kernel.setUniform2iv(this.sizeId, this.textureSize); + super.updateValue(value); + } +} diff --git a/src/backend/web-gl2/kernel-value/dynamic-single-array1d-i.js b/src/backend/web-gl2/kernel-value/dynamic-single-array1d-i.js index 45203f92..344b79d1 100644 --- a/src/backend/web-gl2/kernel-value/dynamic-single-array1d-i.js +++ b/src/backend/web-gl2/kernel-value/dynamic-single-array1d-i.js @@ -1,24 +1,20 @@ -const { utils } = require('../../../utils'); -const { WebGL2KernelValueSingleArray1DI } = require('../../web-gl2/kernel-value/single-array1d-i'); - -class WebGL2KernelValueDynamicSingleArray1DI extends WebGL2KernelValueSingleArray1DI { - getSource() { - const variablePrecision = this.getVariablePrecisionString(); - return utils.linesToString([ - `uniform ${ variablePrecision } sampler2D ${this.id}`, - `uniform ${ variablePrecision } ivec2 ${this.sizeId}`, - `uniform ${ variablePrecision } ivec3 ${this.dimensionsId}`, - ]); - } - - updateValue(value) { - this.setShape(value); - this.kernel.setUniform3iv(this.dimensionsId, this.dimensions); - this.kernel.setUniform2iv(this.sizeId, this.textureSize); - super.updateValue(value); - } -} - -module.exports = { - WebGL2KernelValueDynamicSingleArray1DI -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { WebGL2KernelValueSingleArray1DI } from '../../web-gl2/kernel-value/single-array1d-i'; + +export class WebGL2KernelValueDynamicSingleArray1DI extends WebGL2KernelValueSingleArray1DI { + getSource() { + const variablePrecision = this.getVariablePrecisionString(); + return utils.linesToString([ + `uniform ${ variablePrecision } sampler2D ${this.id}`, + `uniform ${ variablePrecision } ivec2 ${this.sizeId}`, + `uniform ${ variablePrecision } ivec3 ${this.dimensionsId}`, + ]); + } + + updateValue(value) { + this.setShape(value); + this.kernel.setUniform3iv(this.dimensionsId, this.dimensions); + this.kernel.setUniform2iv(this.sizeId, this.textureSize); + super.updateValue(value); + } +} diff --git a/src/backend/web-gl2/kernel-value/dynamic-single-array2d-i.js b/src/backend/web-gl2/kernel-value/dynamic-single-array2d-i.js index d80f712d..127bb64f 100644 --- a/src/backend/web-gl2/kernel-value/dynamic-single-array2d-i.js +++ b/src/backend/web-gl2/kernel-value/dynamic-single-array2d-i.js @@ -1,24 +1,20 @@ -const { utils } = require('../../../utils'); -const { WebGL2KernelValueSingleArray2DI } = require('../../web-gl2/kernel-value/single-array2d-i'); - -class WebGL2KernelValueDynamicSingleArray2DI extends WebGL2KernelValueSingleArray2DI { - getSource() { - const variablePrecision = this.getVariablePrecisionString(); - return utils.linesToString([ - `uniform ${ variablePrecision } sampler2D ${this.id}`, - `uniform ${ variablePrecision } ivec2 ${this.sizeId}`, - `uniform ${ variablePrecision } ivec3 ${this.dimensionsId}`, - ]); - } - - updateValue(value) { - this.setShape(value); - this.kernel.setUniform3iv(this.dimensionsId, this.dimensions); - this.kernel.setUniform2iv(this.sizeId, this.textureSize); - super.updateValue(value); - } -} - -module.exports = { - WebGL2KernelValueDynamicSingleArray2DI -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { WebGL2KernelValueSingleArray2DI } from '../../web-gl2/kernel-value/single-array2d-i'; + +export class WebGL2KernelValueDynamicSingleArray2DI extends WebGL2KernelValueSingleArray2DI { + getSource() { + const variablePrecision = this.getVariablePrecisionString(); + return utils.linesToString([ + `uniform ${ variablePrecision } sampler2D ${this.id}`, + `uniform ${ variablePrecision } ivec2 ${this.sizeId}`, + `uniform ${ variablePrecision } ivec3 ${this.dimensionsId}`, + ]); + } + + updateValue(value) { + this.setShape(value); + this.kernel.setUniform3iv(this.dimensionsId, this.dimensions); + this.kernel.setUniform2iv(this.sizeId, this.textureSize); + super.updateValue(value); + } +} diff --git a/src/backend/web-gl2/kernel-value/dynamic-single-array3d-i.js b/src/backend/web-gl2/kernel-value/dynamic-single-array3d-i.js index 15f79ba4..1dd343f5 100644 --- a/src/backend/web-gl2/kernel-value/dynamic-single-array3d-i.js +++ b/src/backend/web-gl2/kernel-value/dynamic-single-array3d-i.js @@ -1,24 +1,20 @@ -const { utils } = require('../../../utils'); -const { WebGL2KernelValueSingleArray3DI } = require('../../web-gl2/kernel-value/single-array3d-i'); - -class WebGL2KernelValueDynamicSingleArray3DI extends WebGL2KernelValueSingleArray3DI { - getSource() { - const variablePrecision = this.getVariablePrecisionString(); - return utils.linesToString([ - `uniform ${ variablePrecision } sampler2D ${this.id}`, - `uniform ${ variablePrecision } ivec2 ${this.sizeId}`, - `uniform ${ variablePrecision } ivec3 ${this.dimensionsId}`, - ]); - } - - updateValue(value) { - this.setShape(value); - this.kernel.setUniform3iv(this.dimensionsId, this.dimensions); - this.kernel.setUniform2iv(this.sizeId, this.textureSize); - super.updateValue(value); - } -} - -module.exports = { - WebGL2KernelValueDynamicSingleArray3DI -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { WebGL2KernelValueSingleArray3DI } from '../../web-gl2/kernel-value/single-array3d-i'; + +export class WebGL2KernelValueDynamicSingleArray3DI extends WebGL2KernelValueSingleArray3DI { + getSource() { + const variablePrecision = this.getVariablePrecisionString(); + return utils.linesToString([ + `uniform ${ variablePrecision } sampler2D ${this.id}`, + `uniform ${ variablePrecision } ivec2 ${this.sizeId}`, + `uniform ${ variablePrecision } ivec3 ${this.dimensionsId}`, + ]); + } + + updateValue(value) { + this.setShape(value); + this.kernel.setUniform3iv(this.dimensionsId, this.dimensions); + this.kernel.setUniform2iv(this.sizeId, this.textureSize); + super.updateValue(value); + } +} diff --git a/src/backend/web-gl2/kernel-value/dynamic-single-input.js b/src/backend/web-gl2/kernel-value/dynamic-single-input.js index 02058a11..95ee750a 100644 --- a/src/backend/web-gl2/kernel-value/dynamic-single-input.js +++ b/src/backend/web-gl2/kernel-value/dynamic-single-input.js @@ -1,29 +1,25 @@ -const { utils } = require('../../../utils'); -const { WebGL2KernelValueSingleInput } = require('../../web-gl2/kernel-value/single-input'); - -class WebGL2KernelValueDynamicSingleInput extends WebGL2KernelValueSingleInput { - getSource() { - const variablePrecision = this.getVariablePrecisionString(); - return utils.linesToString([ - `uniform ${ variablePrecision } sampler2D ${this.id}`, - `uniform ${ variablePrecision } ivec2 ${this.sizeId}`, - `uniform ${ variablePrecision } ivec3 ${this.dimensionsId}`, - ]); - } - - updateValue(value) { - let [w, h, d] = value.size; - this.dimensions = new Int32Array([w || 1, h || 1, d || 1]); - this.textureSize = utils.getMemoryOptimizedFloatTextureSize(this.dimensions, this.bitRatio); - this.uploadArrayLength = this.textureSize[0] * this.textureSize[1] * this.bitRatio; - this.checkSize(this.textureSize[0] * this.bitRatio, this.textureSize[1] * this.bitRatio); - this.uploadValue = new Float32Array(this.uploadArrayLength); - this.kernel.setUniform3iv(this.dimensionsId, this.dimensions); - this.kernel.setUniform2iv(this.sizeId, this.textureSize); - super.updateValue(value); - } -} - -module.exports = { - WebGL2KernelValueDynamicSingleInput -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { WebGL2KernelValueSingleInput } from '../../web-gl2/kernel-value/single-input'; + +export class WebGL2KernelValueDynamicSingleInput extends WebGL2KernelValueSingleInput { + getSource() { + const variablePrecision = this.getVariablePrecisionString(); + return utils.linesToString([ + `uniform ${ variablePrecision } sampler2D ${this.id}`, + `uniform ${ variablePrecision } ivec2 ${this.sizeId}`, + `uniform ${ variablePrecision } ivec3 ${this.dimensionsId}`, + ]); + } + + updateValue(value) { + let [w, h, d] = value.size; + this.dimensions = new Int32Array([w || 1, h || 1, d || 1]); + this.textureSize = utils.getMemoryOptimizedFloatTextureSize(this.dimensions, this.bitRatio); + this.uploadArrayLength = this.textureSize[0] * this.textureSize[1] * this.bitRatio; + this.checkSize(this.textureSize[0] * this.bitRatio, this.textureSize[1] * this.bitRatio); + this.uploadValue = new Float32Array(this.uploadArrayLength); + this.kernel.setUniform3iv(this.dimensionsId, this.dimensions); + this.kernel.setUniform2iv(this.sizeId, this.textureSize); + super.updateValue(value); + } +} diff --git a/src/backend/web-gl2/kernel-value/dynamic-unsigned-array.js b/src/backend/web-gl2/kernel-value/dynamic-unsigned-array.js index 514682c5..1190d00d 100644 --- a/src/backend/web-gl2/kernel-value/dynamic-unsigned-array.js +++ b/src/backend/web-gl2/kernel-value/dynamic-unsigned-array.js @@ -1,17 +1,13 @@ -const { utils } = require('../../../utils'); -const { WebGLKernelValueDynamicUnsignedArray } = require('../../web-gl/kernel-value/dynamic-unsigned-array'); - -class WebGL2KernelValueDynamicUnsignedArray extends WebGLKernelValueDynamicUnsignedArray { - getSource() { - const variablePrecision = this.getVariablePrecisionString(); - return utils.linesToString([ - `uniform ${ variablePrecision } sampler2D ${this.id}`, - `uniform ${ variablePrecision } ivec2 ${this.sizeId}`, - `uniform ${ variablePrecision } ivec3 ${this.dimensionsId}`, - ]); - } -} - -module.exports = { - WebGL2KernelValueDynamicUnsignedArray -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { WebGLKernelValueDynamicUnsignedArray } from '../../web-gl/kernel-value/dynamic-unsigned-array'; + +export class WebGL2KernelValueDynamicUnsignedArray extends WebGLKernelValueDynamicUnsignedArray { + getSource() { + const variablePrecision = this.getVariablePrecisionString(); + return utils.linesToString([ + `uniform ${ variablePrecision } sampler2D ${this.id}`, + `uniform ${ variablePrecision } ivec2 ${this.sizeId}`, + `uniform ${ variablePrecision } ivec3 ${this.dimensionsId}`, + ]); + } +} diff --git a/src/backend/web-gl2/kernel-value/dynamic-unsigned-input.js b/src/backend/web-gl2/kernel-value/dynamic-unsigned-input.js index 9c79330a..c9311fa8 100644 --- a/src/backend/web-gl2/kernel-value/dynamic-unsigned-input.js +++ b/src/backend/web-gl2/kernel-value/dynamic-unsigned-input.js @@ -1,17 +1,13 @@ -const { utils } = require('../../../utils'); -const { WebGLKernelValueDynamicUnsignedInput } = require('../../web-gl/kernel-value/dynamic-unsigned-input'); - -class WebGL2KernelValueDynamicUnsignedInput extends WebGLKernelValueDynamicUnsignedInput { - getSource() { - const variablePrecision = this.getVariablePrecisionString(); - return utils.linesToString([ - `uniform ${ variablePrecision } sampler2D ${this.id}`, - `uniform ${ variablePrecision } ivec2 ${this.sizeId}`, - `uniform ${ variablePrecision } ivec3 ${this.dimensionsId}`, - ]); - } -} - -module.exports = { - WebGL2KernelValueDynamicUnsignedInput -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { WebGLKernelValueDynamicUnsignedInput } from '../../web-gl/kernel-value/dynamic-unsigned-input'; + +export class WebGL2KernelValueDynamicUnsignedInput extends WebGLKernelValueDynamicUnsignedInput { + getSource() { + const variablePrecision = this.getVariablePrecisionString(); + return utils.linesToString([ + `uniform ${ variablePrecision } sampler2D ${this.id}`, + `uniform ${ variablePrecision } ivec2 ${this.sizeId}`, + `uniform ${ variablePrecision } ivec3 ${this.dimensionsId}`, + ]); + } +} diff --git a/src/backend/web-gl2/kernel-value/float.js b/src/backend/web-gl2/kernel-value/float.js index b3fc5a78..0ebc9a6c 100644 --- a/src/backend/web-gl2/kernel-value/float.js +++ b/src/backend/web-gl2/kernel-value/float.js @@ -1,8 +1,3 @@ -const { utils } = require('../../../utils'); -const { WebGLKernelValueFloat } = require('../../web-gl/kernel-value/float'); - -class WebGL2KernelValueFloat extends WebGLKernelValueFloat {} - -module.exports = { - WebGL2KernelValueFloat -}; \ No newline at end of file +import { WebGLKernelValueFloat } from '../../web-gl/kernel-value/float'; + +export class WebGL2KernelValueFloat extends WebGLKernelValueFloat {} diff --git a/src/backend/web-gl2/kernel-value/html-image-array.js b/src/backend/web-gl2/kernel-value/html-image-array.js index 07303b4b..634965e4 100644 --- a/src/backend/web-gl2/kernel-value/html-image-array.js +++ b/src/backend/web-gl2/kernel-value/html-image-array.js @@ -1,68 +1,64 @@ -const { utils } = require('../../../utils'); -const { WebGLKernelValue } = require('../../web-gl/kernel-value/index'); - -class WebGL2KernelValueHTMLImageArray extends WebGLKernelValue { - constructor(value, settings) { - super(value, settings); - this.checkSize(value[0].width, value[0].height); - this.requestTexture(); - this.dimensions = [value[0].width, value[0].height, value.length]; - this.textureSize = [value[0].width, value[0].height]; - } - getStringValueHandler() { - return `const uploadValue_${this.name} = ${this.varName};\n`; - } - getSource() { - const variablePrecision = this.getVariablePrecisionString(); - return utils.linesToString([ - `uniform ${ variablePrecision } sampler2DArray ${this.id}`, - `${ variablePrecision } ivec2 ${this.sizeId} = ivec2(${this.textureSize[0]}, ${this.textureSize[1]})`, - `${ variablePrecision } ivec3 ${this.dimensionsId} = ivec3(${this.dimensions[0]}, ${this.dimensions[1]}, ${this.dimensions[2]})`, - ]); - } - - updateValue(images) { - const { context: gl } = this; - gl.activeTexture(this.contextHandle); - gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.texture); - gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); - // Upload the images into the texture. - gl.texImage3D( - gl.TEXTURE_2D_ARRAY, - 0, - gl.RGBA, - images[0].width, - images[0].height, - images.length, - 0, - gl.RGBA, - gl.UNSIGNED_BYTE, - null - ); - for (let i = 0; i < images.length; i++) { - const xOffset = 0; - const yOffset = 0; - const imageDepth = 1; - gl.texSubImage3D( - gl.TEXTURE_2D_ARRAY, - 0, - xOffset, - yOffset, - i, - images[i].width, - images[i].height, - imageDepth, - gl.RGBA, - gl.UNSIGNED_BYTE, - this.uploadValue = images[i] - ); - } - this.kernel.setUniform1i(this.id, this.index); - } -} - -module.exports = { - WebGL2KernelValueHTMLImageArray -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { WebGLKernelValue } from '../../web-gl/kernel-value/index'; + +export class WebGL2KernelValueHTMLImageArray extends WebGLKernelValue { + constructor(value, settings) { + super(value, settings); + this.checkSize(value[0].width, value[0].height); + this.requestTexture(); + this.dimensions = [value[0].width, value[0].height, value.length]; + this.textureSize = [value[0].width, value[0].height]; + } + getStringValueHandler() { + return `const uploadValue_${this.name} = ${this.varName};\n`; + } + getSource() { + const variablePrecision = this.getVariablePrecisionString(); + return utils.linesToString([ + `uniform ${ variablePrecision } sampler2DArray ${this.id}`, + `${ variablePrecision } ivec2 ${this.sizeId} = ivec2(${this.textureSize[0]}, ${this.textureSize[1]})`, + `${ variablePrecision } ivec3 ${this.dimensionsId} = ivec3(${this.dimensions[0]}, ${this.dimensions[1]}, ${this.dimensions[2]})`, + ]); + } + + updateValue(images) { + const { context: gl } = this; + gl.activeTexture(this.contextHandle); + gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.texture); + gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); + // Upload the images into the texture. + gl.texImage3D( + gl.TEXTURE_2D_ARRAY, + 0, + gl.RGBA, + images[0].width, + images[0].height, + images.length, + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + null + ); + for (let i = 0; i < images.length; i++) { + const xOffset = 0; + const yOffset = 0; + const imageDepth = 1; + gl.texSubImage3D( + gl.TEXTURE_2D_ARRAY, + 0, + xOffset, + yOffset, + i, + images[i].width, + images[i].height, + imageDepth, + gl.RGBA, + gl.UNSIGNED_BYTE, + this.uploadValue = images[i] + ); + } + this.kernel.setUniform1i(this.id, this.index); + } +} diff --git a/src/backend/web-gl2/kernel-value/html-image.js b/src/backend/web-gl2/kernel-value/html-image.js index 637182a0..e749aaf7 100644 --- a/src/backend/web-gl2/kernel-value/html-image.js +++ b/src/backend/web-gl2/kernel-value/html-image.js @@ -1,17 +1,13 @@ -const { utils } = require('../../../utils'); -const { WebGLKernelValueHTMLImage } = require('../../web-gl/kernel-value/html-image'); - -class WebGL2KernelValueHTMLImage extends WebGLKernelValueHTMLImage { - getSource() { - const variablePrecision = this.getVariablePrecisionString(); - return utils.linesToString([ - `uniform ${ variablePrecision } sampler2D ${this.id}`, - `${ variablePrecision } ivec2 ${this.sizeId} = ivec2(${this.textureSize[0]}, ${this.textureSize[1]})`, - `${ variablePrecision } ivec3 ${this.dimensionsId} = ivec3(${this.dimensions[0]}, ${this.dimensions[1]}, ${this.dimensions[2]})`, - ]); - } -} - -module.exports = { - WebGL2KernelValueHTMLImage -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { WebGLKernelValueHTMLImage } from '../../web-gl/kernel-value/html-image'; + +export class WebGL2KernelValueHTMLImage extends WebGLKernelValueHTMLImage { + getSource() { + const variablePrecision = this.getVariablePrecisionString(); + return utils.linesToString([ + `uniform ${ variablePrecision } sampler2D ${this.id}`, + `${ variablePrecision } ivec2 ${this.sizeId} = ivec2(${this.textureSize[0]}, ${this.textureSize[1]})`, + `${ variablePrecision } ivec3 ${this.dimensionsId} = ivec3(${this.dimensions[0]}, ${this.dimensions[1]}, ${this.dimensions[2]})`, + ]); + } +} diff --git a/src/backend/web-gl2/kernel-value/html-video.js b/src/backend/web-gl2/kernel-value/html-video.js index 85fb3953..665773ff 100644 --- a/src/backend/web-gl2/kernel-value/html-video.js +++ b/src/backend/web-gl2/kernel-value/html-video.js @@ -1,8 +1,3 @@ -const { utils } = require('../../../utils'); -const { WebGL2KernelValueHTMLImage } = require('./html-image'); - -class WebGL2KernelValueHTMLVideo extends WebGL2KernelValueHTMLImage {} - -module.exports = { - WebGL2KernelValueHTMLVideo -}; \ No newline at end of file +import { WebGL2KernelValueHTMLImage } from './html-image'; + +export class WebGL2KernelValueHTMLVideo extends WebGL2KernelValueHTMLImage {} diff --git a/src/backend/web-gl2/kernel-value/integer.js b/src/backend/web-gl2/kernel-value/integer.js index a41a2533..530df2fb 100644 --- a/src/backend/web-gl2/kernel-value/integer.js +++ b/src/backend/web-gl2/kernel-value/integer.js @@ -1,21 +1,16 @@ -const { utils } = require('../../../utils'); -const { WebGLKernelValueInteger } = require('../../web-gl/kernel-value/integer'); - -class WebGL2KernelValueInteger extends WebGLKernelValueInteger { - getSource(value) { - const variablePrecision = this.getVariablePrecisionString(); - if (this.origin === 'constants') { - return `const ${ variablePrecision } int ${this.id} = ${ parseInt(value) };\n`; - } - return `uniform ${ variablePrecision } int ${this.id};\n`; - } - - updateValue(value) { - if (this.origin === 'constants') return; - this.kernel.setUniform1i(this.id, this.uploadValue = value); - } -} - -module.exports = { - WebGL2KernelValueInteger -}; \ No newline at end of file +import { WebGLKernelValueInteger } from '../../web-gl/kernel-value/integer'; + +export class WebGL2KernelValueInteger extends WebGLKernelValueInteger { + getSource(value) { + const variablePrecision = this.getVariablePrecisionString(); + if (this.origin === 'constants') { + return `const ${ variablePrecision } int ${this.id} = ${ parseInt(value) };\n`; + } + return `uniform ${ variablePrecision } int ${this.id};\n`; + } + + updateValue(value) { + if (this.origin === 'constants') return; + this.kernel.setUniform1i(this.id, this.uploadValue = value); + } +} diff --git a/src/backend/web-gl2/kernel-value/memory-optimized-number-texture.js b/src/backend/web-gl2/kernel-value/memory-optimized-number-texture.js index 7fd85798..d4830b97 100644 --- a/src/backend/web-gl2/kernel-value/memory-optimized-number-texture.js +++ b/src/backend/web-gl2/kernel-value/memory-optimized-number-texture.js @@ -1,18 +1,14 @@ -const { utils } = require('../../../utils'); -const { WebGLKernelValueMemoryOptimizedNumberTexture } = require('../../web-gl/kernel-value/memory-optimized-number-texture'); - -class WebGL2KernelValueMemoryOptimizedNumberTexture extends WebGLKernelValueMemoryOptimizedNumberTexture { - getSource() { - const { id, sizeId, textureSize, dimensionsId, dimensions } = this; - const variablePrecision = this.getVariablePrecisionString(); - return utils.linesToString([ - `uniform sampler2D ${id}`, - `${ variablePrecision } ivec2 ${sizeId} = ivec2(${textureSize[0]}, ${textureSize[1]})`, - `${ variablePrecision } ivec3 ${dimensionsId} = ivec3(${dimensions[0]}, ${dimensions[1]}, ${dimensions[2]})`, - ]); - } -} - -module.exports = { - WebGL2KernelValueMemoryOptimizedNumberTexture -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { WebGLKernelValueMemoryOptimizedNumberTexture } from '../../web-gl/kernel-value/memory-optimized-number-texture'; + +export class WebGL2KernelValueMemoryOptimizedNumberTexture extends WebGLKernelValueMemoryOptimizedNumberTexture { + getSource() { + const { id, sizeId, textureSize, dimensionsId, dimensions } = this; + const variablePrecision = this.getVariablePrecisionString(); + return utils.linesToString([ + `uniform sampler2D ${id}`, + `${ variablePrecision } ivec2 ${sizeId} = ivec2(${textureSize[0]}, ${textureSize[1]})`, + `${ variablePrecision } ivec3 ${dimensionsId} = ivec3(${dimensions[0]}, ${dimensions[1]}, ${dimensions[2]})`, + ]); + } +} diff --git a/src/backend/web-gl2/kernel-value/number-texture.js b/src/backend/web-gl2/kernel-value/number-texture.js index 440f1e4e..d638c528 100644 --- a/src/backend/web-gl2/kernel-value/number-texture.js +++ b/src/backend/web-gl2/kernel-value/number-texture.js @@ -1,18 +1,14 @@ -const { utils } = require('../../../utils'); -const { WebGLKernelValueNumberTexture } = require('../../web-gl/kernel-value/number-texture'); - -class WebGL2KernelValueNumberTexture extends WebGLKernelValueNumberTexture { - getSource() { - const { id, sizeId, textureSize, dimensionsId, dimensions } = this; - const variablePrecision = this.getVariablePrecisionString(); - return utils.linesToString([ - `uniform ${ variablePrecision } sampler2D ${id}`, - `${ variablePrecision } ivec2 ${sizeId} = ivec2(${textureSize[0]}, ${textureSize[1]})`, - `${ variablePrecision } ivec3 ${dimensionsId} = ivec3(${dimensions[0]}, ${dimensions[1]}, ${dimensions[2]})`, - ]); - } -} - -module.exports = { - WebGL2KernelValueNumberTexture -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { WebGLKernelValueNumberTexture } from '../../web-gl/kernel-value/number-texture'; + +export class WebGL2KernelValueNumberTexture extends WebGLKernelValueNumberTexture { + getSource() { + const { id, sizeId, textureSize, dimensionsId, dimensions } = this; + const variablePrecision = this.getVariablePrecisionString(); + return utils.linesToString([ + `uniform ${ variablePrecision } sampler2D ${id}`, + `${ variablePrecision } ivec2 ${sizeId} = ivec2(${textureSize[0]}, ${textureSize[1]})`, + `${ variablePrecision } ivec3 ${dimensionsId} = ivec3(${dimensions[0]}, ${dimensions[1]}, ${dimensions[2]})`, + ]); + } +} diff --git a/src/backend/web-gl2/kernel-value/single-array.js b/src/backend/web-gl2/kernel-value/single-array.js index c6ff3d9b..6bce4a4a 100644 --- a/src/backend/web-gl2/kernel-value/single-array.js +++ b/src/backend/web-gl2/kernel-value/single-array.js @@ -1,34 +1,30 @@ -const { utils } = require('../../../utils'); -const { WebGLKernelValueSingleArray } = require('../../web-gl/kernel-value/single-array'); - -class WebGL2KernelValueSingleArray extends WebGLKernelValueSingleArray { - getSource() { - const variablePrecision = this.getVariablePrecisionString(); - return utils.linesToString([ - `uniform ${ variablePrecision } sampler2D ${this.id}`, - `${ variablePrecision } ivec2 ${this.sizeId} = ivec2(${this.textureSize[0]}, ${this.textureSize[1]})`, - `${ variablePrecision } ivec3 ${this.dimensionsId} = ivec3(${this.dimensions[0]}, ${this.dimensions[1]}, ${this.dimensions[2]})`, - ]); - } - - updateValue(value) { - if (value.constructor !== this.initialValueConstructor) { - this.onUpdateValueMismatch(); - return; - } - const { context: gl } = this; - utils.flattenTo(value, this.uploadValue); - gl.activeTexture(this.contextHandle); - gl.bindTexture(gl.TEXTURE_2D, this.texture); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, this.textureSize[0], this.textureSize[1], 0, gl.RGBA, gl.FLOAT, this.uploadValue); - this.kernel.setUniform1i(this.id, this.index); - } -} - -module.exports = { - WebGL2KernelValueSingleArray -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { WebGLKernelValueSingleArray } from '../../web-gl/kernel-value/single-array'; + +export class WebGL2KernelValueSingleArray extends WebGLKernelValueSingleArray { + getSource() { + const variablePrecision = this.getVariablePrecisionString(); + return utils.linesToString([ + `uniform ${ variablePrecision } sampler2D ${this.id}`, + `${ variablePrecision } ivec2 ${this.sizeId} = ivec2(${this.textureSize[0]}, ${this.textureSize[1]})`, + `${ variablePrecision } ivec3 ${this.dimensionsId} = ivec3(${this.dimensions[0]}, ${this.dimensions[1]}, ${this.dimensions[2]})`, + ]); + } + + updateValue(value) { + if (value.constructor !== this.initialValueConstructor) { + this.onUpdateValueMismatch(); + return; + } + const { context: gl } = this; + utils.flattenTo(value, this.uploadValue); + gl.activeTexture(this.contextHandle); + gl.bindTexture(gl.TEXTURE_2D, this.texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, this.textureSize[0], this.textureSize[1], 0, gl.RGBA, gl.FLOAT, this.uploadValue); + this.kernel.setUniform1i(this.id, this.index); + } +} diff --git a/src/backend/web-gl2/kernel-value/single-array1d-i.js b/src/backend/web-gl2/kernel-value/single-array1d-i.js index 6a64101d..a380c9c9 100644 --- a/src/backend/web-gl2/kernel-value/single-array1d-i.js +++ b/src/backend/web-gl2/kernel-value/single-array1d-i.js @@ -1,25 +1,21 @@ -const { utils } = require('../../../utils'); -const { WebGLKernelValueSingleArray1DI } = require('../../web-gl/kernel-value/single-array1d-i'); - -class WebGL2KernelValueSingleArray1DI extends WebGLKernelValueSingleArray1DI { - updateValue(value) { - if (value.constructor !== this.initialValueConstructor) { - this.onUpdateValueMismatch(); - return; - } - const { context: gl } = this; - utils.flattenTo(value, this.uploadValue); - gl.activeTexture(this.contextHandle); - gl.bindTexture(gl.TEXTURE_2D, this.texture); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, this.textureSize[0], this.textureSize[1], 0, gl.RGBA, gl.FLOAT, this.uploadValue); - this.kernel.setUniform1i(this.id, this.index); - } -} - -module.exports = { - WebGL2KernelValueSingleArray1DI -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { WebGLKernelValueSingleArray1DI } from '../../web-gl/kernel-value/single-array1d-i'; + +export class WebGL2KernelValueSingleArray1DI extends WebGLKernelValueSingleArray1DI { + updateValue(value) { + if (value.constructor !== this.initialValueConstructor) { + this.onUpdateValueMismatch(); + return; + } + const { context: gl } = this; + utils.flattenTo(value, this.uploadValue); + gl.activeTexture(this.contextHandle); + gl.bindTexture(gl.TEXTURE_2D, this.texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, this.textureSize[0], this.textureSize[1], 0, gl.RGBA, gl.FLOAT, this.uploadValue); + this.kernel.setUniform1i(this.id, this.index); + } +} diff --git a/src/backend/web-gl2/kernel-value/single-array2.js b/src/backend/web-gl2/kernel-value/single-array2.js index 638125fb..ff41ed60 100644 --- a/src/backend/web-gl2/kernel-value/single-array2.js +++ b/src/backend/web-gl2/kernel-value/single-array2.js @@ -1,7 +1,3 @@ -const { WebGLKernelValueSingleArray2 } = require('../../web-gl/kernel-value/single-array2'); - -class WebGL2KernelValueSingleArray2 extends WebGLKernelValueSingleArray2 {} - -module.exports = { - WebGL2KernelValueSingleArray2 -}; \ No newline at end of file +import { WebGLKernelValueSingleArray2 } from '../../web-gl/kernel-value/single-array2'; + +export class WebGL2KernelValueSingleArray2 extends WebGLKernelValueSingleArray2 {} diff --git a/src/backend/web-gl2/kernel-value/single-array2d-i.js b/src/backend/web-gl2/kernel-value/single-array2d-i.js index 8374e67c..4c62c47f 100644 --- a/src/backend/web-gl2/kernel-value/single-array2d-i.js +++ b/src/backend/web-gl2/kernel-value/single-array2d-i.js @@ -1,25 +1,21 @@ -const { utils } = require('../../../utils'); -const { WebGLKernelValueSingleArray2DI } = require('../../web-gl/kernel-value/single-array2d-i'); - -class WebGL2KernelValueSingleArray2DI extends WebGLKernelValueSingleArray2DI { - updateValue(value) { - if (value.constructor !== this.initialValueConstructor) { - this.onUpdateValueMismatch(); - return; - } - const { context: gl } = this; - utils.flattenTo(value, this.uploadValue); - gl.activeTexture(this.contextHandle); - gl.bindTexture(gl.TEXTURE_2D, this.texture); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, this.textureSize[0], this.textureSize[1], 0, gl.RGBA, gl.FLOAT, this.uploadValue); - this.kernel.setUniform1i(this.id, this.index); - } -} - -module.exports = { - WebGL2KernelValueSingleArray2DI -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { WebGLKernelValueSingleArray2DI } from '../../web-gl/kernel-value/single-array2d-i'; + +export class WebGL2KernelValueSingleArray2DI extends WebGLKernelValueSingleArray2DI { + updateValue(value) { + if (value.constructor !== this.initialValueConstructor) { + this.onUpdateValueMismatch(); + return; + } + const { context: gl } = this; + utils.flattenTo(value, this.uploadValue); + gl.activeTexture(this.contextHandle); + gl.bindTexture(gl.TEXTURE_2D, this.texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, this.textureSize[0], this.textureSize[1], 0, gl.RGBA, gl.FLOAT, this.uploadValue); + this.kernel.setUniform1i(this.id, this.index); + } +} diff --git a/src/backend/web-gl2/kernel-value/single-array3.js b/src/backend/web-gl2/kernel-value/single-array3.js index 9af01c8f..3dc5df84 100644 --- a/src/backend/web-gl2/kernel-value/single-array3.js +++ b/src/backend/web-gl2/kernel-value/single-array3.js @@ -1,7 +1,3 @@ -const { WebGLKernelValueSingleArray3 } = require('../../web-gl/kernel-value/single-array3'); - -class WebGL2KernelValueSingleArray3 extends WebGLKernelValueSingleArray3 {} - -module.exports = { - WebGL2KernelValueSingleArray3 -}; \ No newline at end of file +import { WebGLKernelValueSingleArray3 } from '../../web-gl/kernel-value/single-array3'; + +export class WebGL2KernelValueSingleArray3 extends WebGLKernelValueSingleArray3 {} diff --git a/src/backend/web-gl2/kernel-value/single-array3d-i.js b/src/backend/web-gl2/kernel-value/single-array3d-i.js index ea850885..382a3600 100644 --- a/src/backend/web-gl2/kernel-value/single-array3d-i.js +++ b/src/backend/web-gl2/kernel-value/single-array3d-i.js @@ -1,25 +1,21 @@ -const { utils } = require('../../../utils'); -const { WebGLKernelValueSingleArray3DI } = require('../../web-gl/kernel-value/single-array3d-i'); - -class WebGL2KernelValueSingleArray3DI extends WebGLKernelValueSingleArray3DI { - updateValue(value) { - if (value.constructor !== this.initialValueConstructor) { - this.onUpdateValueMismatch(); - return; - } - const { context: gl } = this; - utils.flattenTo(value, this.uploadValue); - gl.activeTexture(this.contextHandle); - gl.bindTexture(gl.TEXTURE_2D, this.texture); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, this.textureSize[0], this.textureSize[1], 0, gl.RGBA, gl.FLOAT, this.uploadValue); - this.kernel.setUniform1i(this.id, this.index); - } -} - -module.exports = { - WebGL2KernelValueSingleArray3DI -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { WebGLKernelValueSingleArray3DI } from '../../web-gl/kernel-value/single-array3d-i'; + +export class WebGL2KernelValueSingleArray3DI extends WebGLKernelValueSingleArray3DI { + updateValue(value) { + if (value.constructor !== this.initialValueConstructor) { + this.onUpdateValueMismatch(); + return; + } + const { context: gl } = this; + utils.flattenTo(value, this.uploadValue); + gl.activeTexture(this.contextHandle); + gl.bindTexture(gl.TEXTURE_2D, this.texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, this.textureSize[0], this.textureSize[1], 0, gl.RGBA, gl.FLOAT, this.uploadValue); + this.kernel.setUniform1i(this.id, this.index); + } +} diff --git a/src/backend/web-gl2/kernel-value/single-array4.js b/src/backend/web-gl2/kernel-value/single-array4.js index d5ec9d2a..d84ac6bc 100644 --- a/src/backend/web-gl2/kernel-value/single-array4.js +++ b/src/backend/web-gl2/kernel-value/single-array4.js @@ -1,7 +1,3 @@ -const { WebGLKernelValueSingleArray4 } = require('../../web-gl/kernel-value/single-array4'); - -class WebGL2KernelValueSingleArray4 extends WebGLKernelValueSingleArray4 {} - -module.exports = { - WebGL2KernelValueSingleArray4 -}; \ No newline at end of file +import { WebGLKernelValueSingleArray4 } from '../../web-gl/kernel-value/single-array4'; + +export class WebGL2KernelValueSingleArray4 extends WebGLKernelValueSingleArray4 {} diff --git a/src/backend/web-gl2/kernel-value/single-input.js b/src/backend/web-gl2/kernel-value/single-input.js index 2bf23acb..6dac696b 100644 --- a/src/backend/web-gl2/kernel-value/single-input.js +++ b/src/backend/web-gl2/kernel-value/single-input.js @@ -1,30 +1,26 @@ -const { utils } = require('../../../utils'); -const { WebGLKernelValueSingleInput } = require('../../web-gl/kernel-value/single-input'); - -class WebGL2KernelValueSingleInput extends WebGLKernelValueSingleInput { - getSource() { - const variablePrecision = this.getVariablePrecisionString(); - return utils.linesToString([ - `uniform ${ variablePrecision } sampler2D ${this.id}`, - `${ variablePrecision } ivec2 ${this.sizeId} = ivec2(${this.textureSize[0]}, ${this.textureSize[1]})`, - `${ variablePrecision } ivec3 ${this.dimensionsId} = ivec3(${this.dimensions[0]}, ${this.dimensions[1]}, ${this.dimensions[2]})`, - ]); - } - - updateValue(input) { - const { context: gl } = this; - utils.flattenTo(input.value, this.uploadValue); - gl.activeTexture(this.contextHandle); - gl.bindTexture(gl.TEXTURE_2D, this.texture); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, this.textureSize[0], this.textureSize[1], 0, gl.RGBA, gl.FLOAT, this.uploadValue); - this.kernel.setUniform1i(this.id, this.index); - } -} - -module.exports = { - WebGL2KernelValueSingleInput -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { WebGLKernelValueSingleInput } from '../../web-gl/kernel-value/single-input'; + +export class WebGL2KernelValueSingleInput extends WebGLKernelValueSingleInput { + getSource() { + const variablePrecision = this.getVariablePrecisionString(); + return utils.linesToString([ + `uniform ${ variablePrecision } sampler2D ${this.id}`, + `${ variablePrecision } ivec2 ${this.sizeId} = ivec2(${this.textureSize[0]}, ${this.textureSize[1]})`, + `${ variablePrecision } ivec3 ${this.dimensionsId} = ivec3(${this.dimensions[0]}, ${this.dimensions[1]}, ${this.dimensions[2]})`, + ]); + } + + updateValue(input) { + const { context: gl } = this; + utils.flattenTo(input.value, this.uploadValue); + gl.activeTexture(this.contextHandle); + gl.bindTexture(gl.TEXTURE_2D, this.texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, this.textureSize[0], this.textureSize[1], 0, gl.RGBA, gl.FLOAT, this.uploadValue); + this.kernel.setUniform1i(this.id, this.index); + } +} diff --git a/src/backend/web-gl2/kernel-value/unsigned-array.js b/src/backend/web-gl2/kernel-value/unsigned-array.js index bac1e6f1..ca8cff6c 100644 --- a/src/backend/web-gl2/kernel-value/unsigned-array.js +++ b/src/backend/web-gl2/kernel-value/unsigned-array.js @@ -1,17 +1,13 @@ -const { utils } = require('../../../utils'); -const { WebGLKernelValueUnsignedArray } = require('../../web-gl/kernel-value/unsigned-array'); - -class WebGL2KernelValueUnsignedArray extends WebGLKernelValueUnsignedArray { - getSource() { - const variablePrecision = this.getVariablePrecisionString(); - return utils.linesToString([ - `uniform ${ variablePrecision } sampler2D ${this.id}`, - `${ variablePrecision } ivec2 ${this.sizeId} = ivec2(${this.textureSize[0]}, ${this.textureSize[1]})`, - `${ variablePrecision } ivec3 ${this.dimensionsId} = ivec3(${this.dimensions[0]}, ${this.dimensions[1]}, ${this.dimensions[2]})`, - ]); - } -} - -module.exports = { - WebGL2KernelValueUnsignedArray -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { WebGLKernelValueUnsignedArray } from '../../web-gl/kernel-value/unsigned-array'; + +export class WebGL2KernelValueUnsignedArray extends WebGLKernelValueUnsignedArray { + getSource() { + const variablePrecision = this.getVariablePrecisionString(); + return utils.linesToString([ + `uniform ${ variablePrecision } sampler2D ${this.id}`, + `${ variablePrecision } ivec2 ${this.sizeId} = ivec2(${this.textureSize[0]}, ${this.textureSize[1]})`, + `${ variablePrecision } ivec3 ${this.dimensionsId} = ivec3(${this.dimensions[0]}, ${this.dimensions[1]}, ${this.dimensions[2]})`, + ]); + } +} diff --git a/src/backend/web-gl2/kernel-value/unsigned-input.js b/src/backend/web-gl2/kernel-value/unsigned-input.js index ed933347..7dddcb5f 100644 --- a/src/backend/web-gl2/kernel-value/unsigned-input.js +++ b/src/backend/web-gl2/kernel-value/unsigned-input.js @@ -1,17 +1,13 @@ -const { utils } = require('../../../utils'); -const { WebGLKernelValueUnsignedInput } = require('../../web-gl/kernel-value/unsigned-input'); - -class WebGL2KernelValueUnsignedInput extends WebGLKernelValueUnsignedInput { - getSource() { - const variablePrecision = this.getVariablePrecisionString(); - return utils.linesToString([ - `uniform ${ variablePrecision } sampler2D ${this.id}`, - `${ variablePrecision } ivec2 ${this.sizeId} = ivec2(${this.textureSize[0]}, ${this.textureSize[1]})`, - `${ variablePrecision } ivec3 ${this.dimensionsId} = ivec3(${this.dimensions[0]}, ${this.dimensions[1]}, ${this.dimensions[2]})`, - ]); - } -} - -module.exports = { - WebGL2KernelValueUnsignedInput -}; \ No newline at end of file +import { utils } from '../../../utils'; +import { WebGLKernelValueUnsignedInput } from '../../web-gl/kernel-value/unsigned-input'; + +export class WebGL2KernelValueUnsignedInput extends WebGLKernelValueUnsignedInput { + getSource() { + const variablePrecision = this.getVariablePrecisionString(); + return utils.linesToString([ + `uniform ${ variablePrecision } sampler2D ${this.id}`, + `${ variablePrecision } ivec2 ${this.sizeId} = ivec2(${this.textureSize[0]}, ${this.textureSize[1]})`, + `${ variablePrecision } ivec3 ${this.dimensionsId} = ivec3(${this.dimensions[0]}, ${this.dimensions[1]}, ${this.dimensions[2]})`, + ]); + } +} diff --git a/src/backend/web-gl2/kernel.js b/src/backend/web-gl2/kernel.js index d27fdec8..d95a8660 100644 --- a/src/backend/web-gl2/kernel.js +++ b/src/backend/web-gl2/kernel.js @@ -1,685 +1,681 @@ -const { WebGLKernel } = require('../web-gl/kernel'); -const { WebGL2FunctionNode } = require('./function-node'); -const { FunctionBuilder } = require('../function-builder'); -const { utils } = require('../../utils'); -const { fragmentShader } = require('./fragment-shader'); -const { vertexShader } = require('./vertex-shader'); -const { lookupKernelValueType } = require('./kernel-value-maps'); - -let isSupported = null; -let testCanvas = null; -let testContext = null; -let testExtensions = null; - -/** - * - * @type {IKernelFeatures} - */ -let features = null; - -/** - * @extends WebGLKernel - */ -class WebGL2Kernel extends WebGLKernel { - static get isSupported() { - if (isSupported !== null) { - return isSupported; - } - this.setupFeatureChecks(); - isSupported = this.isContextMatch(testContext); - return isSupported; - } - - static setupFeatureChecks() { - if (typeof document !== 'undefined') { - testCanvas = document.createElement('canvas'); - } else if (typeof OffscreenCanvas !== 'undefined') { - testCanvas = new OffscreenCanvas(0, 0); - } - if (!testCanvas) return; - testContext = testCanvas.getContext('webgl2'); - if (!testContext || !testContext.getExtension) return; - testExtensions = { - EXT_color_buffer_float: testContext.getExtension('EXT_color_buffer_float'), - OES_texture_float_linear: testContext.getExtension('OES_texture_float_linear'), - }; - features = this.getFeatures(); - } - - static isContextMatch(context) { - // from global - if (typeof WebGL2RenderingContext !== 'undefined') { - return context instanceof WebGL2RenderingContext; - } - return false; - } - - static getFeatures() { - return Object.freeze({ - isFloatRead: this.getIsFloatRead(), - isIntegerDivisionAccurate: this.getIsIntegerDivisionAccurate(), - kernelMap: true, - isTextureFloat: true, - channelCount: this.getChannelCount(), - maxTextureSize: this.getMaxTextureSize(), - }); - } - - static getIsTextureFloat() { - return true; - } - - static getIsIntegerDivisionAccurate() { - return super.getIsIntegerDivisionAccurate(); - } - - static getChannelCount() { - return testContext.getParameter(testContext.MAX_DRAW_BUFFERS); - } - - static getMaxTextureSize() { - return testContext.getParameter(testContext.MAX_TEXTURE_SIZE); - } - - static lookupKernelValueType(type, dynamic, precision, value) { - return lookupKernelValueType(type, dynamic, precision, value); - } - - static get testCanvas() { - return testCanvas; - } - - static get testContext() { - return testContext; - } - - /** - * - * @returns {{isFloatRead: Boolean, isIntegerDivisionAccurate: Boolean, kernelMap: Boolean, isTextureFloat: Boolean}} - */ - static get features() { - return features; - } - - static get fragmentShader() { - return fragmentShader; - } - static get vertexShader() { - return vertexShader; - } - - initContext() { - const settings = { - alpha: false, - depth: false, - antialias: false - }; - const context = this.canvas.getContext('webgl2', settings); - return context; - } - - initExtensions() { - this.extensions = { - EXT_color_buffer_float: this.context.getExtension('EXT_color_buffer_float'), - OES_texture_float_linear: this.context.getExtension('OES_texture_float_linear'), - }; - } - - /** - * @desc Validate settings related to Kernel, such as dimensions size, and auto output support. - * @param {IArguments} args - */ - validateSettings(args) { - if (!this.validate) { - this.texSize = utils.getKernelTextureSize({ - optimizeFloatMemory: this.optimizeFloatMemory, - precision: this.precision, - }, this.output); - return; - } - - const features = this.constructor.features; - if (this.precision === 'single' && !features.isFloatRead) { - throw new Error('Float texture outputs are not supported'); - } else if (!this.graphical && this.precision === null) { - this.precision = features.isFloatRead ? 'single' : 'unsigned'; - } - - if (this.fixIntegerDivisionAccuracy === null) { - this.fixIntegerDivisionAccuracy = !features.isIntegerDivisionAccurate; - } else if (this.fixIntegerDivisionAccuracy && features.isIntegerDivisionAccurate) { - this.fixIntegerDivisionAccuracy = false; - } - - this.checkOutput(); - - 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); - switch (argType) { - case 'Array': - this.output = utils.getDimensions(argType); - break; - case 'NumberTexture': - case 'MemoryOptimizedNumberTexture': - case 'ArrayTexture(1)': - case 'ArrayTexture(2)': - case 'ArrayTexture(3)': - case 'ArrayTexture(4)': - this.output = args[0].output; - break; - default: - 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'); - } - - if (this.precision === 'single') { - console.warn('Cannot use graphical mode and single precision at the same time'); - this.precision = 'unsigned'; - } - - this.texSize = utils.clone(this.output); - return; - } else if (!this.graphical && this.precision === null && features.isTextureFloat) { - this.precision = 'single'; - } - - this.texSize = utils.getKernelTextureSize({ - optimizeFloatMemory: this.optimizeFloatMemory, - precision: this.precision, - }, this.output); - - this.checkTextureSize(); - } - - translateSource() { - const functionBuilder = FunctionBuilder.fromKernel(this, WebGL2FunctionNode, { - fixIntegerDivisionAccuracy: this.fixIntegerDivisionAccuracy - }); - this.translatedSource = functionBuilder.getPrototypeString('kernel'); - if (!this.graphical && !this.returnType) { - this.returnType = functionBuilder.getKernelResultType(); - } - - if (this.subKernels && this.subKernels.length > 0) { - for (let i = 0; i < this.subKernels.length; i++) { - const subKernel = this.subKernels[i]; - if (!subKernel.returnType) { - subKernel.returnType = functionBuilder.getSubKernelResultType(i); - } - } - } - } - - run() { - const { kernelArguments, texSize, forceUploadKernelConstants } = this; - const gl = this.context; - - gl.useProgram(this.program); - gl.scissor(0, 0, texSize[0], texSize[1]); - - if (this.dynamicOutput) { - this.setUniform3iv('uOutputDim', new Int32Array(this.threadDim)); - this.setUniform2iv('uTexSize', texSize); - } - - this.setUniform2f('ratio', texSize[0] / this.maxTexSize[0], texSize[1] / this.maxTexSize[1]); - - this.switchingKernels = false; - for (let i = 0; i < forceUploadKernelConstants.length; i++) { - const constant = forceUploadKernelConstants[i]; - constant.updateValue(this.constants[constant.name]); - if (this.switchingKernels) return; - } - for (let i = 0; i < kernelArguments.length; i++) { - kernelArguments[i].updateValue(arguments[i]); - if (this.switchingKernels) return; - } - - if (this.plugins) { - for (let i = 0; i < this.plugins.length; i++) { - const plugin = this.plugins[i]; - if (plugin.onBeforeRun) { - plugin.onBeforeRun(this); - } - } - } - - if (this.graphical) { - if (this.pipeline) { - gl.bindRenderbuffer(gl.RENDERBUFFER, null); - gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); - if (!this.outputTexture || this.immutable) { - this._setupOutputTexture(); - } - gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); - return new this.TextureConstructor({ - texture: this.outputTexture, - size: texSize, - dimensions: this.threadDim, - output: this.output, - context: this.context - }); - } - gl.bindRenderbuffer(gl.RENDERBUFFER, null); - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); - return; - } - - gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); - if (this.immutable) { - this._setupOutputTexture(); - } - - if (this.subKernels !== null) { - if (this.immutable) { - this._setupSubOutputTextures(); - } - gl.drawBuffers(this.drawBuffersMap); - } - - gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); - } - - drawBuffers() { - this.context.drawBuffers(this.drawBuffersMap); - } - - getOutputTexture() { - return this.outputTexture; - } - - _setupOutputTexture() { - const { texSize } = this; - const gl = this.context; - const texture = this.outputTexture = gl.createTexture(); - gl.activeTexture(gl.TEXTURE0 + this.constantTextureCount + this.argumentTextureCount); - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - if (this.precision === 'single') { - if (this.pipeline) { - switch (this.returnType) { - case 'Number': - case 'Float': - case 'Integer': - if (this.optimizeFloatMemory) { - gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA32F, texSize[0], texSize[1]); - } else { - gl.texStorage2D(gl.TEXTURE_2D, 1, gl.R32F, texSize[0], texSize[1]); - } - break; - case 'Array(2)': - gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RG32F, texSize[0], texSize[1]); - break; - case 'Array(3)': // there is _no_ 3 channel format which is guaranteed to be color-renderable - case 'Array(4)': - gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA32F, texSize[0], texSize[1]); - break; - default: - throw new Error('Unhandled return type'); - } - } else { - gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA32F, texSize[0], texSize[1]); - } - } else { - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize[0], texSize[1], 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); - } - - _setupSubOutputTextures() { - const { texSize } = this; - const gl = this.context; - this.drawBuffersMap = [gl.COLOR_ATTACHMENT0]; - this.subKernelOutputTextures = []; - for (let i = 0; i < this.subKernels.length; i++) { - const texture = this.context.createTexture(); - this.subKernelOutputTextures.push(texture); - this.drawBuffersMap.push(gl.COLOR_ATTACHMENT0 + i + 1); - gl.activeTexture(gl.TEXTURE0 + this.constantTextureCount + this.argumentTextureCount + i); - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - // TODO: upgrade this - if (this.precision === 'single') { - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, texSize[0], texSize[1], 0, gl.RGBA, gl.FLOAT, null); - } else { - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize[0], texSize[1], 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i + 1, gl.TEXTURE_2D, texture, 0); - } - } - - /** - * - * @desc Get the header string for the program. - * This returns an empty string if no sub-kernels are defined. - * - * @returns {String} result - */ - _getHeaderString() { - return ''; - } - - /** - * @desc Get texture coordinate string for the program - * @returns {String} result - */ - _getTextureCoordinate() { - const subKernels = this.subKernels; - if (subKernels === null || subKernels.length < 1) { - switch (this.tactic) { - case 'speed': - return 'in lowp vec2 vTexCoord;\n'; - case 'performance': - return 'in highp vec2 vTexCoord;\n'; - case 'balanced': - default: - return 'in mediump vec2 vTexCoord;\n'; - } - } else { - switch (this.tactic) { - case 'speed': - return 'out lowp vec2 vTexCoord;\n'; - case 'performance': - return 'out highp vec2 vTexCoord;\n'; - case 'balanced': - default: - return 'out mediump vec2 vTexCoord;\n'; - } - } - } - - /** - * @desc Generate transpiled glsl Strings for user-defined parameters sent to a kernel - * @param {Array} args - The actual parameters sent to the Kernel - * @returns {String} result - */ - _getMainArgumentsString(args) { - const result = []; - const argumentNames = this.argumentNames; - for (let i = 0; i < argumentNames.length; i++) { - result.push(this.kernelArguments[i].getSource(args[i])); - } - return result.join(''); - } - - /** - * @desc Get Kernel program string (in *glsl*) for a kernel. - * @returns {String} result - */ - getKernelString() { - let kernelResultDeclaration; - switch (this.returnType) { - case 'Array(2)': - kernelResultDeclaration = 'vec2 kernelResult'; - break; - case 'Array(3)': - kernelResultDeclaration = 'vec3 kernelResult'; - break; - case 'Array(4)': - kernelResultDeclaration = 'vec4 kernelResult'; - break; - case 'LiteralInteger': - case 'Float': - case 'Number': - case 'Integer': - kernelResultDeclaration = 'float kernelResult'; - break; - default: - if (this.graphical) { - kernelResultDeclaration = 'float kernelResult'; - } else { - throw new Error(`unrecognized output type "${ this.returnType }"`); - } - } - - const result = []; - const subKernels = this.subKernels; - if (subKernels !== null) { - result.push( - kernelResultDeclaration, - 'layout(location = 0) out vec4 data0' - ); - for (let i = 0; i < subKernels.length; i++) { - const subKernel = subKernels[i]; - result.push( - subKernel.returnType === 'Integer' ? - `int subKernelResult_${ subKernel.name } = 0` : - `float subKernelResult_${ subKernel.name } = 0.0`, - `layout(location = ${ i + 1 }) out vec4 data${ i + 1 }` - ); - } - } else { - result.push( - 'out vec4 data0', - kernelResultDeclaration - ); - } - - return utils.linesToString(result) + this.translatedSource; - } - - getMainResultGraphical() { - return utils.linesToString([ - ' threadId = indexTo3D(index, uOutputDim)', - ' kernel()', - ' data0 = actualColor', - ]); - } - - getMainResultPackedPixels() { - switch (this.returnType) { - case 'LiteralInteger': - case 'Number': - case 'Integer': - case 'Float': - return this.getMainResultKernelPackedPixels() + - this.getMainResultSubKernelPackedPixels(); - default: - throw new Error(`packed output only usable with Numbers, "${this.returnType}" specified`); - } - } - - /** - * @return {String} - */ - getMainResultKernelPackedPixels() { - return utils.linesToString([ - ' threadId = indexTo3D(index, uOutputDim)', - ' kernel()', - ` data0 = ${this.useLegacyEncoder ? 'legacyEncode32' : 'encode32'}(kernelResult)` - ]); - } - - /** - * @return {String} - */ - getMainResultSubKernelPackedPixels() { - const result = []; - if (!this.subKernels) return ''; - for (let i = 0; i < this.subKernels.length; i++) { - const subKernel = this.subKernels[i]; - if (subKernel.returnType === 'Integer') { - result.push( - ` data${i + 1} = ${this.useLegacyEncoder ? 'legacyEncode32' : 'encode32'}(float(subKernelResult_${this.subKernels[i].name}))` - ); - } else { - result.push( - ` data${i + 1} = ${this.useLegacyEncoder ? 'legacyEncode32' : 'encode32'}(subKernelResult_${this.subKernels[i].name})` - ); - } - } - return utils.linesToString(result); - } - - getMainResultMemoryOptimizedFloats() { - const result = [ - ' index *= 4', - ]; - - switch (this.returnType) { - case 'Number': - case 'Integer': - case 'Float': - const channels = ['r', 'g', 'b', 'a']; - for (let i = 0; i < channels.length; i++) { - const channel = channels[i]; - this.getMainResultKernelMemoryOptimizedFloats(result, channel); - this.getMainResultSubKernelMemoryOptimizedFloats(result, channel); - if (i + 1 < channels.length) { - result.push(' index += 1'); - } - } - break; - default: - throw new Error(`optimized output only usable with Numbers, ${this.returnType} specified`); - } - - return utils.linesToString(result); - } - - getMainResultKernelMemoryOptimizedFloats(result, channel) { - result.push( - ' threadId = indexTo3D(index, uOutputDim)', - ' kernel()', - ` data0.${channel} = kernelResult`, - ); - } - - getMainResultSubKernelMemoryOptimizedFloats(result, channel) { - if (!this.subKernels) return result; - for (let i = 0; i < this.subKernels.length; i++) { - const subKernel = this.subKernels[i]; - if (subKernel.returnType === 'Integer') { - result.push( - ` data${i + 1}.${channel} = float(subKernelResult_${subKernel.name})`, - ); - } else { - result.push( - ` data${i + 1}.${channel} = subKernelResult_${subKernel.name}`, - ); - } - } - } - - getMainResultKernelNumberTexture() { - return [ - ' threadId = indexTo3D(index, uOutputDim)', - ' kernel()', - ' data0[0] = kernelResult', - ]; - } - - getMainResultSubKernelNumberTexture() { - const result = []; - if (!this.subKernels) return result; - for (let i = 0; i < this.subKernels.length; ++i) { - const subKernel = this.subKernels[i]; - if (subKernel.returnType === 'Integer') { - result.push( - ` data${i + 1}[0] = float(subKernelResult_${subKernel.name})`, - ); - } else { - result.push( - ` data${i + 1}[0] = subKernelResult_${subKernel.name}`, - ); - } - } - return result; - } - - getMainResultKernelArray2Texture() { - return [ - ' threadId = indexTo3D(index, uOutputDim)', - ' kernel()', - ' data0[0] = kernelResult[0]', - ' data0[1] = kernelResult[1]', - ]; - } - - getMainResultSubKernelArray2Texture() { - const result = []; - if (!this.subKernels) return result; - for (let i = 0; i < this.subKernels.length; ++i) { - const subKernel = this.subKernels[i]; - result.push( - ` data${i + 1}[0] = subKernelResult_${subKernel.name}[0]`, - ` data${i + 1}[1] = subKernelResult_${subKernel.name}[1]`, - ); - } - return result; - } - - getMainResultKernelArray3Texture() { - return [ - ' threadId = indexTo3D(index, uOutputDim)', - ' kernel()', - ' data0[0] = kernelResult[0]', - ' data0[1] = kernelResult[1]', - ' data0[2] = kernelResult[2]', - ]; - } - - getMainResultSubKernelArray3Texture() { - const result = []; - if (!this.subKernels) return result; - for (let i = 0; i < this.subKernels.length; ++i) { - const subKernel = this.subKernels[i]; - result.push( - ` data${i + 1}[0] = subKernelResult_${subKernel.name}[0]`, - ` data${i + 1}[1] = subKernelResult_${subKernel.name}[1]`, - ` data${i + 1}[2] = subKernelResult_${subKernel.name}[2]`, - ); - } - return result; - } - - getMainResultKernelArray4Texture() { - return [ - ' threadId = indexTo3D(index, uOutputDim)', - ' kernel()', - ' data0 = kernelResult', - ]; - } - - getMainResultSubKernelArray4Texture() { - const result = []; - if (!this.subKernels) return result; - for (let i = 0; i < this.subKernels.length; ++i) { - result.push( - ` data${i + 1} = subKernelResult_${this.subKernels[i].name}`, - ); - } - return result; - } - - destroyExtensions() { - this.extensions.EXT_color_buffer_float = null; - this.extensions.OES_texture_float_linear = null; - } - - toJSON() { - const json = super.toJSON(); - json.functionNodes = FunctionBuilder.fromKernel(this, WebGL2FunctionNode).toJSON(); - return json; - } -} - -module.exports = { - WebGL2Kernel -}; \ No newline at end of file +import { WebGLKernel } from '../web-gl/kernel'; +import { WebGL2FunctionNode } from './function-node'; +import { FunctionBuilder } from '../function-builder'; +import { utils } from '../../utils'; +import { fragmentShader } from './fragment-shader'; +import { vertexShader } from './vertex-shader'; +import { lookupKernelValueType } from './kernel-value-maps'; + +let isSupported = null; +let testCanvas = null; +let testContext = null; +let testExtensions = null; + +/** + * + * @type {IKernelFeatures} + */ +let features = null; + +/** + * @extends WebGLKernel + */ +export class WebGL2Kernel extends WebGLKernel { + static get isSupported() { + if (isSupported !== null) { + return isSupported; + } + this.setupFeatureChecks(); + isSupported = this.isContextMatch(testContext); + return isSupported; + } + + static setupFeatureChecks() { + if (typeof document !== 'undefined') { + testCanvas = document.createElement('canvas'); + } else if (typeof OffscreenCanvas !== 'undefined') { + testCanvas = new OffscreenCanvas(0, 0); + } + if (!testCanvas) return; + testContext = testCanvas.getContext('webgl2'); + if (!testContext || !testContext.getExtension) return; + testExtensions = { + EXT_color_buffer_float: testContext.getExtension('EXT_color_buffer_float'), + OES_texture_float_linear: testContext.getExtension('OES_texture_float_linear'), + }; + features = this.getFeatures(); + } + + static isContextMatch(context) { + // from global + if (typeof WebGL2RenderingContext !== 'undefined') { + return context instanceof WebGL2RenderingContext; + } + return false; + } + + static getFeatures() { + return Object.freeze({ + isFloatRead: this.getIsFloatRead(), + isIntegerDivisionAccurate: this.getIsIntegerDivisionAccurate(), + kernelMap: true, + isTextureFloat: true, + channelCount: this.getChannelCount(), + maxTextureSize: this.getMaxTextureSize(), + }); + } + + static getIsTextureFloat() { + return true; + } + + static getIsIntegerDivisionAccurate() { + return super.getIsIntegerDivisionAccurate(); + } + + static getChannelCount() { + return testContext.getParameter(testContext.MAX_DRAW_BUFFERS); + } + + static getMaxTextureSize() { + return testContext.getParameter(testContext.MAX_TEXTURE_SIZE); + } + + static lookupKernelValueType(type, dynamic, precision, value) { + return lookupKernelValueType(type, dynamic, precision, value); + } + + static get testCanvas() { + return testCanvas; + } + + static get testContext() { + return testContext; + } + + /** + * + * @returns {{isFloatRead: Boolean, isIntegerDivisionAccurate: Boolean, kernelMap: Boolean, isTextureFloat: Boolean}} + */ + static get features() { + return features; + } + + static get fragmentShader() { + return fragmentShader; + } + static get vertexShader() { + return vertexShader; + } + + initContext() { + const settings = { + alpha: false, + depth: false, + antialias: false + }; + const context = this.canvas.getContext('webgl2', settings); + return context; + } + + initExtensions() { + this.extensions = { + EXT_color_buffer_float: this.context.getExtension('EXT_color_buffer_float'), + OES_texture_float_linear: this.context.getExtension('OES_texture_float_linear'), + }; + } + + /** + * @desc Validate settings related to Kernel, such as dimensions size, and auto output support. + * @param {IArguments} args + */ + validateSettings(args) { + if (!this.validate) { + this.texSize = utils.getKernelTextureSize({ + optimizeFloatMemory: this.optimizeFloatMemory, + precision: this.precision, + }, this.output); + return; + } + + const features = this.constructor.features; + if (this.precision === 'single' && !features.isFloatRead) { + throw new Error('Float texture outputs are not supported'); + } else if (!this.graphical && this.precision === null) { + this.precision = features.isFloatRead ? 'single' : 'unsigned'; + } + + if (this.fixIntegerDivisionAccuracy === null) { + this.fixIntegerDivisionAccuracy = !features.isIntegerDivisionAccurate; + } else if (this.fixIntegerDivisionAccuracy && features.isIntegerDivisionAccurate) { + this.fixIntegerDivisionAccuracy = false; + } + + this.checkOutput(); + + 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); + switch (argType) { + case 'Array': + this.output = utils.getDimensions(argType); + break; + case 'NumberTexture': + case 'MemoryOptimizedNumberTexture': + case 'ArrayTexture(1)': + case 'ArrayTexture(2)': + case 'ArrayTexture(3)': + case 'ArrayTexture(4)': + this.output = args[0].output; + break; + default: + 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'); + } + + if (this.precision === 'single') { + console.warn('Cannot use graphical mode and single precision at the same time'); + this.precision = 'unsigned'; + } + + this.texSize = utils.clone(this.output); + return; + } else if (!this.graphical && this.precision === null && features.isTextureFloat) { + this.precision = 'single'; + } + + this.texSize = utils.getKernelTextureSize({ + optimizeFloatMemory: this.optimizeFloatMemory, + precision: this.precision, + }, this.output); + + this.checkTextureSize(); + } + + translateSource() { + const functionBuilder = FunctionBuilder.fromKernel(this, WebGL2FunctionNode, { + fixIntegerDivisionAccuracy: this.fixIntegerDivisionAccuracy + }); + this.translatedSource = functionBuilder.getPrototypeString('kernel'); + if (!this.graphical && !this.returnType) { + this.returnType = functionBuilder.getKernelResultType(); + } + + if (this.subKernels && this.subKernels.length > 0) { + for (let i = 0; i < this.subKernels.length; i++) { + const subKernel = this.subKernels[i]; + if (!subKernel.returnType) { + subKernel.returnType = functionBuilder.getSubKernelResultType(i); + } + } + } + } + + run() { + const { kernelArguments, texSize, forceUploadKernelConstants } = this; + const gl = this.context; + + gl.useProgram(this.program); + gl.scissor(0, 0, texSize[0], texSize[1]); + + if (this.dynamicOutput) { + this.setUniform3iv('uOutputDim', new Int32Array(this.threadDim)); + this.setUniform2iv('uTexSize', texSize); + } + + this.setUniform2f('ratio', texSize[0] / this.maxTexSize[0], texSize[1] / this.maxTexSize[1]); + + this.switchingKernels = false; + for (let i = 0; i < forceUploadKernelConstants.length; i++) { + const constant = forceUploadKernelConstants[i]; + constant.updateValue(this.constants[constant.name]); + if (this.switchingKernels) return; + } + for (let i = 0; i < kernelArguments.length; i++) { + kernelArguments[i].updateValue(arguments[i]); + if (this.switchingKernels) return; + } + + if (this.plugins) { + for (let i = 0; i < this.plugins.length; i++) { + const plugin = this.plugins[i]; + if (plugin.onBeforeRun) { + plugin.onBeforeRun(this); + } + } + } + + if (this.graphical) { + if (this.pipeline) { + gl.bindRenderbuffer(gl.RENDERBUFFER, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); + if (!this.outputTexture || this.immutable) { + this._setupOutputTexture(); + } + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + return new this.TextureConstructor({ + texture: this.outputTexture, + size: texSize, + dimensions: this.threadDim, + output: this.output, + context: this.context + }); + } + gl.bindRenderbuffer(gl.RENDERBUFFER, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + return; + } + + gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); + if (this.immutable) { + this._setupOutputTexture(); + } + + if (this.subKernels !== null) { + if (this.immutable) { + this._setupSubOutputTextures(); + } + gl.drawBuffers(this.drawBuffersMap); + } + + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + } + + drawBuffers() { + this.context.drawBuffers(this.drawBuffersMap); + } + + getOutputTexture() { + return this.outputTexture; + } + + _setupOutputTexture() { + const { texSize } = this; + const gl = this.context; + const texture = this.outputTexture = gl.createTexture(); + gl.activeTexture(gl.TEXTURE0 + this.constantTextureCount + this.argumentTextureCount); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + if (this.precision === 'single') { + if (this.pipeline) { + switch (this.returnType) { + case 'Number': + case 'Float': + case 'Integer': + if (this.optimizeFloatMemory) { + gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA32F, texSize[0], texSize[1]); + } else { + gl.texStorage2D(gl.TEXTURE_2D, 1, gl.R32F, texSize[0], texSize[1]); + } + break; + case 'Array(2)': + gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RG32F, texSize[0], texSize[1]); + break; + case 'Array(3)': // there is _no_ 3 channel format which is guaranteed to be color-renderable + case 'Array(4)': + gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA32F, texSize[0], texSize[1]); + break; + default: + throw new Error('Unhandled return type'); + } + } else { + gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA32F, texSize[0], texSize[1]); + } + } else { + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize[0], texSize[1], 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + } + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); + } + + _setupSubOutputTextures() { + const { texSize } = this; + const gl = this.context; + this.drawBuffersMap = [gl.COLOR_ATTACHMENT0]; + this.subKernelOutputTextures = []; + for (let i = 0; i < this.subKernels.length; i++) { + const texture = this.context.createTexture(); + this.subKernelOutputTextures.push(texture); + this.drawBuffersMap.push(gl.COLOR_ATTACHMENT0 + i + 1); + gl.activeTexture(gl.TEXTURE0 + this.constantTextureCount + this.argumentTextureCount + i); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + // TODO: upgrade this + if (this.precision === 'single') { + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, texSize[0], texSize[1], 0, gl.RGBA, gl.FLOAT, null); + } else { + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize[0], texSize[1], 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + } + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i + 1, gl.TEXTURE_2D, texture, 0); + } + } + + /** + * + * @desc Get the header string for the program. + * This returns an empty string if no sub-kernels are defined. + * + * @returns {String} result + */ + _getHeaderString() { + return ''; + } + + /** + * @desc Get texture coordinate string for the program + * @returns {String} result + */ + _getTextureCoordinate() { + const subKernels = this.subKernels; + if (subKernels === null || subKernels.length < 1) { + switch (this.tactic) { + case 'speed': + return 'in lowp vec2 vTexCoord;\n'; + case 'performance': + return 'in highp vec2 vTexCoord;\n'; + case 'balanced': + default: + return 'in mediump vec2 vTexCoord;\n'; + } + } else { + switch (this.tactic) { + case 'speed': + return 'out lowp vec2 vTexCoord;\n'; + case 'performance': + return 'out highp vec2 vTexCoord;\n'; + case 'balanced': + default: + return 'out mediump vec2 vTexCoord;\n'; + } + } + } + + /** + * @desc Generate transpiled glsl Strings for user-defined parameters sent to a kernel + * @param {Array} args - The actual parameters sent to the Kernel + * @returns {String} result + */ + _getMainArgumentsString(args) { + const result = []; + const argumentNames = this.argumentNames; + for (let i = 0; i < argumentNames.length; i++) { + result.push(this.kernelArguments[i].getSource(args[i])); + } + return result.join(''); + } + + /** + * @desc Get Kernel program string (in *glsl*) for a kernel. + * @returns {String} result + */ + getKernelString() { + let kernelResultDeclaration; + switch (this.returnType) { + case 'Array(2)': + kernelResultDeclaration = 'vec2 kernelResult'; + break; + case 'Array(3)': + kernelResultDeclaration = 'vec3 kernelResult'; + break; + case 'Array(4)': + kernelResultDeclaration = 'vec4 kernelResult'; + break; + case 'LiteralInteger': + case 'Float': + case 'Number': + case 'Integer': + kernelResultDeclaration = 'float kernelResult'; + break; + default: + if (this.graphical) { + kernelResultDeclaration = 'float kernelResult'; + } else { + throw new Error(`unrecognized output type "${ this.returnType }"`); + } + } + + const result = []; + const subKernels = this.subKernels; + if (subKernels !== null) { + result.push( + kernelResultDeclaration, + 'layout(location = 0) out vec4 data0' + ); + for (let i = 0; i < subKernels.length; i++) { + const subKernel = subKernels[i]; + result.push( + subKernel.returnType === 'Integer' ? + `int subKernelResult_${ subKernel.name } = 0` : + `float subKernelResult_${ subKernel.name } = 0.0`, + `layout(location = ${ i + 1 }) out vec4 data${ i + 1 }` + ); + } + } else { + result.push( + 'out vec4 data0', + kernelResultDeclaration + ); + } + + return utils.linesToString(result) + this.translatedSource; + } + + getMainResultGraphical() { + return utils.linesToString([ + ' threadId = indexTo3D(index, uOutputDim)', + ' kernel()', + ' data0 = actualColor', + ]); + } + + getMainResultPackedPixels() { + switch (this.returnType) { + case 'LiteralInteger': + case 'Number': + case 'Integer': + case 'Float': + return this.getMainResultKernelPackedPixels() + + this.getMainResultSubKernelPackedPixels(); + default: + throw new Error(`packed output only usable with Numbers, "${this.returnType}" specified`); + } + } + + /** + * @return {String} + */ + getMainResultKernelPackedPixels() { + return utils.linesToString([ + ' threadId = indexTo3D(index, uOutputDim)', + ' kernel()', + ` data0 = ${this.useLegacyEncoder ? 'legacyEncode32' : 'encode32'}(kernelResult)` + ]); + } + + /** + * @return {String} + */ + getMainResultSubKernelPackedPixels() { + const result = []; + if (!this.subKernels) return ''; + for (let i = 0; i < this.subKernels.length; i++) { + const subKernel = this.subKernels[i]; + if (subKernel.returnType === 'Integer') { + result.push( + ` data${i + 1} = ${this.useLegacyEncoder ? 'legacyEncode32' : 'encode32'}(float(subKernelResult_${this.subKernels[i].name}))` + ); + } else { + result.push( + ` data${i + 1} = ${this.useLegacyEncoder ? 'legacyEncode32' : 'encode32'}(subKernelResult_${this.subKernels[i].name})` + ); + } + } + return utils.linesToString(result); + } + + getMainResultMemoryOptimizedFloats() { + const result = [ + ' index *= 4', + ]; + + switch (this.returnType) { + case 'Number': + case 'Integer': + case 'Float': + const channels = ['r', 'g', 'b', 'a']; + for (let i = 0; i < channels.length; i++) { + const channel = channels[i]; + this.getMainResultKernelMemoryOptimizedFloats(result, channel); + this.getMainResultSubKernelMemoryOptimizedFloats(result, channel); + if (i + 1 < channels.length) { + result.push(' index += 1'); + } + } + break; + default: + throw new Error(`optimized output only usable with Numbers, ${this.returnType} specified`); + } + + return utils.linesToString(result); + } + + getMainResultKernelMemoryOptimizedFloats(result, channel) { + result.push( + ' threadId = indexTo3D(index, uOutputDim)', + ' kernel()', + ` data0.${channel} = kernelResult`, + ); + } + + getMainResultSubKernelMemoryOptimizedFloats(result, channel) { + if (!this.subKernels) return result; + for (let i = 0; i < this.subKernels.length; i++) { + const subKernel = this.subKernels[i]; + if (subKernel.returnType === 'Integer') { + result.push( + ` data${i + 1}.${channel} = float(subKernelResult_${subKernel.name})`, + ); + } else { + result.push( + ` data${i + 1}.${channel} = subKernelResult_${subKernel.name}`, + ); + } + } + } + + getMainResultKernelNumberTexture() { + return [ + ' threadId = indexTo3D(index, uOutputDim)', + ' kernel()', + ' data0[0] = kernelResult', + ]; + } + + getMainResultSubKernelNumberTexture() { + const result = []; + if (!this.subKernels) return result; + for (let i = 0; i < this.subKernels.length; ++i) { + const subKernel = this.subKernels[i]; + if (subKernel.returnType === 'Integer') { + result.push( + ` data${i + 1}[0] = float(subKernelResult_${subKernel.name})`, + ); + } else { + result.push( + ` data${i + 1}[0] = subKernelResult_${subKernel.name}`, + ); + } + } + return result; + } + + getMainResultKernelArray2Texture() { + return [ + ' threadId = indexTo3D(index, uOutputDim)', + ' kernel()', + ' data0[0] = kernelResult[0]', + ' data0[1] = kernelResult[1]', + ]; + } + + getMainResultSubKernelArray2Texture() { + const result = []; + if (!this.subKernels) return result; + for (let i = 0; i < this.subKernels.length; ++i) { + const subKernel = this.subKernels[i]; + result.push( + ` data${i + 1}[0] = subKernelResult_${subKernel.name}[0]`, + ` data${i + 1}[1] = subKernelResult_${subKernel.name}[1]`, + ); + } + return result; + } + + getMainResultKernelArray3Texture() { + return [ + ' threadId = indexTo3D(index, uOutputDim)', + ' kernel()', + ' data0[0] = kernelResult[0]', + ' data0[1] = kernelResult[1]', + ' data0[2] = kernelResult[2]', + ]; + } + + getMainResultSubKernelArray3Texture() { + const result = []; + if (!this.subKernels) return result; + for (let i = 0; i < this.subKernels.length; ++i) { + const subKernel = this.subKernels[i]; + result.push( + ` data${i + 1}[0] = subKernelResult_${subKernel.name}[0]`, + ` data${i + 1}[1] = subKernelResult_${subKernel.name}[1]`, + ` data${i + 1}[2] = subKernelResult_${subKernel.name}[2]`, + ); + } + return result; + } + + getMainResultKernelArray4Texture() { + return [ + ' threadId = indexTo3D(index, uOutputDim)', + ' kernel()', + ' data0 = kernelResult', + ]; + } + + getMainResultSubKernelArray4Texture() { + const result = []; + if (!this.subKernels) return result; + for (let i = 0; i < this.subKernels.length; ++i) { + result.push( + ` data${i + 1} = subKernelResult_${this.subKernels[i].name}`, + ); + } + return result; + } + + destroyExtensions() { + this.extensions.EXT_color_buffer_float = null; + this.extensions.OES_texture_float_linear = null; + } + + toJSON() { + const json = super.toJSON(); + json.functionNodes = FunctionBuilder.fromKernel(this, WebGL2FunctionNode).toJSON(); + return json; + } +} diff --git a/src/backend/web-gl2/vertex-shader.js b/src/backend/web-gl2/vertex-shader.js index e44dc986..5b65a0bb 100644 --- a/src/backend/web-gl2/vertex-shader.js +++ b/src/backend/web-gl2/vertex-shader.js @@ -1,20 +1,16 @@ -// language=GLSL -const vertexShader = `#version 300 es -__FLOAT_TACTIC_DECLARATION__; -__INT_TACTIC_DECLARATION__; -__SAMPLER_2D_TACTIC_DECLARATION__; - -in vec2 aPos; -in vec2 aTexCoord; - -out vec2 vTexCoord; -uniform vec2 ratio; - -void main(void) { - gl_Position = vec4((aPos + vec2(1)) * ratio + vec2(-1), 0, 1); - vTexCoord = aTexCoord; -}`; - -module.exports = { - vertexShader -}; \ No newline at end of file +// language=GLSL +export const vertexShader = `#version 300 es +__FLOAT_TACTIC_DECLARATION__; +__INT_TACTIC_DECLARATION__; +__SAMPLER_2D_TACTIC_DECLARATION__; + +in vec2 aPos; +in vec2 aTexCoord; + +out vec2 vTexCoord; +uniform vec2 ratio; + +void main(void) { + gl_Position = vec4((aPos + vec2(1)) * ratio + vec2(-1), 0, 1); + vTexCoord = aTexCoord; +}`; diff --git a/src/base-gpu.js b/src/base-gpu.js new file mode 100644 index 00000000..be3e0921 --- /dev/null +++ b/src/base-gpu.js @@ -0,0 +1,577 @@ +import { gpuMock } from 'gpu-mock.js'; +import { CPUKernel } from './backend/cpu/kernel'; +import { WebGL2Kernel } from './backend/web-gl2/kernel'; +import { WebGLKernel } from './backend/web-gl/kernel'; +import { kernelRunShortcut } from './kernel-run-shortcut'; +import { + functionToIFunction, + getFunctionNameFromString, + isFunction, + warnDeprecated +} from './common'; +import { getVariableType } from './utils'; + +/** + * @type {Kernel[]} + */ +const kernelOrder = [ WebGL2Kernel, WebGLKernel ]; + +/** + * @type {string[]} + */ +const kernelTypes = [ 'gpu', 'cpu' ]; + +const internalKernels = { + 'webgl2': WebGL2Kernel, + 'webgl': WebGLKernel, +}; + +let validate = true; + +/** + * The GPU.js library class which manages the GPU context for the creating kernels + */ +export class GPU { + static disableValidation() { + validate = false; + } + + static enableValidation() { + validate = true; + } + + static get isGPUSupported() { + return kernelOrder.some(Kernel => Kernel.isSupported); + } + + /** + * + * @returns {boolean} + */ + static get isKernelMapSupported() { + return kernelOrder.some(Kernel => Kernel.isSupported && Kernel.features.kernelMap); + } + + /** + * @desc TRUE is platform supports OffscreenCanvas + */ + static get isOffscreenCanvasSupported() { + return (typeof Worker !== 'undefined' && typeof OffscreenCanvas !== 'undefined') || typeof importScripts !== 'undefined'; + } + + /** + * @desc TRUE if platform supports WebGL + */ + static get isWebGLSupported() { + return WebGLKernel.isSupported; + } + + /** + * @desc TRUE if platform supports WebGL2 + */ + static get isWebGL2Supported() { + return WebGL2Kernel.isSupported; + } + + /** + * @desc TRUE if platform supports HeadlessGL + */ + static get isHeadlessGLSupported() { + return false; + } + + /** + * + * @desc TRUE if platform supports Canvas + */ + static get isCanvasSupported() { + return typeof HTMLCanvasElement !== 'undefined'; + } + + /** + * @desc TRUE if platform supports HTMLImageArray} + */ + static get isGPUHTMLImageArraySupported() { + return WebGL2Kernel.isSupported; + } + + /** + * @desc TRUE if platform supports single precision} + * @returns {boolean} + */ + static get isSinglePrecisionSupported() { + return kernelOrder.some(Kernel => Kernel.isSupported && Kernel.features.isFloatRead && Kernel.features.isTextureFloat); + } + + /** + * Creates an instance of GPU. + * @param {IGPUSettings} [settings] - Settings to set mode, and other properties + */ + constructor(settings) { + settings = settings || {}; + this.canvas = settings.canvas || null; + this.context = settings.context || null; + this.mode = settings.mode; + this.Kernel = null; + this.kernels = []; + this.functions = []; + this.nativeFunctions = []; + this.injectedNative = null; + if (this.mode === 'dev') return; + this.chooseKernel(); + // add functions from settings + if (settings.functions) { + for (let i = 0; i < settings.functions.length; i++) { + this.addFunction(settings.functions[i]); + } + } + + // add native functions from settings + if (settings.nativeFunctions) { + for (const p in settings.nativeFunctions) { + this.addNativeFunction(p, settings.nativeFunctions[p]); + } + } + } + + getValidate() { + return validate; + } + + /** + * Choose kernel type and save on .Kernel property of GPU + */ + chooseKernel() { + if (this.Kernel) return; + + let Kernel = null; + + if (this.context) { + for (let i = 0; i < kernelOrder.length; i++) { + const ExternalKernel = kernelOrder[i]; + if (ExternalKernel.isContextMatch(this.context)) { + if (!ExternalKernel.isSupported) { + throw new Error(`Kernel type ${ExternalKernel.name} not supported`); + } + Kernel = ExternalKernel; + break; + } + } + if (Kernel === null) { + throw new Error('unknown Context'); + } + } else if (this.mode) { + if (this.mode in internalKernels) { + if (!validate || internalKernels[this.mode].isSupported) { + Kernel = internalKernels[this.mode]; + } + } else if (this.mode === 'gpu') { + for (let i = 0; i < kernelOrder.length; i++) { + if (kernelOrder[i].isSupported) { + Kernel = kernelOrder[i]; + break; + } + } + } else if (this.mode === 'cpu') { + Kernel = CPUKernel; + } + if (!Kernel) { + throw new Error(`A requested mode of "${this.mode}" and is not supported`); + } + } else { + for (let i = 0; i < kernelOrder.length; i++) { + if (kernelOrder[i].isSupported) { + Kernel = kernelOrder[i]; + break; + } + } + if (!Kernel) { + Kernel = CPUKernel; + } + } + + if (!this.mode) { + this.mode = Kernel.mode; + } + this.Kernel = Kernel; + } + + /** + * @desc This creates a callable function object to call the kernel function with the argument parameter set + * @param {Function|String|object} source - The calling to perform the conversion + * @param {Object} [settings] - The parameter configuration object + * @return {Kernel} callable function to run + */ + createKernel(source, settings) { + if (typeof source === 'undefined') { + throw new Error('Missing source parameter'); + } + if (typeof source !== 'object' && !isFunction(source) && typeof source !== 'string') { + throw new Error('source parameter not a function'); + } + + if (this.mode === 'dev') { + const devKernel = gpuMock(source, upgradeDeprecatedCreateKernelSettings(settings)); + this.kernels.push(devKernel); + return devKernel; + } + + source = typeof source === 'function' ? source.toString() : source; + const switchableKernels = {}; + const settingsCopy = upgradeDeprecatedCreateKernelSettings(settings) || {}; + // handle conversion of argumentTypes + if (settings && typeof settings.argumentTypes === 'object') { + settingsCopy.argumentTypes = Object.keys(settings.argumentTypes).map(argumentName => settings.argumentTypes[argumentName]); + } + + function onRequestFallback(args) { + const fallbackKernel = new CPUKernel(source, { + argumentTypes: kernelRun.argumentTypes, + constantTypes: kernelRun.constantTypes, + graphical: kernelRun.graphical, + loopMaxIterations: kernelRun.loopMaxIterations, + constants: kernelRun.constants, + dynamicOutput: kernelRun.dynamicOutput, + dynamicArgument: kernelRun.dynamicArguments, + output: kernelRun.output, + precision: kernelRun.precision, + pipeline: kernelRun.pipeline, + immutable: kernelRun.immutable, + optimizeFloatMemory: kernelRun.optimizeFloatMemory, + fixIntegerDivisionAccuracy: kernelRun.fixIntegerDivisionAccuracy, + functions: kernelRun.functions, + nativeFunctions: kernelRun.nativeFunctions, + injectedNative: kernelRun.injectedNative, + subKernels: kernelRun.subKernels, + strictIntegers: kernelRun.strictIntegers, + debug: kernelRun.debug, + warnVarUsage: kernelRun.warnVarUsage, + }); + fallbackKernel.build.apply(fallbackKernel, args); + const result = fallbackKernel.run.apply(fallbackKernel, args); + kernelRun.replaceKernel(fallbackKernel); + return result; + } + + function onRequestSwitchKernel(args, kernel) { + const argumentTypes = new Array(args.length); + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + const type = kernel.argumentTypes[i]; + if (arg.type) { + argumentTypes[i] = arg.type; + } else { + switch (type) { + case 'Number': + case 'Integer': + case 'Float': + case 'ArrayTexture(1)': + argumentTypes[i] = getVariableType(arg); + break; + default: + argumentTypes[i] = type; + } + } + } + const signature = argumentTypes.join(','); + const existingKernel = switchableKernels[signature]; + if (existingKernel) { + existingKernel.run.apply(existingKernel, args); + if (existingKernel.renderKernels) { + return existingKernel.renderKernels(); + } else { + return existingKernel.renderOutput(); + } + } + + const newKernel = switchableKernels[signature] = new kernel.constructor(source, { + argumentTypes, + constantTypes: kernel.constantTypes, + graphical: kernel.graphical, + loopMaxIterations: kernel.loopMaxIterations, + constants: kernel.constants, + dynamicOutput: kernel.dynamicOutput, + dynamicArgument: kernel.dynamicArguments, + context: kernel.context, + canvas: kernel.canvas, + output: kernel.output, + precision: kernel.precision, + pipeline: kernel.pipeline, + immutable: kernel.immutable, + optimizeFloatMemory: kernel.optimizeFloatMemory, + fixIntegerDivisionAccuracy: kernel.fixIntegerDivisionAccuracy, + functions: kernel.functions, + nativeFunctions: kernel.nativeFunctions, + injectedNative: kernel.injectedNative, + subKernels: kernel.subKernels, + strictIntegers: kernel.strictIntegers, + debug: kernel.debug, + gpu: kernel.gpu, + validate, + warnVarUsage: kernel.warnVarUsage, + returnType: kernel.returnType, + onRequestFallback, + onRequestSwitchKernel, + }); + newKernel.build.apply(newKernel, args); + newKernel.run.apply(newKernel, args); + kernelRun.replaceKernel(newKernel); + if (newKernel.renderKernels) { + return newKernel.renderKernels(); + } else { + return newKernel.renderOutput(); + } + } + const mergedSettings = Object.assign({ + context: this.context, + canvas: this.canvas, + functions: this.functions, + nativeFunctions: this.nativeFunctions, + injectedNative: this.injectedNative, + gpu: this, + validate, + onRequestFallback, + onRequestSwitchKernel + }, settingsCopy); + + const kernelRun = kernelRunShortcut(new this.Kernel(source, mergedSettings)); + + //if canvas didn't come from this, propagate from kernel + if (!this.canvas) { + this.canvas = kernelRun.canvas; + } + + //if context didn't come from this, propagate from kernel + if (!this.context) { + this.context = kernelRun.context; + } + + this.kernels.push(kernelRun); + + return kernelRun; + } + + /** + * + * Create a super kernel which executes sub kernels + * and saves their output to be used with the next sub kernel. + * This can be useful if we want to save the output on one kernel, + * and then use it as an input to another kernel. *Machine Learning* + * + * @param {Object|Array} subKernels - Sub kernels for this kernel + * @param {Function} rootKernel - Root kernel + * + * @returns {Function} callable kernel function + * + * @example + * const megaKernel = gpu.createKernelMap({ + * addResult: function add(a, b) { + * return a[this.thread.x] + b[this.thread.x]; + * }, + * multiplyResult: function multiply(a, b) { + * return a[this.thread.x] * b[this.thread.x]; + * }, + * }, function(a, b, c) { + * return multiply(add(a, b), c); + * }); + * + * megaKernel(a, b, c); + * + * Note: You can also define subKernels as an array of functions. + * > [add, multiply] + * + */ + createKernelMap() { + let fn; + let settings; + if (typeof arguments[arguments.length - 2] === 'function') { + fn = arguments[arguments.length - 2]; + settings = arguments[arguments.length - 1]; + } else { + fn = arguments[arguments.length - 1]; + } + + if (this.mode !== 'dev') { + if (!this.Kernel.isSupported || !this.Kernel.features.kernelMap) { + if (this.mode && kernelTypes.indexOf(this.mode) < 0) { + throw new Error(`kernelMap not supported on ${this.Kernel.name}`); + } + } + } + + const settingsCopy = upgradeDeprecatedCreateKernelSettings(settings); + // handle conversion of argumentTypes + if (settings && typeof settings.argumentTypes === 'object') { + settingsCopy.argumentTypes = Object.keys(settings.argumentTypes).map(argumentName => settings.argumentTypes[argumentName]); + } + + if (Array.isArray(arguments[0])) { + settingsCopy.subKernels = []; + const functions = arguments[0]; + for (let i = 0; i < functions.length; i++) { + const source = functions[i].toString(); + const name = getFunctionNameFromString(source); + settingsCopy.subKernels.push({ + name, + source, + property: i, + }); + } + } else { + settingsCopy.subKernels = []; + const functions = arguments[0]; + for (let p in functions) { + if (!functions.hasOwnProperty(p)) continue; + const source = functions[p].toString(); + const name = getFunctionNameFromString(source); + settingsCopy.subKernels.push({ + name: name || p, + source, + property: p, + }); + } + } + const kernel = this.createKernel(fn, settingsCopy); + + return kernel; + } + + /** + * + * Combine different kernels into one super Kernel, + * useful to perform multiple operations inside one + * kernel without the penalty of data transfer between + * cpu and gpu. + * + * The number of kernel functions sent to this method can be variable. + * You can send in one, two, etc. + * + * @param {Function} subKernels - Kernel function(s) to combine. + * @param {Function} rootKernel - Root kernel to combine kernels into + * + * @example + * combineKernels(add, multiply, function(a,b,c){ + * return add(multiply(a,b), c) + * }) + * + * @returns {Function} Callable kernel function + * + */ + combineKernels() { + const firstKernel = arguments[0]; + const combinedKernel = arguments[arguments.length - 1]; + if (firstKernel.kernel.constructor.mode === 'cpu') return combinedKernel; + const canvas = arguments[0].canvas; + const context = arguments[0].context; + const max = arguments.length - 1; + for (let i = 0; i < max; i++) { + arguments[i] + .setCanvas(canvas) + .setContext(context) + .setPipeline(true); + } + + return function() { + const texture = combinedKernel.apply(this, arguments); + if (texture.toArray) { + return texture.toArray(); + } + return texture; + }; + } + + /** + * @desc Adds additional functions, that the kernel may call. + * @param {Function|String} source - Javascript function to convert + * @param {IFunctionSettings} [settings] + * @returns {GPU} returns itself + */ + addFunction(source, settings) { + this.functions.push(functionToIFunction(source, settings)); + return this; + } + + /** + * @desc Adds additional native functions, that the kernel may call. + * @param {String} name - native function name, used for reverse lookup + * @param {String} source - the native function implementation, as it would be defined in it's entirety + * @param {object} [settings] + * @returns {GPU} returns itself + */ + addNativeFunction(name, source, settings) { + if (this.kernels.length > 0) { + throw new Error('Cannot call "addNativeFunction" after "createKernels" has been called.'); + } + settings = settings || {}; + const { argumentTypes, argumentNames } = this.Kernel.nativeFunctionArguments(source) || {}; + this.nativeFunctions.push({ + name, + source, + settings, + argumentTypes, + argumentNames, + returnType: settings.returnType || this.Kernel.nativeFunctionReturnType(source), + }); + return this; + } + + /** + * Inject a string just before translated kernel functions + * @param {String} source + * @return {GPU} + */ + injectNative(source) { + this.injectedNative = source; + return this; + } + + /** + * @desc Destroys all memory associated with gpu.js & the webGl if we created it + */ + destroy() { + if (!this.kernels) return; + // perform on next run loop - for some reason we dont get lose context events + // if webGl is created and destroyed in the same run loop. + setTimeout(() => { + for (let i = 0; i < this.kernels.length; i++) { + this.kernels[i].destroy(true); // remove canvas if exists + } + // all kernels are associated with one context, go ahead and take care of it here + let firstKernel = this.kernels[0]; + if (firstKernel) { + // if it is shortcut + if (firstKernel.kernel) { + firstKernel = firstKernel.kernel; + } + if (firstKernel.constructor.destroyContext) { + firstKernel.constructor.destroyContext(this.context); + } + } + }, 0); + } +} + +function upgradeDeprecatedCreateKernelSettings(settings) { + if (!settings) { + return {}; + } + const upgradedSettings = Object.assign({}, settings); + + if (settings.hasOwnProperty('floatOutput')) { + warnDeprecated('setting', 'floatOutput', 'precision'); + upgradedSettings.precision = settings.floatOutput ? 'single' : 'unsigned'; + } + if (settings.hasOwnProperty('outputToTexture')) { + warnDeprecated('setting', 'outputToTexture', 'pipeline'); + upgradedSettings.pipeline = Boolean(settings.outputToTexture); + } + if (settings.hasOwnProperty('outputImmutable')) { + warnDeprecated('setting', 'outputImmutable', 'immutable'); + upgradedSettings.immutable = Boolean(settings.outputImmutable); + } + if (settings.hasOwnProperty('floatTextures')) { + warnDeprecated('setting', 'floatTextures', 'optimizeFloatMemory'); + upgradedSettings.optimizeFloatMemory = Boolean(settings.floatTextures); + } + return upgradedSettings; +} diff --git a/src/browser-header.txt b/src/browser-header.txt deleted file mode 100644 index ef9d7f8a..00000000 --- a/src/browser-header.txt +++ /dev/null @@ -1,14 +0,0 @@ -/** - * <%= pkg.name %> - * <%= pkg.homepage %> - * - * <%= pkg.description %> - * - * @version <%= pkg.version %> - * @date <%= new Date() %> - * - * @license <%= pkg.license %> - * The MIT License - * - * Copyright (c) <%= new Date().getFullYear() %> gpu.js Team - */ \ No newline at end of file diff --git a/src/browser.js b/src/browser.js index ffea4a6f..9178f497 100644 --- a/src/browser.js +++ b/src/browser.js @@ -1,8 +1,70 @@ -const lib = require('./index'); -const GPU = lib.GPU; -for (const p in lib) { - if (!lib.hasOwnProperty(p)) continue; - if (p === 'GPU') continue; //prevent recursive reference - GPU[p] = lib[p]; -} -module.exports = GPU; \ No newline at end of file +import { GPU } from './base-gpu'; +import { alias } from './alias'; +import { utils } from './utils'; +import * as common from './common'; +import { Input, input } from './input'; +import { Texture } from './texture'; +import { FunctionBuilder } from './backend/function-builder'; +import { FunctionNode } from './backend/function-node'; +import { CPUFunctionNode } from './backend/cpu/function-node'; +import { CPUKernel } from './backend/cpu/kernel'; +import { WebGLFunctionNode } from './backend/web-gl/function-node'; +import { WebGLKernel } from './backend/web-gl/kernel'; +import { WebGL2FunctionNode } from './backend/web-gl2/function-node'; +import { WebGL2Kernel } from './backend/web-gl2/kernel'; +import { GLKernel } from './backend/gl/kernel'; +import { Kernel } from './backend/kernel'; + +/** + * Stub for HeadlessGL. + */ +class HeadlessGLKernel extends WebGLKernel { + static get isSupported() { return false } + static isContextMatch() { return false } + static getIsTextureFloat() { return false } + static getIsDrawBuffers() { return false } + static getChannelCount() { return 1 } + static get testCanvas() { return null } + static get testContext() { return null } + static get features() { return null } + static setupFeatureChecks() {} + static destroyContext() {} + initCanvas() { return {} } + initContext() { return null } + toString() { return '' } + initExtensions() {} + build() {} + destroyExtensions() {} + setOutput() {} + + static getFeatures() { + return Object.freeze({ + isFloatRead: false, + isIntegerDivisionAccurate: false, + isTextureFloat: false, + isDrawBuffers: false, + kernelMap: false, + channelCount: 1, + }); + } +}; + +const lib = GPU; +lib.alias = alias; +lib.CPUFunctionNode = CPUFunctionNode; +lib.CPUKernel = CPUKernel; +lib.FunctionBuilder = FunctionBuilder; +lib.FunctionNode = FunctionNode; +lib.HeadlessGLKernel = HeadlessGLKernel; +lib.Input = Input; +lib.input = input; +lib.Texture = Texture; +lib.utils = { ...common, ...utils }; +lib.WebGL2FunctionNode = WebGL2FunctionNode; +lib.WebGL2Kernel = WebGL2Kernel; +lib.WebGLFunctionNode = WebGLFunctionNode; +lib.WebGLKernel = WebGLKernel; +lib.GLKernel = GLKernel; +lib.Kernel = Kernel; + +export default lib; diff --git a/src/common.js b/src/common.js new file mode 100644 index 00000000..a780f77b --- /dev/null +++ b/src/common.js @@ -0,0 +1,139 @@ +/** + * @fileoverview Branch of utils to prevent circular dependencies. + */ + +const ARGUMENT_NAMES = /([^\s,]+)/g; +const FUNCTION_NAME = /function ([^(]*)/; +const STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; + +/** + * @descReturn TRUE, on a JS function + * @param {Function} funcObj - Object to validate if its a function + * @returns {Boolean} TRUE if the object is a JS function + */ +export function isFunction(funcObj) { + return typeof(funcObj) === 'function'; +}; + +/** + * @desc Return the function name from a JS function string + * @param {String} funcStr - String of JS function to validate + * @returns {String} Function name string (if found) + */ +export function getFunctionNameFromString(funcStr) { + return FUNCTION_NAME.exec(funcStr)[1].trim(); +}; + +/** + * + * @param {String|Function} source + * @param {IFunctionSettings} [settings] + * @returns {IFunction} + */ +export function functionToIFunction(source, settings) { + settings = settings || {}; + if (typeof source !== 'string' && typeof source !== 'function') throw new Error('source not a string or function'); + const sourceString = typeof source === 'string' ? source : source.toString(); + + let argumentTypes = []; + + if (Array.isArray(settings.argumentTypes)) { + argumentTypes = settings.argumentTypes; + } else if (typeof settings.argumentTypes === 'object') { + argumentTypes = getArgumentNamesFromString(sourceString) + .map(name => settings.argumentTypes[name]) || []; + } else { + argumentTypes = settings.argumentTypes || []; + } + + return { + source: sourceString, + argumentTypes, + returnType: settings.returnType || null, + }; +}; + +export function warnDeprecated(type, oldName, newName) { + const msg = newName + ? `It has been replaced with "${ newName }"` + : 'It has been removed'; + console.warn(`You are using a deprecated ${ type } "${ oldName }". ${msg}. Fixing, but please upgrade as it will soon be removed.`) +}; + +/** + * @desc Return TRUE, on a valid JS function string + * Note: This does just a VERY simply sanity check. And may give false positives. + * + * @param {String} fn - String of JS function to validate + * @returns {Boolean} TRUE if the string passes basic validation + */ +export function isFunctionString(fn) { + if (typeof fn === 'string') { + return (fn + .slice(0, 'function'.length) + .toLowerCase() === 'function'); + } + return false; +}; + +/** + * @desc Return list of argument names extracted from a javascript function + * @param {String} fn - String of JS function to validate + * @returns {String[]} Array representing all the parameter names + */ +export function getArgumentNamesFromString(fn) { + const fnStr = fn.replace(STRIP_COMMENTS, ''); + let result = fnStr.slice(fnStr.indexOf('(') + 1, fnStr.indexOf(')')).match(ARGUMENT_NAMES); + if (result === null) { + result = []; + } + return result; +}; + +/** + * @desc Checks if is an array or Array-like object + * @param {Object} array - The argument object to check if is array + * @returns {Boolean} true if is array or Array-like object + */ +export function isArray(array) { + return !isNaN(array.length); +}; + +export function erectMemoryOptimized2DFloat(array, width, height) { + const yResults = new Array(height); + for (let y = 0; y < height; y++) { + const offset = y * width; + yResults[y] = array.subarray(offset, offset + width); + } + return yResults; +}; + +export function erectMemoryOptimized3DFloat(array, width, height, depth) { + const zResults = new Array(depth); + for (let z = 0; z < depth; z++) { + const yResults = new Array(height); + for (let y = 0; y < height; y++) { + const offset = (z * height * width) + (y * width); + yResults[y] = array.subarray(offset, offset + width); + } + zResults[z] = yResults; + } + return zResults; +}; + +export function getAstString(source, ast) { + const lines = Array.isArray(source) ? source : source.split(/\r?\n/g); + const start = ast.loc.start; + const end = ast.loc.end; + const result = []; + if (start.line === end.line) { + result.push(lines[start.line - 1].substring(start.column, end.column)); + } else { + result.push(lines[start.line - 1].slice(start.column)); + for (let i = start.line; i < end.line; i++) { + result.push(lines[i]); + } + result.push(lines[end.line - 1].slice(0, end.column)); + } + return result.join('\n'); +}; diff --git a/src/gpu.js b/src/gpu.js index 12e3b768..24ab8e9a 100644 --- a/src/gpu.js +++ b/src/gpu.js @@ -1,579 +1,81 @@ -const { gpuMock } = require('gpu-mock.js'); -const { utils } = require('./utils'); -const { CPUKernel } = require('./backend/cpu/kernel'); -const { HeadlessGLKernel } = require('./backend/headless-gl/kernel'); -const { WebGL2Kernel } = require('./backend/web-gl2/kernel'); -const { WebGLKernel } = require('./backend/web-gl/kernel'); -const { kernelRunShortcut } = require('./kernel-run-shortcut'); - - -/** - * - * @type {Kernel[]} - */ -const kernelOrder = [HeadlessGLKernel, WebGL2Kernel, WebGLKernel]; - -/** - * - * @type {string[]} - */ -const kernelTypes = ['gpu', 'cpu']; - -const internalKernels = { - 'headlessgl': HeadlessGLKernel, - 'webgl2': WebGL2Kernel, - 'webgl': WebGLKernel, -}; - -let validate = true; - -/** - * The GPU.js library class which manages the GPU context for the creating kernels - */ -class GPU { - static disableValidation() { - validate = false; - } - - static enableValidation() { - validate = true; - } - - static get isGPUSupported() { - return kernelOrder.some(Kernel => Kernel.isSupported); - } - - /** - * - * @returns {boolean} - */ - static get isKernelMapSupported() { - return kernelOrder.some(Kernel => Kernel.isSupported && Kernel.features.kernelMap); - } - - /** - * @desc TRUE is platform supports OffscreenCanvas - */ - static get isOffscreenCanvasSupported() { - return (typeof Worker !== 'undefined' && typeof OffscreenCanvas !== 'undefined') || typeof importScripts !== 'undefined'; - } - - /** - * @desc TRUE if platform supports WebGL - */ - static get isWebGLSupported() { - return WebGLKernel.isSupported; - } - - /** - * @desc TRUE if platform supports WebGL2 - */ - static get isWebGL2Supported() { - return WebGL2Kernel.isSupported; - } - - /** - * @desc TRUE if platform supports HeadlessGL - */ - static get isHeadlessGLSupported() { - return HeadlessGLKernel.isSupported; - } - - /** - * - * @desc TRUE if platform supports Canvas - */ - static get isCanvasSupported() { - return typeof HTMLCanvasElement !== 'undefined'; - } - - /** - * @desc TRUE if platform supports HTMLImageArray} - */ - static get isGPUHTMLImageArraySupported() { - return WebGL2Kernel.isSupported; - } - - /** - * @desc TRUE if platform supports single precision} - * @returns {boolean} - */ - static get isSinglePrecisionSupported() { - return kernelOrder.some(Kernel => Kernel.isSupported && Kernel.features.isFloatRead && Kernel.features.isTextureFloat); - } - - /** - * Creates an instance of GPU. - * @param {IGPUSettings} [settings] - Settings to set mode, and other properties - */ - constructor(settings) { - settings = settings || {}; - this.canvas = settings.canvas || null; - this.context = settings.context || null; - this.mode = settings.mode; - this.Kernel = null; - this.kernels = []; - this.functions = []; - this.nativeFunctions = []; - this.injectedNative = null; - if (this.mode === 'dev') return; - this.chooseKernel(); - // add functions from settings - if (settings.functions) { - for (let i = 0; i < settings.functions.length; i++) { - this.addFunction(settings.functions[i]); - } - } - - // add native functions from settings - if (settings.nativeFunctions) { - for (const p in settings.nativeFunctions) { - this.addNativeFunction(p, settings.nativeFunctions[p]); - } - } - } - - /** - * Choose kernel type and save on .Kernel property of GPU - */ - chooseKernel() { - if (this.Kernel) return; - - let Kernel = null; - - if (this.context) { - for (let i = 0; i < kernelOrder.length; i++) { - const ExternalKernel = kernelOrder[i]; - if (ExternalKernel.isContextMatch(this.context)) { - if (!ExternalKernel.isSupported) { - throw new Error(`Kernel type ${ExternalKernel.name} not supported`); - } - Kernel = ExternalKernel; - break; - } - } - if (Kernel === null) { - throw new Error('unknown Context'); - } - } else if (this.mode) { - if (this.mode in internalKernels) { - if (!validate || internalKernels[this.mode].isSupported) { - Kernel = internalKernels[this.mode]; - } - } else if (this.mode === 'gpu') { - for (let i = 0; i < kernelOrder.length; i++) { - if (kernelOrder[i].isSupported) { - Kernel = kernelOrder[i]; - break; - } - } - } else if (this.mode === 'cpu') { - Kernel = CPUKernel; - } - if (!Kernel) { - throw new Error(`A requested mode of "${this.mode}" and is not supported`); - } - } else { - for (let i = 0; i < kernelOrder.length; i++) { - if (kernelOrder[i].isSupported) { - Kernel = kernelOrder[i]; - break; - } - } - if (!Kernel) { - Kernel = CPUKernel; - } - } - - if (!this.mode) { - this.mode = Kernel.mode; - } - this.Kernel = Kernel; - } - - /** - * @desc This creates a callable function object to call the kernel function with the argument parameter set - * @param {Function|String|object} source - The calling to perform the conversion - * @param {Object} [settings] - The parameter configuration object - * @return {Kernel} callable function to run - */ - createKernel(source, settings) { - if (typeof source === 'undefined') { - throw new Error('Missing source parameter'); - } - if (typeof source !== 'object' && !utils.isFunction(source) && typeof source !== 'string') { - throw new Error('source parameter not a function'); - } - - if (this.mode === 'dev') { - const devKernel = gpuMock(source, upgradeDeprecatedCreateKernelSettings(settings)); - this.kernels.push(devKernel); - return devKernel; - } - - source = typeof source === 'function' ? source.toString() : source; - const switchableKernels = {}; - const settingsCopy = upgradeDeprecatedCreateKernelSettings(settings) || {}; - // handle conversion of argumentTypes - if (settings && typeof settings.argumentTypes === 'object') { - settingsCopy.argumentTypes = Object.keys(settings.argumentTypes).map(argumentName => settings.argumentTypes[argumentName]); - } - - function onRequestFallback(args) { - const fallbackKernel = new CPUKernel(source, { - argumentTypes: kernelRun.argumentTypes, - constantTypes: kernelRun.constantTypes, - graphical: kernelRun.graphical, - loopMaxIterations: kernelRun.loopMaxIterations, - constants: kernelRun.constants, - dynamicOutput: kernelRun.dynamicOutput, - dynamicArgument: kernelRun.dynamicArguments, - output: kernelRun.output, - precision: kernelRun.precision, - pipeline: kernelRun.pipeline, - immutable: kernelRun.immutable, - optimizeFloatMemory: kernelRun.optimizeFloatMemory, - fixIntegerDivisionAccuracy: kernelRun.fixIntegerDivisionAccuracy, - functions: kernelRun.functions, - nativeFunctions: kernelRun.nativeFunctions, - injectedNative: kernelRun.injectedNative, - subKernels: kernelRun.subKernels, - strictIntegers: kernelRun.strictIntegers, - debug: kernelRun.debug, - warnVarUsage: kernelRun.warnVarUsage, - }); - fallbackKernel.build.apply(fallbackKernel, args); - const result = fallbackKernel.run.apply(fallbackKernel, args); - kernelRun.replaceKernel(fallbackKernel); - return result; - } - - function onRequestSwitchKernel(args, kernel) { - const argumentTypes = new Array(args.length); - for (let i = 0; i < args.length; i++) { - const arg = args[i]; - const type = kernel.argumentTypes[i]; - if (arg.type) { - argumentTypes[i] = arg.type; - } else { - switch (type) { - case 'Number': - case 'Integer': - case 'Float': - case 'ArrayTexture(1)': - argumentTypes[i] = utils.getVariableType(arg); - break; - default: - argumentTypes[i] = type; - } - } - } - const signature = argumentTypes.join(','); - const existingKernel = switchableKernels[signature]; - if (existingKernel) { - existingKernel.run.apply(existingKernel, args); - if (existingKernel.renderKernels) { - return existingKernel.renderKernels(); - } else { - return existingKernel.renderOutput(); - } - } - - const newKernel = switchableKernels[signature] = new kernel.constructor(source, { - argumentTypes, - constantTypes: kernel.constantTypes, - graphical: kernel.graphical, - loopMaxIterations: kernel.loopMaxIterations, - constants: kernel.constants, - dynamicOutput: kernel.dynamicOutput, - dynamicArgument: kernel.dynamicArguments, - context: kernel.context, - canvas: kernel.canvas, - output: kernel.output, - precision: kernel.precision, - pipeline: kernel.pipeline, - immutable: kernel.immutable, - optimizeFloatMemory: kernel.optimizeFloatMemory, - fixIntegerDivisionAccuracy: kernel.fixIntegerDivisionAccuracy, - functions: kernel.functions, - nativeFunctions: kernel.nativeFunctions, - injectedNative: kernel.injectedNative, - subKernels: kernel.subKernels, - strictIntegers: kernel.strictIntegers, - debug: kernel.debug, - gpu: kernel.gpu, - validate, - warnVarUsage: kernel.warnVarUsage, - returnType: kernel.returnType, - onRequestFallback, - onRequestSwitchKernel, - }); - newKernel.build.apply(newKernel, args); - newKernel.run.apply(newKernel, args); - kernelRun.replaceKernel(newKernel); - if (newKernel.renderKernels) { - return newKernel.renderKernels(); - } else { - return newKernel.renderOutput(); - } - } - const mergedSettings = Object.assign({ - context: this.context, - canvas: this.canvas, - functions: this.functions, - nativeFunctions: this.nativeFunctions, - injectedNative: this.injectedNative, - gpu: this, - validate, - onRequestFallback, - onRequestSwitchKernel - }, settingsCopy); - - const kernelRun = kernelRunShortcut(new this.Kernel(source, mergedSettings)); - - //if canvas didn't come from this, propagate from kernel - if (!this.canvas) { - this.canvas = kernelRun.canvas; - } - - //if context didn't come from this, propagate from kernel - if (!this.context) { - this.context = kernelRun.context; - } - - this.kernels.push(kernelRun); - - return kernelRun; - } - - /** - * - * Create a super kernel which executes sub kernels - * and saves their output to be used with the next sub kernel. - * This can be useful if we want to save the output on one kernel, - * and then use it as an input to another kernel. *Machine Learning* - * - * @param {Object|Array} subKernels - Sub kernels for this kernel - * @param {Function} rootKernel - Root kernel - * - * @returns {Function} callable kernel function - * - * @example - * const megaKernel = gpu.createKernelMap({ - * addResult: function add(a, b) { - * return a[this.thread.x] + b[this.thread.x]; - * }, - * multiplyResult: function multiply(a, b) { - * return a[this.thread.x] * b[this.thread.x]; - * }, - * }, function(a, b, c) { - * return multiply(add(a, b), c); - * }); - * - * megaKernel(a, b, c); - * - * Note: You can also define subKernels as an array of functions. - * > [add, multiply] - * - */ - createKernelMap() { - let fn; - let settings; - if (typeof arguments[arguments.length - 2] === 'function') { - fn = arguments[arguments.length - 2]; - settings = arguments[arguments.length - 1]; - } else { - fn = arguments[arguments.length - 1]; - } - - if (this.mode !== 'dev') { - if (!this.Kernel.isSupported || !this.Kernel.features.kernelMap) { - if (this.mode && kernelTypes.indexOf(this.mode) < 0) { - throw new Error(`kernelMap not supported on ${this.Kernel.name}`); - } - } - } - - const settingsCopy = upgradeDeprecatedCreateKernelSettings(settings); - // handle conversion of argumentTypes - if (settings && typeof settings.argumentTypes === 'object') { - settingsCopy.argumentTypes = Object.keys(settings.argumentTypes).map(argumentName => settings.argumentTypes[argumentName]); - } - - if (Array.isArray(arguments[0])) { - settingsCopy.subKernels = []; - const functions = arguments[0]; - for (let i = 0; i < functions.length; i++) { - const source = functions[i].toString(); - const name = utils.getFunctionNameFromString(source); - settingsCopy.subKernels.push({ - name, - source, - property: i, - }); - } - } else { - settingsCopy.subKernels = []; - const functions = arguments[0]; - for (let p in functions) { - if (!functions.hasOwnProperty(p)) continue; - const source = functions[p].toString(); - const name = utils.getFunctionNameFromString(source); - settingsCopy.subKernels.push({ - name: name || p, - source, - property: p, - }); - } - } - const kernel = this.createKernel(fn, settingsCopy); - - return kernel; - } - - /** - * - * Combine different kernels into one super Kernel, - * useful to perform multiple operations inside one - * kernel without the penalty of data transfer between - * cpu and gpu. - * - * The number of kernel functions sent to this method can be variable. - * You can send in one, two, etc. - * - * @param {Function} subKernels - Kernel function(s) to combine. - * @param {Function} rootKernel - Root kernel to combine kernels into - * - * @example - * combineKernels(add, multiply, function(a,b,c){ - * return add(multiply(a,b), c) - * }) - * - * @returns {Function} Callable kernel function - * - */ - combineKernels() { - const firstKernel = arguments[0]; - const combinedKernel = arguments[arguments.length - 1]; - if (firstKernel.kernel.constructor.mode === 'cpu') return combinedKernel; - const canvas = arguments[0].canvas; - const context = arguments[0].context; - const max = arguments.length - 1; - for (let i = 0; i < max; i++) { - arguments[i] - .setCanvas(canvas) - .setContext(context) - .setPipeline(true); - } - - return function() { - const texture = combinedKernel.apply(this, arguments); - if (texture.toArray) { - return texture.toArray(); - } - return texture; - }; - } - - /** - * @desc Adds additional functions, that the kernel may call. - * @param {Function|String} source - Javascript function to convert - * @param {IFunctionSettings} [settings] - * @returns {GPU} returns itself - */ - addFunction(source, settings) { - this.functions.push(utils.functionToIFunction(source, settings)); - return this; - } - - /** - * @desc Adds additional native functions, that the kernel may call. - * @param {String} name - native function name, used for reverse lookup - * @param {String} source - the native function implementation, as it would be defined in it's entirety - * @param {object} [settings] - * @returns {GPU} returns itself - */ - addNativeFunction(name, source, settings) { - if (this.kernels.length > 0) { - throw new Error('Cannot call "addNativeFunction" after "createKernels" has been called.'); - } - settings = settings || {}; - const { argumentTypes, argumentNames } = this.Kernel.nativeFunctionArguments(source) || {}; - this.nativeFunctions.push({ - name, - source, - settings, - argumentTypes, - argumentNames, - returnType: settings.returnType || this.Kernel.nativeFunctionReturnType(source), - }); - return this; - } - - /** - * Inject a string just before translated kernel functions - * @param {String} source - * @return {GPU} - */ - injectNative(source) { - this.injectedNative = source; - return this; - } - - /** - * @desc Destroys all memory associated with gpu.js & the webGl if we created it - */ - destroy() { - if (!this.kernels) return; - // perform on next run loop - for some reason we dont get lose context events - // if webGl is created and destroyed in the same run loop. - setTimeout(() => { - for (let i = 0; i < this.kernels.length; i++) { - this.kernels[i].destroy(true); // remove canvas if exists - } - // all kernels are associated with one context, go ahead and take care of it here - let firstKernel = this.kernels[0]; - if (firstKernel) { - // if it is shortcut - if (firstKernel.kernel) { - firstKernel = firstKernel.kernel; - } - if (firstKernel.constructor.destroyContext) { - firstKernel.constructor.destroyContext(this.context); - } - } - }, 0); - } -} - - -function upgradeDeprecatedCreateKernelSettings(settings) { - if (!settings) { - return {}; - } - const upgradedSettings = Object.assign({}, settings); - - if (settings.hasOwnProperty('floatOutput')) { - utils.warnDeprecated('setting', 'floatOutput', 'precision'); - upgradedSettings.precision = settings.floatOutput ? 'single' : 'unsigned'; - } - if (settings.hasOwnProperty('outputToTexture')) { - utils.warnDeprecated('setting', 'outputToTexture', 'pipeline'); - upgradedSettings.pipeline = Boolean(settings.outputToTexture); - } - if (settings.hasOwnProperty('outputImmutable')) { - utils.warnDeprecated('setting', 'outputImmutable', 'immutable'); - upgradedSettings.immutable = Boolean(settings.outputImmutable); - } - if (settings.hasOwnProperty('floatTextures')) { - utils.warnDeprecated('setting', 'floatTextures', 'optimizeFloatMemory'); - upgradedSettings.optimizeFloatMemory = Boolean(settings.floatTextures); - } - return upgradedSettings; -} - -module.exports = { - GPU, - kernelOrder, - kernelTypes -}; \ No newline at end of file +import { GPU as BaseGPU } from './base-gpu'; +import { HeadlessGLKernel } from './backend/headless-gl/kernel'; +import { CPUKernel } from './backend/cpu/kernel'; + +/** + * Extends the BaseGPU class to cover HeadlessGL instead of WebGL. + */ +export class GPU extends BaseGPU { + static get isGPUSupported() { + return HeadlessGLKernel.isSupported; + } + + static get isKernelMapSupported() { + return HeadlessGLKernel.isSupported && HeadlessGLKernel.features.kernelMap; + } + + static get isSinglePrecisionSupported() { + return HeadlessGLKernel.isSupported + && HeadlessGLKernel.features.isFloatRead + && HeadlessGLKernel.features.isTextureFloat; + } + + static get isWebGLSupported() { + return false; + } + + static get isWebGL2Supported() { + return false; + } + + static get isHeadlessGLSupported() { + return HeadlessGLKernel.isSupported; + } + + static get isGPUHTMLImageArraySupported() { + return false; + } + + chooseKernel() { + if (this.Kernel) return; + + let Kernel = null; + + if (this.context) { + if (HeadlessGLKernel.isContextMatch(this.context)) { + if (!HeadlessGLKernel.isSupported) { + throw new Error(`Kernel type ${HeadlessGLKernel.name} not supported`); + } + Kernel = HeadlessGLKernel; + } + if (Kernel === null) { + throw new Error('unknown Context'); + } + } else if (this.mode) { + if (this.mode === 'headlessgl') { + if (!this.getValidate() || HeadlessGLKernel.isSupported) { + Kernel = HeadlessGLKernel; + } + } else if (this.mode === 'gpu') { + if (HeadlessGLKernel.isSupported) { + Kernel = HeadlessGLKernel; + } + } else if (this.mode === 'cpu') { + Kernel = CPUKernel; + } + + if (!Kernel) { + throw new Error(`A requested mode of "${this.mode}" and is not supported`); + } + } else { + Kernel = HeadlessGLKernel.isSupported ? HeadlessGLKernel : CPUKernel; + } + + if (!this.mode) { + this.mode = Kernel.mode; + } + this.Kernel = Kernel; + } + + +}; diff --git a/src/index.js b/src/index.js index 95302ea9..9a8bd3c3 100644 --- a/src/index.js +++ b/src/index.js @@ -1,51 +1,45 @@ -const { GPU } = require('./gpu'); -const { alias } = require('./alias'); -const { utils } = require('./utils'); -const { Input, input } = require('./input'); -const { Texture } = require('./texture'); -const { FunctionBuilder } = require('./backend/function-builder'); -const { FunctionNode } = require('./backend/function-node'); -const { CPUFunctionNode } = require('./backend/cpu/function-node'); -const { CPUKernel } = require('./backend/cpu/kernel'); - -const { HeadlessGLKernel } = require('./backend/headless-gl/kernel'); - -const { WebGLFunctionNode } = require('./backend/web-gl/function-node'); -const { WebGLKernel } = require('./backend/web-gl/kernel'); -const { kernelValueMaps: webGLKernelValueMaps } = require('./backend/web-gl/kernel-value-maps'); - -const { WebGL2FunctionNode } = require('./backend/web-gl2/function-node'); -const { WebGL2Kernel } = require('./backend/web-gl2/kernel'); -const { kernelValueMaps: webGL2KernelValueMaps } = require('./backend/web-gl2/kernel-value-maps'); - -const { GLKernel } = require('./backend/gl/kernel'); - -const { Kernel } = require('./backend/kernel'); - -const { FunctionTracer } = require('./backend/function-tracer'); - -module.exports = { - alias, - CPUFunctionNode, - CPUKernel, - GPU, - FunctionBuilder, - FunctionNode, - HeadlessGLKernel, - Input, - input, - Texture, - utils, - - WebGL2FunctionNode, - WebGL2Kernel, - webGL2KernelValueMaps, - - WebGLFunctionNode, - WebGLKernel, - webGLKernelValueMaps, - - GLKernel, - Kernel, - FunctionTracer, -}; \ No newline at end of file +import { GPU } from './gpu'; +import { alias } from './alias'; +import * as common from './common'; +import { utils as util } from './utils'; +import { Input, input } from './input'; +import { Texture } from './texture'; +import { FunctionBuilder } from './backend/function-builder'; +import { FunctionNode } from './backend/function-node'; +import { CPUFunctionNode } from './backend/cpu/function-node'; +import { CPUKernel } from './backend/cpu/kernel'; +import { HeadlessGLKernel } from './backend/headless-gl/kernel'; +import { WebGLFunctionNode } from './backend/web-gl/function-node'; +import { WebGLKernel } from './backend/web-gl/kernel'; +import { WebGL2FunctionNode } from './backend/web-gl2/function-node'; +import { WebGL2Kernel } from './backend/web-gl2/kernel'; +import { GLKernel } from './backend/gl/kernel'; +import { Kernel } from './backend/kernel'; +import { kernelValueMaps as webGLKernelValueMaps } from './backend/web-gl/kernel-value-maps'; +import { kernelValueMaps as webGL2KernelValueMaps } from './backend/web-gl2/kernel-value-maps'; +import { FunctionTracer } from './backend/function-tracer'; + +const utils = { ...common, ...util }; + +export { + alias, + CPUFunctionNode, + CPUKernel, + GPU, + FunctionBuilder, + FunctionNode, + HeadlessGLKernel, + Input, + input, + Texture, + utils, + WebGL2FunctionNode, + WebGL2Kernel, + webGL2KernelValueMaps, + WebGLFunctionNode, + WebGLKernel, + webGLKernelValueMaps, + GLKernel, + Kernel, + FunctionTracer, +}; diff --git a/src/input.js b/src/input.js index 12f0c252..1b959487 100644 --- a/src/input.js +++ b/src/input.js @@ -1,54 +1,50 @@ -class Input { - constructor(value, size) { - this.value = value; - if (Array.isArray(size)) { - this.size = size; - } else { - this.size = new Int32Array(3); - if (size.z) { - this.size = new Int32Array([size.x, size.y, size.z]); - } else if (size.y) { - this.size = new Int32Array([size.x, size.y]); - } else { - this.size = new Int32Array([size.x]); - } - } - - const [w, h, d] = this.size; - if (d) { - if (this.value.length !== (w * h * d)) { - throw new Error(`Input size ${this.value.length} does not match ${w} * ${h} * ${d} = ${(h * w * d)}`); - } - } else if (h) { - if (this.value.length !== (w * h)) { - throw new Error(`Input size ${this.value.length} does not match ${w} * ${h} = ${(h * w)}`); - } - } else { - if (this.value.length !== w) { - throw new Error(`Input size ${this.value.length} does not match ${w}`); - } - } - - } - - toArray() { - const { utils } = require('./utils'); - const [w, h, d] = this.size; - if (d) { - return utils.erectMemoryOptimized3DFloat(this.value.subarray ? this.value : new Float32Array(this.value), w, h, d); - } else if (h) { - return utils.erectMemoryOptimized2DFloat(this.value.subarray ? this.value : new Float32Array(this.value), w, h); - } else { - return this.value; - } - } -} - -function input(value, size) { - return new Input(value, size); -} - -module.exports = { - Input, - input -}; \ No newline at end of file +import { erectMemoryOptimized2DFloat, erectMemoryOptimized3DFloat } from './common'; + +export class Input { + constructor(value, size) { + this.value = value; + if (Array.isArray(size)) { + this.size = size; + } else { + this.size = new Int32Array(3); + if (size.z) { + this.size = new Int32Array([size.x, size.y, size.z]); + } else if (size.y) { + this.size = new Int32Array([size.x, size.y]); + } else { + this.size = new Int32Array([size.x]); + } + } + + const [w, h, d] = this.size; + if (d) { + if (this.value.length !== (w * h * d)) { + throw new Error(`Input size ${this.value.length} does not match ${w} * ${h} * ${d} = ${(h * w * d)}`); + } + } else if (h) { + if (this.value.length !== (w * h)) { + throw new Error(`Input size ${this.value.length} does not match ${w} * ${h} = ${(h * w)}`); + } + } else { + if (this.value.length !== w) { + throw new Error(`Input size ${this.value.length} does not match ${w}`); + } + } + + } + + toArray() { + const [ w, h, d ] = this.size; + if (d) { + return erectMemoryOptimized3DFloat(this.value.subarray ? this.value : new Float32Array(this.value), w, h, d); + } else if (h) { + return erectMemoryOptimized2DFloat(this.value.subarray ? this.value : new Float32Array(this.value), w, h); + } else { + return this.value; + } + } +}; + +export function input(value, size) { + return new Input(value, size); +}; diff --git a/src/kernel-run-shortcut.js b/src/kernel-run-shortcut.js index 3739d0c7..88b41077 100644 --- a/src/kernel-run-shortcut.js +++ b/src/kernel-run-shortcut.js @@ -1,95 +1,92 @@ -const { utils } = require('./utils'); - -/** - * Makes kernels easier for mortals (including me) - * @param kernel - * @returns {function()} - */ -function kernelRunShortcut(kernel) { - let run = function() { - kernel.build.apply(kernel, arguments); - if (kernel.renderKernels) { - run = function() { - kernel.run.apply(kernel, arguments); - if (kernel.switchingKernels) { - kernel.switchingKernels = false; - return kernel.onRequestSwitchKernel(arguments, kernel); - } - return kernel.renderKernels(); - }; - } else if (kernel.renderOutput) { - run = function() { - kernel.run.apply(kernel, arguments); - if (kernel.switchingKernels) { - kernel.switchingKernels = false; - return kernel.onRequestSwitchKernel(arguments, kernel); - } - return kernel.renderOutput(); - }; - } else { - run = function() { - return kernel.run.apply(kernel, arguments); - }; - } - return run.apply(kernel, arguments); - }; - const shortcut = function() { - return run.apply(kernel, arguments); - }; - /** - * Run kernel in async mode - * @returns {Promise