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 {PromiseNote: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: