More refactoring towards es6

This commit is contained in:
Robert Plummer 2017-04-20 10:28:56 -04:00
parent 5c458c60c1
commit 8be5dcec0c
18 changed files with 1581 additions and 1533 deletions

View File

@ -0,0 +1,196 @@
const GPUUtils = require('../gpu-utils');
const parser = require('../parser').parser;
///
/// Class: CPUFunctionNode
///
/// [INTERNAL] Represents a single function, inside JS, webGL, or openGL.
///
/// This handles all the raw state, converted state, etc. Of a single function.
///
/// Properties:
/// functionName - {String} Name of the function
/// jsFunction - {JS Function} The JS Function the node represents
/// jsFunctionString - {String} jsFunction.toString()
/// paramNames - {[String,...]} Parameter names of the function
/// paramType - {[String,...]} Shader land parameter type assumption
/// isRootKernel - {Boolean} Special indicator, for kernel function
/// webglFunctionString - {String} webgl converted function string
/// openglFunctionString - {String} opengl converted function string
/// calledFunctions - {[String,...]} List of all the functions called
/// initVariables - {[String,...]} List of variables initialized in the function
/// readVariables - {[String,...]} List of variables read operations occur
/// writeVariables - {[String,...]} List of variables write operations occur
///
export default class CPUFunctionNode {
//
// Constructor
//----------------------------------------------------------------------------------------------------
///
/// Function: functionNode
///
/// [Constructor] Builds the function with the given JS function, and argument type array.
///
/// Parameters:
/// gpu - {GPU} The GPU instance
/// functionName - {String} Function name to assume, if its null, it attempts to extract from the function
/// jsFunction - {JS Function / String} JS Function to do conversion
/// paramTypeArray - {[String,...]} Parameter type array, assumes all parameters are 'float' if null
/// returnType - {String} The return type, assumes 'float' if null
///
constructor(functionName, jsFunction, paramTypeArray, returnType) {
//
// Internal vars setup
//
this.calledFunctions = [];
this.initVariables = [];
this.readVariables = [];
this.writeVariables = [];
//
// Missing jsFunction object exception
//
if(jsFunction == null) {
throw 'jsFunction, parameter is null';
}
//
// Setup jsFunction and its string property + validate them
//
this.jsFunctionString = jsFunction.toString();
if(!GPUUtils.isFunctionString(this.jsFunctionString)) {
console.error('jsFunction, to string conversion check failed: not a function?', this.jsFunctionString);
throw 'jsFunction, to string conversion check failed: not a function?';
}
if(!GPUUtils.isFunction(jsFunction)) {
//throw 'jsFunction, is not a valid JS Function';
this.jsFunction = null;
} else {
this.jsFunction = jsFunction;
}
//
// Setup the function name property
//
this.functionName = functionName
|| (jsFunction && jsFunction.name)
|| GPUUtils.getFunctionNameFromString(this.jsFunctionString);
if(!(this.functionName)) {
throw 'jsFunction, missing name argument or value';
}
//
// Extract parameter name, and its argument types
//
this.paramNames = GPUUtils.getParamNamesFromString(this.jsFunctionString);
if(paramTypeArray != null) {
if(paramTypeArray.length != this.paramNames.length) {
throw 'Invalid argument type array length, against function length -> ('+
paramTypeArray.length+','+
this.paramNames.length+
')';
}
this.paramType = paramTypeArray;
} else {
this.paramType = [];
for(let a = 0; a < this.paramNames.length; ++a) {
this.paramType.push('float');
}
}
//
// Return type handling
//
this.returnType = returnType || 'float';
}
//
// Core function
//----------------------------------------------------------------------------------------------------
///
/// Function: getJSFunction
///
/// Gets and return the stored JS Function.
/// Note: that this internally eval the function, if only the string was provided on construction
///
/// Returns:
/// {JS Function} The function object
///
getJsFunction() {
if(this.jsFunction) {
return this.jsFunction;
}
if(this.jsFunctionString) {
this.jsFunction = eval(this.jsFunctionString);
return this.jsFunction;
}
throw 'Missing jsFunction, and jsFunctionString parameter';
}
///
/// Function: getJS_AST
///
/// Parses the class function JS, and returns its Abstract Syntax Tree object.
///
/// This is used internally to convert to shader code
///
/// Parameters:
/// inParser - {JISON Parser} Parser to use, assumes in scope 'parser' if null
///
/// Returns:
/// {AST Object} The function AST Object, note that result is cached under this.jsFunctionAST;
///
getJsAST(inParser) {
if(this.jsFunctionAST) {
return this.jsFunctionAST;
}
inParser = inParser || parser;
if(inParser == null) {
throw 'Missing JS to AST parser';
}
const prasedObj = parser.parse('var '+this.functionName+' = '+this.jsFunctionString+';');
if(prasedObj === null) {
throw 'Failed to parse JS code via JISON';
}
// take out the function object, outside the var declarations
const funcAST = prasedObj.body[0].declarations[0].init;
this.jsFunctionAST = funcAST;
return funcAST;
}
///
/// Function: getFunctionString
///
/// Returns the converted webgl shader function equivalent of the JS function
///
/// Returns:
/// {String} webgl function string, result is cached under this.webglFunctionString
///
getFunctionString(opt) {
return this.functionString;
}
///
/// Function: setFunctionString
///
/// Set the functionString value, overwriting it
///
/// Parameters:
/// functionString - {String} Shader code string, representing the function
///
setFunctionString(functionString) {
this.functionString = functionString;
}
}

View File

@ -0,0 +1,92 @@
const GPUUtils = require('../gpu-utils');
export default class BaseKernel extends Function {
constructor(fnString, runner) {
const args = GPUUtils.getParamNamesFromString(fnString);
super(args, );
this.gpujs = runner;
this._dimensions = null;
this._debug = false;
this._graphical = false;
this._loopMaxIterations = 0;
this._constants = 0;
this._wraparound = null;
this._hardcodeConstants = null;
this._outputToTexture = null;
this._floatTextures = null;
this._floatOutput = null;
this._floatOutputForce = null;
this.texSize = null;
}
build() {
throw new Error('"build" not defined on Base');
}
dimensions(dim) {
this._dimensions = dim;
return this;
}
debug(flag) {
this._debug = flag;
return this;
}
graphical(flag) {
this._graphical = flag;
return this;
}
loopMaxIterations(max) {
this._loopMaxIterations = max;
return this;
}
constants(constants) {
this._constants = constants;
return this;
}
wraparound(flag) {
console.warn('Wraparound mode is not supported and undocumented.');
this._wraparound = flag;
return this;
}
hardcodeConstants(flag) {
this._hardcodeConstants = flag;
return this;
}
outputToTexture(flag) {
this._outputToTexture = flag;
return this;
}
floatTextures(flag) {
this._floatTextures = flag;
return this;
}
floatOutput(flag) {
this._floatOutput = flag;
return this;
}
floatOutputForce(flag) {
this._floatOutputForce = flag;
return this;
}
getCanvas() {
return this.canvas;
}
getWebgl() {
return this.webgl;
}
validateOptions() {
throw new Error('"validateOptions not defined');
}
}

View File

@ -0,0 +1,82 @@
const FunctionBuilder = require('../function-builder');
const GPUUtils = require('../../gpu-utils');
///
/// Class: Base
///
/// Represents the 'private/protected' namespace of the GPU class
///
/// *base.js* internal functions namespace
/// *gpu.js* PUBLIC function namespace
///
/// I know @private makes more sense, but since the documentation engine state is undetirmined.
/// (See https://github.com/gpujs/gpu.js/issues/19 regarding documentation engine issue)
/// File isolation is currently the best way to go
///
export default class BaseRunner {
constructor() {
this.kernel = null;
this.fn = null;
this.fnString = null;
this._canvas = GPUUtils.initCanvas();
this._webgl = GPUUtils.initWebGl(this._canvas);
this.programCache = {};
this.endianness = GPUUtils.systemEndianness();
this.functionBuilder = new FunctionBuilder(this);
this.functionBuilder.polyfillStandardFunctions();
}
// Legacy method to get webgl : Preseved for backwards compatibility
getGl() {
return this._webgl;
}
textureToArray(texture) {
const copy = this.createKernel(function(x) {
return x[this.thread.z][this.thread.y][this.thread.x];
});
return copy(texture);
}
deleteTexture(texture) {
this._webgl.deleteTexture(texture.texture);
}
setFn(fn) {
this.fn = fn;
this.fnString = fn.toString();
return this;
}
///
/// Get and returns the ASYNCHRONOUS executor, of a class and kernel
/// This returns a Promise object from an argument set.
///
/// Note that there is no current implementation.
///
buildPromiseKernel() {
throw new Error('not yet implemented');
}
get mode() {
throw new Error('"mode" not implemented on Base kernel');
}
///
/// Get and returns the Synchronous executor, of a class and kernel
/// Which returns the result directly after passing the arguments.
///
buildKernel() {
const fnString = this.fnString;
if( !GPUUtils.isFunctionString(fnString) ) {
throw 'Unable to get body of kernel function';
}
this.kernel = new this.Kernel(this.fnString);
this.kernel.build(this.fnString, this.fn);
}
}

View File

@ -0,0 +1,26 @@
const BaseFunctionNode = require('../base-function-node');
///
/// Class: functionNode
///
/// [INTERNAL] Represents a single function, inside JS, webGL, or openGL.
///
/// This handles all the raw state, converted state, etc. Of a single function.
///
/// Properties:
/// functionName - {String} Name of the function
/// jsFunction - {JS Function} The JS Function the node represents
/// jsFunctionString - {String} jsFunction.toString()
/// paramNames - {[String,...]} Parameter names of the function
/// paramType - {[String,...]} Shader land parameter type assumption
/// isRootKernel - {Boolean} Special indicator, for kernel function
/// webglFunctionString - {String} webgl converted function string
/// openglFunctionString - {String} opengl converted function string
/// calledFunctions - {[String,...]} List of all the functions called
/// initVariables - {[String,...]} List of variables initialized in the function
/// readVariables - {[String,...]} List of variables read operations occur
/// writeVariables - {[String,...]} List of variables write operations occur
///
export default class CPUFunctionNode extends BaseFunctionNode {
}

View File

@ -0,0 +1,120 @@
const BaseKernel = require('../base-kernel');
const GPUUtils = require('../../gpu-utils');
export default class CPUKernel extends BaseKernel {
validateOptions() {
if (!this._dimensions || this._dimensions.length === 0) {
if (arguments.length != 1) {
throw 'Auto dimensions only supported for kernels with only one input';
}
const argType = GPUUtils.getArgumentType(arguments[0]);
if (argType == 'Array') {
this._dimensions = GPUUtils.getDimensions(argType);
} else if (argType == 'Texture') {
this._dimensions = arguments[0].dimensions;
} else {
throw 'Auto dimensions not supported for input type: ' + argType;
}
}
}
build() {
const kernelArgs = [];
for (let i = 0; i < arguments.length; i++) {
const argType = GPUUtils.getArgumentType(arguments[i]);
if (argType == 'Array' || argType == 'Number') {
kernelArgs[i] = arguments[i];
} else if (argType == 'Texture') {
kernelArgs[i] = arguments[i].toArray();
} else {
throw 'Input type not supported (CPU): ' + arguments[i];
}
}
const threadDim = GPUUtils.clone(this._dimensions);
while (threadDim.length < 3) {
threadDim.push(1);
}
let ret = new Array(threadDim[2]);
for (let i = 0; i < threadDim[2]; i++) {
ret[i] = new Array(threadDim[1]);
for (let j = 0; j < threadDim[1]; j++) {
ret[i][j] = new Array(threadDim[0]);
}
}
const ctx = {
thread: {
x: 0,
y: 0,
z: 0
},
dimensions: {
x: threadDim[0],
y: threadDim[1],
z: threadDim[2]
},
constants: this._constants
};
let canvasCtx;
let imageData;
let data;
if (this._graphical) {
canvas.width = threadDim[0];
canvas.height = threadDim[1];
canvasCtx = canvas.getContext('2d');
imageData = canvasCtx.createImageData(threadDim[0], threadDim[1]);
data = new Uint8ClampedArray(threadDim[0] * threadDim[1] * 4);
ctx.color = function (r, g, b, a) {
if (a == undefined) {
a = 1.0;
}
r = Math.floor(r * 255);
g = Math.floor(g * 255);
b = Math.floor(b * 255);
a = Math.floor(a * 255);
const width = ctx.dimensions.x;
const height = ctx.dimensions.y;
const x = ctx.thread.x;
const y = height - ctx.thread.y - 1;
const index = x + y * width;
data[index * 4 + 0] = r;
data[index * 4 + 1] = g;
data[index * 4 + 2] = b;
data[index * 4 + 3] = a;
};
}
for (ctx.thread.z = 0; ctx.thread.z < threadDim[2]; ctx.thread.z++) {
for (ctx.thread.y = 0; ctx.thread.y < threadDim[1]; ctx.thread.y++) {
for (ctx.thread.x = 0; ctx.thread.x < threadDim[0]; ctx.thread.x++) {
ret[ctx.thread.z][ctx.thread.y][ctx.thread.x] = kernel.apply(ctx, kernelArgs);
}
}
}
if (this._graphical) {
imageData.data.set(data);
canvasCtx.putImageData(imageData, 0, 0);
}
if (this._dimensions.length == 1) {
ret = ret[0][0];
} else if (this._dimensions.length == 2) {
ret = ret[0];
}
return ret;
}
}

View File

@ -0,0 +1,35 @@
const GPUUtils = require('../../gpu-utils');
const BaseRunner = require('../base-runner');
export default class CPURunner extends BaseRunner {
constructor() {
super();
this._canvas = GPUUtils.initCanvas();
this.Kernel = CPUKernel;
this.kernel = null;
}
/// JS fallback transformation, basically pure JS
///
/// @param inputFunction The calling to perform the conversion
/// @param opt The parameter object
///
/// @returns callable function if converted, else returns null
buildKernel(fnString, fn) {
const kernel = this._kernelFunction;
const opt = this._kernelParamObj;
let canvas = gpu._canvasCpu;
if (!canvas) {
canvas = gpu._canvasCpu = GPUUtils.initCanvas();
}
ret.canvas = canvas;
return this.setupExecutorExtendedFunctions(ret, opt);
}
get mode() {
return 'cpu';
}
}

View File

@ -1,4 +1,4 @@
const FunctionNode = require('function-node');
const FunctionNode = require('./function-node');
///
/// Class: functionBuilder
///
@ -30,8 +30,8 @@ export default class FunctionBuilder {
/// gpu - {GPU} The GPU instance
/// functionName - {String} Function name to assume, if its null, it attempts to extract from the function
/// jsFunction - {JS Function} JS Function to do conversion
/// paramTypeArray - {[String,...]} Parameter type array, assumes all parameters are "float" if null
/// returnType - {String} The return type, assumes "float" if null
/// paramTypeArray - {[String,...]} Parameter type array, assumes all parameters are 'float' if null
/// returnType - {String} The return type, assumes 'float' if null
///
addFunction(functionName, jsFunction, paramTypeArray, returnType) {
this.addFunctionNode(new FunctionNode(this.gpu, functionName, jsFunction, paramTypeArray, returnType));
@ -46,7 +46,7 @@ export default class FunctionBuilder {
/// inNode - {functionNode} functionNode to add
///
addFunctionNode(inNode) {
this.nodeMap[ inNode.functionName ] = inNode;
this.nodeMap[inNode.functionName] = inNode;
}
///
@ -54,11 +54,11 @@ export default class FunctionBuilder {
///
/// Trace all the depending functions being called, from a single function
///
/// This allow for "uneeded" functions to be automatically optimized out.
/// This allow for 'uneeded' functions to be automatically optimized out.
/// Note that the 0-index, is the starting function trace.
///
/// Parameters:
/// functionName - {String} Function name to trace from, default to "kernel"
/// functionName - {String} Function name to trace from, default to 'kernel'
/// retList - {[String,...]} Returning list of function names that is traced. Including itself.
///
/// Returns:
@ -75,7 +75,7 @@ export default class FunctionBuilder {
} else {
retList.push(functionName);
fNode.getWebGlFunctionString(opt); //ensure JS trace is done
fNode.getFunctionString(opt); //ensure JS trace is done
for(let i = 0; i < fNode.calledFunctions.length; ++i) {
this.traceFunctionCalls(fNode.calledFunctions[i], retList, opt);
}
@ -99,10 +99,10 @@ export default class FunctionBuilder {
for(let i = 0; i < functionList.length; ++i) {
const node = this.nodeMap[functionList[i]];
if(node) {
ret.push(this.nodeMap[functionList[i]].getWebGlFunctionString(opt));
ret.push(this.nodeMap[functionList[i]].getFunctionString(opt));
}
}
return ret.join("\n");
return ret.join('\n');
}
webGlPrototypeStringFromFunctionNames(functionList, opt) {
@ -110,10 +110,10 @@ export default class FunctionBuilder {
for(let i = 0; i < functionList.length; ++i) {
const node = this.nodeMap[functionList[i]];
if(node) {
ret.push(this.nodeMap[functionList[i]].getWebGlFunctionPrototypeString(opt));
ret.push(this.nodeMap[functionList[i]].getFunctionPrototypeString(opt));
}
}
return ret.join("\n");
return ret.join('\n');
}
///
@ -163,14 +163,14 @@ export default class FunctionBuilder {
//---------------------------------------------------------
// Round function used in polyfill
round(a) { return Math.floor(a + 0.5); }
static round(a) { return Math.floor(a + 0.5); }
///
/// Function: polyfillStandardFunctions
///
/// Polyfill in the missing Math funcitons (round)
/// Polyfill in the missing Math functions (round)
///
polyfillStandardFunctions() {
this.addFunction(null, round);
this.addFunction(null, FunctionBuilder.round);
}
}

View File

@ -1,218 +0,0 @@
///
/// Class: functionNode
///
/// [INTERNAL] Represents a single function, inside JS, webGL, or openGL.
///
/// This handles all the raw state, converted state, etc. Of a single function.
///
/// Properties:
/// functionName - {String} Name of the function
/// jsFunction - {JS Function} The JS Function the node represents
/// jsFunctionString - {String} jsFunction.toString()
/// paramNames - {[String,...]} Parameter names of the function
/// paramType - {[String,...]} Shader land parameter type assumption
/// isRootKernel - {Boolean} Special indicator, for kernel function
/// webglFunctionString - {String} webgl converted function string
/// openglFunctionString - {String} opengl converted function string
/// calledFunctions - {[String,...]} List of all the functions called
/// initVariables - {[String,...]} List of variables initialized in the function
/// readVariables - {[String,...]} List of variables read operations occur
/// writeVariables - {[String,...]} List of variables write operations occur
///
export default class FunctionNode {
//
// Constructor
//----------------------------------------------------------------------------------------------------
///
/// Function: functionNode
///
/// [Constructor] Builds the function with the given JS function, and argument type array.
///
/// Parameters:
/// gpu - {GPU} The GPU instance
/// functionName - {String} Function name to assume, if its null, it attempts to extract from the function
/// jsFunction - {JS Function / String} JS Function to do conversion
/// paramTypeArray - {[String,...]} Parameter type array, assumes all parameters are 'float' if null
/// returnType - {String} The return type, assumes 'float' if null
///
constructor(gpu, functionName, jsFunction, paramTypeArray, returnType) {
this.gpu = gpu;
//
// Internal vars setup
//
this.calledFunctions = [];
this.initVariables = [];
this.readVariables = [];
this.writeVariables = [];
//
// Missing jsFunction object exception
//
if(jsFunction == null) {
throw 'jsFunction, parameter is null';
}
//
// Setup jsFunction and its string property + validate them
//
this.jsFunctionString = jsFunction.toString();
if(!GPUUtils.isFunctionString(this.jsFunctionString)) {
console.error('jsFunction, to string conversion check falied: not a function?', this.jsFunctionString);
throw 'jsFunction, to string conversion check falied: not a function?';
}
if(!GPUUtils.isFunction(jsFunction)) {
//throw 'jsFunction, is not a valid JS Function';
this.jsFunction = null;
} else {
this.jsFunction = jsFunction;
}
//
// Setup the function name property
//
this.functionName = functionName
|| (jsFunction && jsFunction.name)
|| GPUUtils.getFunctionNameFromString(this.jsFunctionString);
if(!(this.functionName)) {
throw 'jsFunction, missing name argument or value';
}
//
// Extract parameter name, and its argument types
//
this.paramNames = GPUUtils.getParamNamesFromString(this.jsFunctionString);
if(paramTypeArray != null) {
if(paramTypeArray.length != this.paramNames.length) {
throw 'Invalid argument type array length, against function length -> ('+
paramTypeArray.length+','+
this.paramNames.length+
')';
}
this.paramType = paramTypeArray;
} else {
this.paramType = [];
for(var a=0; a<this.paramNames.length; ++a) {
this.paramType.push('float');
}
}
//
// Return type handling
//
this.returnType = returnType || 'float';
}
//
// Core function
//----------------------------------------------------------------------------------------------------
///
/// Function: getJSFunction
///
/// Gets and return the stored JS Function.
/// Note: that this internally eval the function, if only the string was provided on construction
///
/// Returns:
/// {JS Function} The function object
///
getJsFunction() {
if(this.jsFunction) {
return this.jsFunction;
}
if(this.jsFunctionString) {
this.jsFunction = eval(this.jsFunctionString);
return this.jsFunction;
}
throw 'Missing jsFunction, and jsFunctionString parameter';
}
///
/// Function: getJS_AST
///
/// Parses the class function JS, and returns its Abstract Syntax Tree object.
///
/// This is used internally to convert to shader code
///
/// Parameters:
/// inParser - {JISON Parser} Parser to use, assumes in scope 'parser' if null
///
/// Returns:
/// {AST Object} The function AST Object, note that result is cached under this.jsFunctionAST;
///
getJsAST(inParser) {
if(this.jsFunctionAST) {
return this.jsFunctionAST;
}
inParser = inParser || parser;
if(inParser == null) {
throw 'Missing JS to AST parser';
}
var prasedObj = parser.parse('var '+this.functionName+' = '+this.jsFunctionString+';');
if(prasedObj === null) {
throw 'Failed to parse JS code via JISON';
}
// take out the function object, outside the var declarations
var funcAST = prasedObj.body[0].declarations[0].init;
this.jsFunctionAST = funcAST;
return funcAST;
}
///
/// Function: getWebglFunctionString
///
/// Returns the converted webgl shader function equivalent of the JS function
///
/// Returns:
/// {String} webgl function string, result is cached under this.webglFunctionString
///
getWebGlFunctionString(opt) {
if(this.webglFunctionString) {
return this.webglFunctionString;
}
return this.webglFunctionString = this.functionNodeWebGl(this, opt);
}
///
/// Function: getWebglFunctionPrototypeString
///
/// Returns the converted webgl shader function equivalent of the JS function
///
/// Returns:
/// {String} webgl function string, result is cached under this.getWebglFunctionPrototypeString
///
getWebGlFunctionPrototypeString(opt) {
opt = opt || {};
if(this.webGlFunctionPrototypeString) {
return this.webGlFunctionPrototypeString;
}
return this.webglFunctionPrototypeString = this.functionNodeWebGl(this, {
prototypeOnly:true,
isRootKernel: opt.isRootKernel
});
}
///
/// Function: setWebglString
///
/// Set the webglFunctionString value, overwriting it
///
/// Parameters:
/// shaderCode - {String} Shader code string, representing the function
///
setWebGlFunctionString(shaderCode) {
this.webGlFunctionString = shaderCode;
}
}

View File

@ -1,185 +0,0 @@
const FunctionBuilder = require('function-builder');
const GPUUtils = require('../gpu-utils');
///
/// Class: GPUCore
///
/// Represents the "private/protected" namespace of the GPU class
///
/// *GPUCore.js* internal functions namespace
/// *gpu.js* PUBLIC function namespace
///
/// I know @private makes more sense, but since the documentation engine state is undetirmined.
/// (See https://github.com/gpujs/gpu.js/issues/19 regarding documentation engine issue)
/// File isolation is currently the best way to go
///
export default class GPUCore {
constructor() {
// var gl, canvas;
//
// canvas = undefined;
// if (gl === undefined) {
// canvas = GPUUtils.init_canvas();
// gl = GPUUtils.init_webgl(canvas);
// }
//
// this.webgl = gl;
// this.canvas = canvas;
this.programCache = {};
this.endianness = GPUUtils.systemEndianness();
this.functionBuilder = new FunctionBuilder(this);
this.functionBuilder.polyfillStandardFunctions();
}
// Legacy method to get webgl : Preseved for backwards compatibility
getGl() {
return this._webgl;
}
textureToArray(texture) {
var copy = this.createKernel(function(x) {
return x[this.thread.z][this.thread.y][this.thread.x];
});
return copy(texture);
}
deleteTexture(texture) {
var gl = this._webgl;
gl.deleteTexture(texture.texture);
}
///
/// Get and returns the Synchronous executor, of a class and kernel
/// Which returns the result directly after passing the arguments.
///
getSynchronousModeExecutor() {
var kernel = this._kernelFunction;
var paramObj = this._kernelParamObj;
paramObj.dimensions = paramObj.dimensions || [];
var mode = this.computeMode;
//
// CPU mode
//
if ( mode == "cpu" ) {
return this._mode_cpu(kernel, paramObj);
}
//
// Attempts to do the glsl conversion
//
try {
return this._mode_gpu(kernel, paramObj);
} catch (e) {
if ( mode != "gpu") {
console.warning("Falling back to CPU!");
this.computeMode = mode = "cpu";
return this._mode_cpu(kernel, paramObj);
} else {
throw e;
}
}
}
///
/// Get and returns the ASYNCRONUS executor, of a class and kernel
/// This returns a Promise object from an argument set.
///
/// Note that there is no current implmentation.
///
getPromiseModeExecutor() {
return null;
}
///
/// Prepare the synchrnous mode executor,
/// With additional functionalities attached (standard)
///
/// Parameters:
/// ret {JS Function} Function object to extend
/// opt {Object} Options object
///
/// Returns:
/// {JS Function} the same ret object
///
setupExecutorExtendedFunctions(ret, opt) {
var gpu = this;
// Allow original class object reference from kernel
ret.gpujs = gpu;
ret.dimensions = function(dim) {
opt.dimensions = dim;
return ret;
};
ret.debug = function(flag) {
opt.debug = flag;
return ret;
};
ret.graphical = function(flag) {
opt.graphical = flag;
return ret;
};
ret.loopMaxIterations = function(max) {
opt.loopMaxIterations = max;
return ret;
};
ret.constants = function(constants) {
opt.constants = constants;
return ret;
};
ret.wraparound = function(flag) {
console.warn("Wraparound mode is not supported and undocumented.");
opt.wraparound = flag;
return ret;
};
ret.hardcodeConstants = function(flag) {
opt.hardcodeConstants = flag;
return ret;
};
ret.outputToTexture = function(flag) {
opt.outputToTexture = flag;
return ret;
};
ret.floatTextures = function(flag) {
opt.floatTextures = flag;
return ret;
};
ret.floatOutput = function(flag) {
opt.floatOutput = flag;
return ret;
};
ret.mode = function(mode) {
opt.mode = mode;
return gpu.createKernel(
gpu._kernelFunction,
gpu._kernelParamObj
);
};
ret.getCanvas = function(mode) {
return ret.canvas;
};
ret.getWebgl = function(mode) {
return ret.webgl;
};
return ret;
}
}

View File

@ -0,0 +1,520 @@
const BaseKernel = require('../base-kernel');
const GPUUtils = require('../../gpu-utils');
const GPUTexture = require('../../gpu-texture');
const GPUFunctionNode = require('./gpu-function-node');
export default class GPUKernel extends BaseKernel {
validateOptions() {
const isReadPixel = GPUUtils.isFloatReadPixelsSupported(this.gpujs);
if (this._floatTextures === true && !GPUUtils.OES_texture_float) {
throw 'Float textures are not supported on this browser';
} else if (this._floatOutput === true && this._floatOutputForce !== true && !isReadPixel) {
throw 'Float texture outputs are not supported on this browser';
} else if (this._floatTextures === undefined && GPUUtils.OES_texture_float) {
this._floatTextures = true;
this._floatOutput = isReadPixel && !this._graphical;
}
if (!this._dimensions || this._dimensions.length === 0) {
if (arguments.length != 1) {
throw 'Auto dimensions only supported for kernels with only one input';
}
const argType = GPUUtils.getArgumentType(arguments[0]);
if (argType == "Array") {
this._dimensions = GPUUtils.getDimensions(argType);
} else if (argType == "Texture") {
this._dimensions = arguments[0]._dimensions;
} else {
throw 'Auto dimensions not supported for input type: ' + argType;
}
}
this.texSize = GPUUtils.dimToTexSize(this, this._dimensions, true);
if (this._graphical) {
if (this._dimensions.length != 2) {
throw 'Output must have 2 dimensions on graphical mode';
}
if (this._floatOutput) {
throw 'Cannot use graphical mode and float output at the same time';
}
this.texSize = GPUUtils.clone(this._dimensions);
} else if (this._floatOutput === undefined && GPUUtils.OES_texture_float) {
this._floatOutput = true;
}
}
build(fnString, fn) {
this.validateOptions();
const paramNames = GPUUtils.getParamNamesFromString(fnString);
const vertices = new Float32Array([
-1, -1,
1, -1,
-1, 1,
1, 1]);
const texCoords = new Float32Array([
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
1.0, 1.0]);
const builder = this.functionBuilder;
const endianness = this.endianness;
const texSize = this.texSize;
this.canvas.width = texSize[0];
this.canvas.height = texSize[1];
gl.viewport(0, 0, texSize[0], texSize[1]);
const threadDim = GPUUtils.clone(this._dimensions);
while (threadDim.length < 3) {
threadDim.push(1);
}
if (program === undefined) {
let constantsStr = '';
if (this._constants) {
for (let name in this._constants) {
let value = parseFloat(this._constants[name]);
if (Number.isInteger(value)) {
constantsStr += 'const float constants_' + name + '=' + parseInt(value) + '.0;\n';
} else {
constantsStr += 'const float constants_' + name + '=' + parseFloat(value) + ';\n';
}
}
}
let paramStr = '';
const paramType = [];
for (let i = 0; i < paramNames.length; i++) {
const argType = GPUUtils.getArgumentType(arguments[i]);
paramType.push(argType);
if (this._hardcodeConstants) {
if (argType == "Array" || argType == "Texture") {
const paramDim = GPUUtils.getDimensions(arguments[i], true);
const paramSize = GPUUtils.dimToTexSize(gpu, paramDim);
paramStr += 'uniform highp sampler2D user_' + paramNames[i] + ';\n';
paramStr += 'highp vec2 user_' + paramNames[i] + 'Size = vec2(' + paramSize[0] + '.0, ' + paramSize[1] + '.0);\n';
paramStr += 'highp vec3 user_' + paramNames[i] + 'Dim = vec3(' + paramDim[0] + '.0, ' + paramDim[1] + '.0, ' + paramDim[2] + '.0);\n';
} else if (argType == "Number" && Number.isInteger(arguments[i])) {
paramStr += 'highp float user_' + paramNames[i] + ' = ' + arguments[i] + '.0;\n';
} else if (argType == "Number") {
paramStr += 'highp float user_' + paramNames[i] + ' = ' + arguments[i] + ';\n';
}
} else {
if (argType == "Array" || argType == "Texture") {
paramStr += 'uniform highp sampler2D user_' + paramNames[i] + ';\n';
paramStr += 'uniform highp vec2 user_' + paramNames[i] + 'Size;\n';
paramStr += 'uniform highp vec3 user_' + paramNames[i] + 'Dim;\n';
} else if (argType == "Number") {
paramStr += 'uniform highp float user_' + paramNames[i] + ';\n';
}
}
}
const kernelNode = new GPUFunctionNode(gpu, "kernel", kernel);
kernelNode.paramNames = paramNames;
kernelNode.paramType = paramType;
kernelNode.isRootKernel = true;
builder.addFunctionNode(kernelNode);
const vertShaderSrc = `
precision highp float;
precision highp int;
precision highp sampler2D;
attribute highp vec2 aPos;
attribute highp vec2 aTexCoord;
varying highp vec2 vTexCoord;
void main(void) {
gl_Position = vec4(aPos, 0, 1);
vTexCoord = aTexCoord;
}
`;
const fragShaderSrc = `
precision highp float;
precision highp int;
precision highp sampler2D;
#define LOOP_MAX ${ (this._loopMaxIterations ? parseInt(this._loopMaxIterations)+'.0' : '100.0') };
#define EPSILON 0.0000001;
${ this._hardcodeConstants
? `highp vec3 uOutputDim = vec3(${ threadDim[0] },${ threadDim[1] }, ${ threadDim[2] });`
: 'uniform highp vec3 uOutputDim};'
}
${ this._hardcodeConstants
? `highp vec2 uTexSize = vec2(${ texSize[0] }, ${ texSize[1] });`
: 'uniform highp vec2 uTexSize;'
}
varying highp vec2 vTexCoord;
vec4 round(vec4 x) {
return floor(x + 0.5);
}
highp float round(highp float x) {
return floor(x + 0.5);
}
vec2 integerMod(vec2 x, float y) {
vec2 res = floor(mod(x, y));
return res * step(1.0 - floor(y), -res);
}
vec3 integerMod(vec3 x, float y) {
vec3 res = floor(mod(x, y));
return res * step(1.0 - floor(y), -res);
}
vec4 integerMod(vec4 x, vec4 y) {
vec4 res = floor(mod(x, y));
return res * step(1.0 - floor(y), -res);
}
highp float integerMod(highp float x, highp float y) {
highp float res = floor(mod(x, y));
return res * (res > floor(y) - 1.0 ? 0.0 : 1.0);
}
highp int integerMod(highp int x, highp int y) {
return int(integerMod(float(x), float(y)));
}
// Here be dragons!
// DO NOT OPTIMIZE THIS CODE
// YOU WILL BREAK SOMETHING ON SOMEBODY\'S MACHINE
// LEAVE IT AS IT IS, LEST YOU WASTE YOUR OWN TIME
const vec2 MAGIC_VEC = vec2(1.0, -256.0);
const vec4 SCALE_FACTOR = vec4(1.0, 256.0, 65536.0, 0.0);
const vec4 SCALE_FACTOR_INV = vec4(1.0, 0.00390625, 0.0000152587890625, 0.0); // 1, 1/256, 1/65536
highp float decode32(highp vec4 rgba) {
${ endianness == 'LE'
? ''
: 'rgba.rgba = rgba.abgr;'
}
rgba *= 255.0;
vec2 gte128;
gte128.x = rgba.b >= 128.0 ? 1.0 : 0.0;
gte128.y = rgba.a >= 128.0 ? 1.0 : 0.0;
float exponent = 2.0 * rgba.a - 127.0 + dot(gte128, MAGIC_VEC);
float res = exp2(round(exponent));
rgba.b = rgba.b - 128.0 * gte128.x;
res = dot(rgba, SCALE_FACTOR) * exp2(round(exponent-23.0)) + res;
res *= gte128.y * -2.0 + 1.0;
return res;
}
highp vec4 encode32(highp float f) {
highp float F = abs(f);
highp float sign = f < 0.0 ? 1.0 : 0.0;
highp float exponent = floor(log2(F));
highp float mantissa = (exp2(-exponent) * F);
// exponent += floor(log2(mantissa));
vec4 rgba = vec4(F * exp2(23.0-exponent)) * SCALE_FACTOR_INV;
rgba.rg = integerMod(rgba.rg, 256.0);
rgba.b = integerMod(rgba.b, 128.0);
rgba.a = exponent*0.5 + 63.5;
rgba.ba += vec2(integerMod(exponent+127.0, 2.0), sign) * 128.0;
rgba = floor(rgba);
rgba *= 0.003921569; // 1/255
${ endianness == 'LE'
? ''
: 'rgba.rgba = rgba.abgr;'
}
return rgba;',
}
// Dragons end here
highp float index;
highp vec3 threadId;
highp vec3 indexTo3D(highp float idx, highp vec3 texDim) {
highp float z = floor(idx / (texDim.x * texDim.y));
idx -= z * texDim.x * texDim.y;
highp float y = floor(idx / texDim.x);
highp float x = integerMod(idx, texDim.x);
return vec3(x, y, z);
}
highp float get(highp sampler2D tex, highp vec2 texSize, highp vec3 texDim, highp float z, highp float y, highp float x) {
highp vec3 xyz = vec3(x, y, z);
xyz = floor(xyz + 0.5);
${ this._wraparound
? ' xyz = mod(xyz, texDim);'
: ''
}
highp float index = round(xyz.x + texDim.x * (xyz.y + texDim.y * xyz.z));
${ this._floatTextures ? ' int channel = int(integerMod(index, 4.0));' : '' }
${ this._floatTextures ? ' index = float(int(index)/4);' : ''}
highp float w = round(texSize.x);
vec2 st = vec2(integerMod(index, w), float(int(index) / int(w))) + 0.5;
${ this._floatTextures ? ' index = float(int(index)/4);' : ''}
highp vec4 texel = texture2D(tex, st / texSize);
${ this._floatTextures ? ' if (channel == 0) return texel.r;' : '' }
${ this._floatTextures ? ' if (channel == 1) return texel.g;' : '' }
${ this._floatTextures ? ' if (channel == 2) return texel.b;' : '' }
${ this._floatTextures ? ' if (channel == 3) return texel.a;' : '' }
${ this._floatTextures ? '' : ' return decode32(texel);' }
}
highp float get(highp sampler2D tex, highp vec2 texSize, highp vec3 texDim, highp float y, highp float x) {
return get(tex, texSize, texDim, 0.0, y, x);
}
highp float get(highp sampler2D tex, highp vec2 texSize, highp vec3 texDim, highp float x) {
return get(tex, texSize, texDim, 0.0, 0.0, x);
}
const bool outputToColor = ${ this._graphical ? 'true' : 'false' };
highp vec4 actualColor;
void color(float r, float g, float b, float a) {
actualColor = vec4(r,g,b,a);
}
void color(float r, float g, float b) {
color(r,g,b,1.0);
}
highp float kernelResult = 0.0;
${ paramStr }
${ constantsStr }
builder.webGlPrototypeString("kernel", opt)
builder.webGlString("kernel", opt)
void main(void) {
index = floor(vTexCoord.s * float(uTexSize.x)) + floor(vTexCoord.t * float(uTexSize.y)) * uTexSize.x;
${ this._floatOutput ? 'index *= 4.0;' : '' }
threadId = indexTo3D(index, uOutputDim);
kernel();
if (outputToColor == true) {
gl_FragColor = actualColor;
} else {
${ this._floatOutput ? '' : 'gl_FragColor = encode32(kernelResult);' }
${ this._floatOutput ? 'gl_FragColor.r = kernelResult;' : '' }
${ this._floatOutput ? 'index += 1.0; threadId = indexTo3D(index, uOutputDim); kernel();' : '' }
${ this._floatOutput ? 'gl_FragColor.g = kernelResult;' : '' }
${ this._floatOutput ? 'index += 1.0; threadId = indexTo3D(index, uOutputDim); kernel();' : '' }
${ this._floatOutput ? 'gl_FragColor.b = kernelResult;' : '' }
${ this._floatOutput ? 'index += 1.0; threadId = indexTo3D(index, uOutputDim); kernel();' : '' }
${ this._floatOutput ? 'gl_FragColor.a = kernelResult;' : '' }
}
}`;
const vertShader = gl.createShader(gl.VERTEX_SHADER);
const fragShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(vertShader, vertShaderSrc);
gl.shaderSource(fragShader, fragShaderSrc);
gl.compileShader(vertShader);
gl.compileShader(fragShader);
if (!gl.getShaderParameter(vertShader, gl.COMPILE_STATUS)) {
console.log(vertShaderSrc);
console.error("An error occurred compiling the shaders: " + gl.getShaderInfoLog(vertShader));
throw "Error compiling vertex shader";
}
if (!gl.getShaderParameter(fragShader, gl.COMPILE_STATUS)) {
console.log(fragShaderSrc);
console.error("An error occurred compiling the shaders: " + gl.getShaderInfoLog(fragShader));
throw "Error compiling fragment shader";
}
if (this._debug) {
console.log('Options:');
console.dir(opt);
console.log('GLSL Shader Output:');
console.log(fragShaderSrc);
}
const program = this.program = gl.createProgram();
gl.attachShader(program, vertShader);
gl.attachShader(program, fragShader);
gl.linkProgram(program);
programCache[programCacheKey] = program;
programUniformLocationCache[programCacheKey] = [];
}
gl.useProgram(program);
const texCoordOffset = vertices.byteLength;
let buffer = bufferCache[programCacheKey];
if (!buffer) {
buffer = gl.createBuffer();
bufferCache[programCacheKey] = buffer;
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices.byteLength + texCoords.byteLength, gl.STATIC_DRAW);
} else {
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
}
gl.bufferSubData(gl.ARRAY_BUFFER, 0, vertices);
gl.bufferSubData(gl.ARRAY_BUFFER, texCoordOffset, texCoords);
var aPosLoc = gl.getAttribLocation(program, "aPos");
gl.enableVertexAttribArray(aPosLoc);
gl.vertexAttribPointer(aPosLoc, 2, gl.FLOAT, gl.FALSE, 0, 0);
var aTexCoordLoc = gl.getAttribLocation(program, "aTexCoord");
gl.enableVertexAttribArray(aTexCoordLoc);
gl.vertexAttribPointer(aTexCoordLoc, 2, gl.FLOAT, gl.FALSE, 0, texCoordOffset);
if (!this._hardcodeConstants) {
var uOutputDimLoc = this.getUniformLocation("uOutputDim");
gl.uniform3fv(uOutputDimLoc, threadDim);
var uTexSizeLoc = this.getUniformLocation("uTexSize");
gl.uniform2fv(uTexSizeLoc, texSize);
}
if (!textureCache[programCacheKey]) {
textureCache[programCacheKey] = [];
}
let textureCount = 0;
for (textureCount=0; textureCount<paramNames.length; textureCount++) {
let paramDim, paramSize, texture;
var argType = GPUUtils.getArgumentType(arguments[textureCount]);
if (argType == "Array") {
paramDim = GPUUtils.getDimensions(arguments[textureCount], true);
paramSize = GPUUtils.dimToTexSize(opt, paramDim);
if (textureCache[programCacheKey][textureCount]) {
texture = textureCache[programCacheKey][textureCount];
} else {
texture = gl.createTexture();
textureCache[programCacheKey][textureCount] = texture;
}
gl.activeTexture(gl["TEXTURE"+textureCount]);
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);
var paramLength = paramSize[0] * paramSize[1];
if (this._floatTextures) {
paramLength *= 4;
}
var paramArray = new Float32Array(paramLength);
paramArray.set(flatten(arguments[textureCount]));
var argBuffer;
if (this._floatTextures) {
argBuffer = new Float32Array(paramArray);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, paramSize[0], paramSize[1], 0, gl.RGBA, gl.FLOAT, argBuffer);
} else {
argBuffer = new Uint8Array((new Float32Array(paramArray)).buffer);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, paramSize[0], paramSize[1], 0, gl.RGBA, gl.UNSIGNED_BYTE, argBuffer);
}
var paramLoc = this.getUniformLocation("user_" + paramNames[textureCount]);
var paramSizeLoc = this.getUniformLocation("user_" + paramNames[textureCount] + "Size");
var paramDimLoc = this.getUniformLocation("user_" + paramNames[textureCount] + "Dim");
if (!this._hardcodeConstants) {
gl.uniform3fv(paramDimLoc, paramDim);
gl.uniform2fv(paramSizeLoc, paramSize);
}
gl.uniform1i(paramLoc, textureCount);
} else if (argType == "Number") {
var argLoc = this.getUniformLocation("user_"+paramNames[textureCount]);
gl.uniform1f(argLoc, arguments[textureCount]);
} else if (argType == "Texture") {
paramDim = this.getDimensions(arguments[textureCount], true);
paramSize = arguments[textureCount].size;
texture = arguments[textureCount].texture;
gl.activeTexture(gl["TEXTURE"+textureCount]);
gl.bindTexture(gl.TEXTURE_2D, texture);
var paramLoc = this.getUniformLocation("user_" + paramNames[textureCount]);
var paramSizeLoc = this.getUniformLocation("user_" + paramNames[textureCount] + "Size");
var paramDimLoc = this.getUniformLocation("user_" + paramNames[textureCount] + "Dim");
gl.uniform3fv(paramDimLoc, paramDim);
gl.uniform2fv(paramSizeLoc, paramSize);
gl.uniform1i(paramLoc, textureCount);
} else {
throw "Input type not supported (GPU): " + arguments[textureCount];
}
}
var outputTexture = textureCache[programCacheKey][textureCount];
if (!outputTexture) {
outputTexture = gl.createTexture();
textureCache[programCacheKey][textureCount] = outputTexture;
}
gl.activeTexture(gl["TEXTURE"+textureCount]);
gl.bindTexture(gl.TEXTURE_2D, outputTexture);
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._floatOutput) {
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._graphical) {
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
return;
}
var framebuffer = framebufferCache[programCacheKey];
if (!framebuffer) {
framebuffer = gl.createFramebuffer();
framebufferCache[programCacheKey] = framebuffer;
}
framebuffer.width = texSize[0];
framebuffer.height = texSize[1];
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, outputTexture, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
if (this._outputToTexture) {
// Don't retain a handle on the output texture, we might need to render on the same texture later
delete textureCache[programCacheKey][textureCount];
return new GPUTexture(gpu, outputTexture, texSize, this._dimensions);
} else {
var result;
if (this._floatOutput) {
result = new Float32Array(texSize[0]*texSize[1]*4);
gl.readPixels(0, 0, texSize[0], texSize[1], gl.RGBA, gl.FLOAT, result);
} else {
var bytes = new Uint8Array(texSize[0]*texSize[1]*4);
gl.readPixels(0, 0, texSize[0], texSize[1], gl.RGBA, gl.UNSIGNED_BYTE, bytes);
result = Float32Array.prototype.slice.call(new Float32Array(bytes.buffer));
}
result = result.subarray(0, threadDim[0] * threadDim[1] * threadDim[2]);
if (this._dimensions.length == 1) {
return result;
} else if (this._dimensions.length == 2) {
return GPUUtils.splitArray(result, this._dimensions[0]);
} else if (this._dimensions.length == 3) {
const cube = GPUUtils.splitArray(result, this._dimensions[0] * this._dimensions[1]);
return cube.map(function(x) {
return GPUUtils.splitArray(x, this._dimensions[0]);
});
}
}
}
}

View File

@ -0,0 +1,67 @@
const BaseRunner = require('../base-runner');
const GPUUtils = require('../../gpu-utils');
const GPUKernel = require('./gpu-kernel');
export default class GPURunner extends BaseRunner {
constructor(opt) {
super();
this.programUniformLocationCache = {};
this.programCacheKey = this.getProgramCacheKey(arguments, opt, opt.dimensions);
this.programCache = {};
this.bufferCache = {};
this.textureCache = {};
this.framebufferCache = {};
this.Kernel = GPUKernel;
this.kernel = null;
}
getProgramCacheKey(args, opt, outputDim) {
var key = '';
for (var i=0; i<args.length; i++) {
var argType = GPUUtils.getArgumentType(args[i]);
key += argType;
if (opt.hardcodeConstants) {
var dimensions;
if (argType == "Array" || argType == "Texture") {
dimensions = this.getDimensions(args[i], true);
key += '['+dimensions[0]+','+dimensions[1]+','+dimensions[2]+']';
}
}
}
var specialFlags = '';
if (opt.wraparound) {
specialFlags += "Wraparound";
}
if (opt.hardcodeConstants) {
specialFlags += "Hardcode";
specialFlags += '['+outputDim[0]+','+outputDim[1]+','+outputDim[2]+']';
}
if (opt.constants) {
specialFlags += "Constants";
specialFlags += JSON.stringify(opt.constants);
}
if (specialFlags) {
key = key + '-' + specialFlags;
}
return key;
}
getUniformLocation(name) {
var location = this.programUniformLocationCache[this.programCacheKey][name];
if (!location) {
location = gl.getUniformLocation(program, name);
this.programUniformLocationCache[this.programCacheKey][name] = location;
}
return location;
}
get mode() {
return 'gpu';
}
}

View File

@ -1,135 +0,0 @@
export default class ModeCPU extends GPUCore {
/// JS fallback transformation, basically pure JS
///
/// @param inputFunction The calling to perform the conversion
/// @param opt The parameter object
///
/// @returns callable function if converted, else returns null
modeCpu(kernel, opt) {
var gpu = this;
var canvas = gpu._canvasCpu;
if (!canvas) {
canvas = gpu._canvasCpu = GPUUtils.init_canvas();
}
function ret() {
if (!opt.dimensions || opt.dimensions.length === 0) {
if (arguments.length != 1) {
throw "Auto dimensions only supported for kernels with only one input";
}
var argType = GPUUtils.getArgumentType(arguments[0]);
if (argType == "Array") {
opt.dimensions = getDimensions(argType);
} else if (argType == "Texture") {
opt.dimensions = arguments[0].dimensions;
} else {
throw "Auto dimensions not supported for input type: " + argType;
}
}
var kernelArgs = [];
for (var i=0; i<arguments.length; i++) {
var argType = GPUUtils.getArgumentType(arguments[i]);
if (argType == "Array" || argType == "Number") {
kernelArgs[i] = arguments[i];
} else if (argType == "Texture") {
kernelArgs[i] = arguments[i].toArray();
} else {
throw "Input type not supported (CPU): " + arguments[i];
}
}
var threadDim = GPUUtils.clone(opt.dimensions);
while (threadDim.length < 3) {
threadDim.push(1);
}
var ret = new Array(threadDim[2]);
for (var i=0; i<threadDim[2]; i++) {
ret[i] = new Array(threadDim[1]);
for (var j=0; j<threadDim[1]; j++) {
ret[i][j] = new Array(threadDim[0]);
}
}
var ctx = {
thread: {
x: 0,
y: 0,
z: 0
},
dimensions: {
x: threadDim[0],
y: threadDim[1],
z: threadDim[2]
},
constants: opt.constants
};
var canvasCtx;
var imageData;
var data;
if (opt.graphical) {
canvas.width = threadDim[0];
canvas.height = threadDim[1];
canvasCtx = canvas.getContext("2d");
imageData = canvasCtx.createImageData(threadDim[0], threadDim[1]);
data = new Uint8ClampedArray(threadDim[0]*threadDim[1]*4);
ctx.color = function(r, g, b, a) {
if (a == undefined) {
a = 1.0;
}
r = Math.floor(r * 255);
g = Math.floor(g * 255);
b = Math.floor(b * 255);
a = Math.floor(a * 255);
var width = ctx.dimensions.x;
var height = ctx.dimensions.y;
var x = ctx.thread.x;
var y = height - ctx.thread.y - 1;
var index = x + y*width;
data[index*4+0] = r;
data[index*4+1] = g;
data[index*4+2] = b;
data[index*4+3] = a;
};
}
for (ctx.thread.z=0; ctx.thread.z<threadDim[2]; ctx.thread.z++) {
for (ctx.thread.y=0; ctx.thread.y<threadDim[1]; ctx.thread.y++) {
for (ctx.thread.x=0; ctx.thread.x<threadDim[0]; ctx.thread.x++) {
ret[ctx.thread.z][ctx.thread.y][ctx.thread.x] = kernel.apply(ctx, kernelArgs);
}
}
}
if (opt.graphical) {
imageData.data.set(data);
canvasCtx.putImageData(imageData, 0, 0);
}
if (opt.dimensions.length == 1) {
ret = ret[0][0];
} else if (opt.dimensions.length == 2) {
ret = ret[0];
}
return ret;
}
ret.canvas = canvas;
return gpu.setupExecutorExtendedFunctions(ret, opt);
}
}

View File

@ -1,679 +0,0 @@
const GPUCore = require('gpu-core');
const GPUUtils = require('../gpu-utils');
const GPUTexture = require('../gpu-texture');
export default class ModeGPU extends GPUCore {
constructor(opt) {
this.programUniformLocationCache = {};
this.programCacheKey = this.getProgramCacheKey(arguments, opt, opt.dimensions);
var program = programCache[this.programCacheKey];
this.programCache = {};
this.bufferCache = {};
this.textureCache = {};
this.framebufferCache = {};
}
dimToTexSize(opt, dimensions, output) {
let numTexels = dimensions[0];
for (let i = 1; i < dimensions.length; i++) {
numTexels *= dimensions[i];
}
if (opt.floatTextures && (!output || opt.floatOutput)) {
numTexels = Math.ceil(numTexels / 4);
}
var w = Math.ceil(Math.sqrt(numTexels));
return [w, w];
}
getDimensions(x, pad) {
var ret;
if (GPUUtils.isArray(x)) {
var dim = [];
var temp = x;
while (GPUUtils.isArray(temp)) {
dim.push(temp.length);
temp = temp[0];
}
ret = dim.reverse();
} else if (x instanceof GPUTexture) {
ret = x.dimensions;
} else {
throw "Unknown dimensions of " + x;
}
if (pad) {
ret = GPUUtils.clone(ret);
while (ret.length < 3) {
ret.push(1);
}
}
return ret;
}
pad(arr, padding) {
function zeros(n) {
return Array.apply(null, Array(n)).map(Number.prototype.valueOf,0);
}
var len = arr.length + padding * 2;
var ret = arr.map(function(x) {
return [].concat(zeros(padding), x, zeros(padding));
});
for (var i=0; i<padding; i++) {
ret = [].concat([zeros(len)], ret, [zeros(len)]);
}
return ret;
}
flatten(arr) {
if (GPUUtils.isArray(arr[0])) {
if (GPUUtils.isArray(arr[0][0])) {
// Annoyingly typed arrays do not have concat so we turn them into arrays first
if (!Array.isArray(arr[0][0])) {
return [].concat.apply([], [].concat.apply([], arr).map(function(x) {
return Array.prototype.slice.call(x);
}));
}
return [].concat.apply([], [].concat.apply([], arr));
} else {
return [].concat.apply([], arr);
}
} else {
return arr;
}
}
splitArray(array, part) {
var tmp = [];
for(var i = 0; i < array.length; i += part) {
tmp.push(array.slice(i, i + part));
}
return tmp;
}
getProgramCacheKey(args, opt, outputDim) {
var key = '';
for (var i=0; i<args.length; i++) {
var argType = GPUUtils.getArgumentType(args[i]);
key += argType;
if (opt.hardcodeConstants) {
var dimensions;
if (argType == "Array" || argType == "Texture") {
dimensions = this.getDimensions(args[i], true);
key += '['+dimensions[0]+','+dimensions[1]+','+dimensions[2]+']';
}
}
}
var specialFlags = '';
if (opt.wraparound) {
specialFlags += "Wraparound";
}
if (opt.hardcodeConstants) {
specialFlags += "Hardcode";
specialFlags += '['+outputDim[0]+','+outputDim[1]+','+outputDim[2]+']';
}
if (opt.constants) {
specialFlags += "Constants";
specialFlags += JSON.stringify(opt.constants);
}
if (specialFlags) {
key = key + '-' + specialFlags;
}
return key;
}
getUniformLocation(name) {
var location = this.programUniformLocationCache[this.programCacheKey][name];
if (!location) {
location = gl.getUniformLocation(program, name);
this.programUniformLocationCache[this.programCacheKey][name] = location;
}
return location;
}
mode(kernel, opt) {
var gpu = this;
var canvas = this._canvas;
if (!canvas) {
canvas = this._canvas = GPUUtils.initCanvas();
}
var gl = this._webgl;
if (!gl) {
gl = this._webgl = GPUUtils.initWebGl(canvas);
}
var builder = this.functionBuilder;
var endianness = this.endianness;
var funcStr = kernel.toString();
if( !GPUUtils.isFunctionString(funcStr) ) {
throw "Unable to get body of kernel function";
}
var paramNames = GPUUtils.getParamNamesFromString(funcStr);
var vertices = new Float32Array([
-1, -1,
1, -1,
-1, 1,
1, 1]);
var texCoords = new Float32Array([
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
1.0, 1.0]);
function ret() {
if (opt.floatTextures === true && !GPUUtils.OES_texture_float) {
throw "Float textures are not supported on this browser";
} else if (opt.floatOutput === true && opt.floatOutputForce !== true && !GPUUtils.test_floatReadPixels(gpu)) {
throw "Float texture outputs are not supported on this browser";
} else if (opt.floatTextures === undefined && GPUUtils.OES_texture_float) {
opt.floatTextures = true;
opt.floatOutput = GPUUtils.isFloatReadPixelsSupported(gpu) && !opt.graphical;
}
if (!opt.dimensions || opt.dimensions.length === 0) {
if (arguments.length != 1) {
throw "Auto dimensions only supported for kernels with only one input";
}
var argType = GPUUtils.getArgumentType(arguments[0]);
if (argType == "Array") {
opt.dimensions = this.getDimensions(argType);
} else if (argType == "Texture") {
opt.dimensions = arguments[0].dimensions;
} else {
throw "Auto dimensions not supported for input type: " + argType;
}
}
var texSize = this.dimToTexSize(opt, opt.dimensions, true);
if (opt.graphical) {
if (opt.dimensions.length != 2) {
throw "Output must have 2 dimensions on graphical mode";
}
if (opt.floatOutput) {
throw "Cannot use graphical mode and float output at the same time";
}
texSize = GPUUtils.clone(opt.dimensions);
} else if (opt.floatOutput === undefined && GPUUtils.OES_texture_float) {
opt.floatOutput = true;
}
canvas.width = texSize[0];
canvas.height = texSize[1];
gl.viewport(0, 0, texSize[0], texSize[1]);
var threadDim = GPUUtils.clone(opt.dimensions);
while (threadDim.length < 3) {
threadDim.push(1);
}
if (program === undefined) {
var constantsStr = '';
if (opt.constants) {
for (var name in opt.constants) {
var value = parseFloat(opt.constants[name]);
if (Number.isInteger(value)) {
constantsStr += 'const float constants_' + name + '=' + parseInt(value) + '.0;\n';
} else {
constantsStr += 'const float constants_' + name + '=' + parseFloat(value) + ';\n';
}
}
}
var paramStr = '';
var paramType = [];
for (var i=0; i<paramNames.length; i++) {
var argType = GPUUtils.getArgumentType(arguments[i]);
paramType.push(argType);
if (opt.hardcodeConstants) {
if (argType == "Array" || argType == "Texture") {
var paramDim = this.getDimensions(arguments[i], true);
var paramSize = this.dimToTexSize(gpu, paramDim);
paramStr += 'uniform highp sampler2D user_' + paramNames[i] + ';\n';
paramStr += 'highp vec2 user_' + paramNames[i] + 'Size = vec2(' + paramSize[0] + '.0, ' + paramSize[1] + '.0);\n';
paramStr += 'highp vec3 user_' + paramNames[i] + 'Dim = vec3(' + paramDim[0] + '.0, ' + paramDim[1] + '.0, ' + paramDim[2] + '.0);\n';
} else if (argType == "Number" && Number.isInteger(arguments[i])) {
paramStr += 'highp float user_' + paramNames[i] + ' = ' + arguments[i] + '.0;\n';
} else if (argType == "Number") {
paramStr += 'highp float user_' + paramNames[i] + ' = ' + arguments[i] + ';\n';
}
} else {
if (argType == "Array" || argType == "Texture") {
paramStr += 'uniform highp sampler2D user_' + paramNames[i] + ';\n';
paramStr += 'uniform highp vec2 user_' + paramNames[i] + 'Size;\n';
paramStr += 'uniform highp vec3 user_' + paramNames[i] + 'Dim;\n';
} else if (argType == "Number") {
paramStr += 'uniform highp float user_' + paramNames[i] + ';\n';
}
}
}
var kernelNode = new FunctionNode(gpu, "kernel", kernel);
kernelNode.paramNames = paramNames;
kernelNode.paramType = paramType;
kernelNode.isRootKernel = true;
builder.addFunctionNode(kernelNode);
var vertShaderSrc = `
precision highp float;
precision highp int;
precision highp sampler2D;
attribute highp vec2 aPos;
attribute highp vec2 aTexCoord;
varying highp vec2 vTexCoord;
void main(void) {
gl_Position = vec4(aPos, 0, 1);
vTexCoord = aTexCoord;
}
`;
var fragShaderSrc = `
precision highp float;
precision highp int;
precision highp sampler2D;
#define LOOP_MAX ${ (opt.loopMaxIterations ? parseInt(opt.loopMaxIterations)+'.0' : '100.0') };
#define EPSILON 0.0000001;
${ opt.hardcodeConstants
? `highp vec3 uOutputDim = vec3(${ threadDim[0] },${ threadDim[1] }, ${ threadDim[2] });`
: 'uniform highp vec3 uOutputDim};'
}
${ opt.hardcodeConstants
? `highp vec2 uTexSize = vec2(${ texSize[0] }, ${ texSize[1] });`
: 'uniform highp vec2 uTexSize;'
}
varying highp vec2 vTexCoord;
vec4 round(vec4 x) {
return floor(x + 0.5);
}
highp float round(highp float x) {
return floor(x + 0.5);
}
vec2 integerMod(vec2 x, float y) {
vec2 res = floor(mod(x, y));
return res * step(1.0 - floor(y), -res);
}
vec3 integerMod(vec3 x, float y) {
vec3 res = floor(mod(x, y));
return res * step(1.0 - floor(y), -res);
}
vec4 integerMod(vec4 x, vec4 y) {
vec4 res = floor(mod(x, y));
return res * step(1.0 - floor(y), -res);
}
highp float integerMod(highp float x, highp float y) {
highp float res = floor(mod(x, y));
return res * (res > floor(y) - 1.0 ? 0.0 : 1.0);
}
highp int integerMod(highp int x, highp int y) {
return int(integerMod(float(x), float(y)));
}
// Here be dragons!
// DO NOT OPTIMIZE THIS CODE
// YOU WILL BREAK SOMETHING ON SOMEBODY\'S MACHINE
// LEAVE IT AS IT IS, LEST YOU WASTE YOUR OWN TIME
const vec2 MAGIC_VEC = vec2(1.0, -256.0);
const vec4 SCALE_FACTOR = vec4(1.0, 256.0, 65536.0, 0.0);
const vec4 SCALE_FACTOR_INV = vec4(1.0, 0.00390625, 0.0000152587890625, 0.0); // 1, 1/256, 1/65536
highp float decode32(highp vec4 rgba) {
${ endianness == 'LE'
? ''
: 'rgba.rgba = rgba.abgr;'
}
rgba *= 255.0;
vec2 gte128;
gte128.x = rgba.b >= 128.0 ? 1.0 : 0.0;
gte128.y = rgba.a >= 128.0 ? 1.0 : 0.0;
float exponent = 2.0 * rgba.a - 127.0 + dot(gte128, MAGIC_VEC);
float res = exp2(round(exponent));
rgba.b = rgba.b - 128.0 * gte128.x;
res = dot(rgba, SCALE_FACTOR) * exp2(round(exponent-23.0)) + res;
res *= gte128.y * -2.0 + 1.0;
return res;
}
highp vec4 encode32(highp float f) {
highp float F = abs(f);
highp float sign = f < 0.0 ? 1.0 : 0.0;
highp float exponent = floor(log2(F));
highp float mantissa = (exp2(-exponent) * F);
// exponent += floor(log2(mantissa));
vec4 rgba = vec4(F * exp2(23.0-exponent)) * SCALE_FACTOR_INV;
rgba.rg = integerMod(rgba.rg, 256.0);
rgba.b = integerMod(rgba.b, 128.0);
rgba.a = exponent*0.5 + 63.5;
rgba.ba += vec2(integerMod(exponent+127.0, 2.0), sign) * 128.0;
rgba = floor(rgba);
rgba *= 0.003921569; // 1/255
${ endianness == 'LE'
? ''
: 'rgba.rgba = rgba.abgr;'
}
return rgba;',
}
// Dragons end here
highp float index;
highp vec3 threadId;
highp vec3 indexTo3D(highp float idx, highp vec3 texDim) {
highp float z = floor(idx / (texDim.x * texDim.y));
idx -= z * texDim.x * texDim.y;
highp float y = floor(idx / texDim.x);
highp float x = integerMod(idx, texDim.x);
return vec3(x, y, z);
}
highp float get(highp sampler2D tex, highp vec2 texSize, highp vec3 texDim, highp float z, highp float y, highp float x) {
highp vec3 xyz = vec3(x, y, z);
xyz = floor(xyz + 0.5);
${ opt.wraparound
? ' xyz = mod(xyz, texDim);'
: ''
}
highp float index = round(xyz.x + texDim.x * (xyz.y + texDim.y * xyz.z));
${ opt.floatTextures ? ' int channel = int(integerMod(index, 4.0));' : '' }
${ opt.floatTextures ? ' index = float(int(index)/4);' : ''}
highp float w = round(texSize.x);
vec2 st = vec2(integerMod(index, w), float(int(index) / int(w))) + 0.5;
${ opt.floatTextures ? ' index = float(int(index)/4);' : ''}
highp vec4 texel = texture2D(tex, st / texSize);
${ opt.floatTextures ? ' if (channel == 0) return texel.r;' : '' }
${ opt.floatTextures ? ' if (channel == 1) return texel.g;' : '' }
${ opt.floatTextures ? ' if (channel == 2) return texel.b;' : '' }
${ opt.floatTextures ? ' if (channel == 3) return texel.a;' : '' }
${ opt.floatTextures ? '' : ' return decode32(texel);' }
}
highp float get(highp sampler2D tex, highp vec2 texSize, highp vec3 texDim, highp float y, highp float x) {
return get(tex, texSize, texDim, 0.0, y, x);
}
highp float get(highp sampler2D tex, highp vec2 texSize, highp vec3 texDim, highp float x) {
return get(tex, texSize, texDim, 0.0, 0.0, x);
}
const bool outputToColor = ${ opt.graphical? 'true' : 'false' };
highp vec4 actualColor;
void color(float r, float g, float b, float a) {
actualColor = vec4(r,g,b,a);
}
void color(float r, float g, float b) {
color(r,g,b,1.0);
}
highp float kernelResult = 0.0;
paramStr
constantsStr
builder.webGlPrototypeString("kernel", opt)
builder.webGlString("kernel", opt)
void main(void) {
index = floor(vTexCoord.s * float(uTexSize.x)) + floor(vTexCoord.t * float(uTexSize.y)) * uTexSize.x;
${ opt.floatOutput ? 'index *= 4.0;' : '' }
threadId = indexTo3D(index, uOutputDim);
kernel();
if (outputToColor == true) {
gl_FragColor = actualColor;
} else {
${ opt.floatOutput ? '' : 'gl_FragColor = encode32(kernelResult);' }
${ opt.floatOutput ? 'gl_FragColor.r = kernelResult;' : '' }
${ opt.floatOutput ? 'index += 1.0; threadId = indexTo3D(index, uOutputDim); kernel();' : '' }
${ opt.floatOutput ? 'gl_FragColor.g = kernelResult;' : '' }
${ opt.floatOutput ? 'index += 1.0; threadId = indexTo3D(index, uOutputDim); kernel();' : '' }
${ opt.floatOutput ? 'gl_FragColor.b = kernelResult;' : '' }
${ opt.floatOutput ? 'index += 1.0; threadId = indexTo3D(index, uOutputDim); kernel();' : '' }
${ opt.floatOutput ? 'gl_FragColor.a = kernelResult;' : '' }
}
}`;
const vertShader = gl.createShader(gl.VERTEX_SHADER);
const fragShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(vertShader, vertShaderSrc);
gl.shaderSource(fragShader, fragShaderSrc);
gl.compileShader(vertShader);
gl.compileShader(fragShader);
if (!gl.getShaderParameter(vertShader, gl.COMPILE_STATUS)) {
console.log(vertShaderSrc);
console.error("An error occurred compiling the shaders: " + gl.getShaderInfoLog(vertShader));
throw "Error compiling vertex shader";
}
if (!gl.getShaderParameter(fragShader, gl.COMPILE_STATUS)) {
console.log(fragShaderSrc);
console.error("An error occurred compiling the shaders: " + gl.getShaderInfoLog(fragShader));
throw "Error compiling fragment shader";
}
if (opt.debug) {
console.log('Options:');
console.dir(opt);
console.log('GLSL Shader Output:');
console.log(fragShaderSrc);
}
const program = this.program = gl.createProgram();
gl.attachShader(program, vertShader);
gl.attachShader(program, fragShader);
gl.linkProgram(program);
programCache[programCacheKey] = program;
programUniformLocationCache[programCacheKey] = [];
}
gl.useProgram(program);
const texCoordOffset = vertices.byteLength;
let buffer = bufferCache[programCacheKey];
if (!buffer) {
buffer = gl.createBuffer();
bufferCache[programCacheKey] = buffer;
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices.byteLength + texCoords.byteLength, gl.STATIC_DRAW);
} else {
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
}
gl.bufferSubData(gl.ARRAY_BUFFER, 0, vertices);
gl.bufferSubData(gl.ARRAY_BUFFER, texCoordOffset, texCoords);
var aPosLoc = gl.getAttribLocation(program, "aPos");
gl.enableVertexAttribArray(aPosLoc);
gl.vertexAttribPointer(aPosLoc, 2, gl.FLOAT, gl.FALSE, 0, 0);
var aTexCoordLoc = gl.getAttribLocation(program, "aTexCoord");
gl.enableVertexAttribArray(aTexCoordLoc);
gl.vertexAttribPointer(aTexCoordLoc, 2, gl.FLOAT, gl.FALSE, 0, texCoordOffset);
if (!opt.hardcodeConstants) {
var uOutputDimLoc = this.getUniformLocation("uOutputDim");
gl.uniform3fv(uOutputDimLoc, threadDim);
var uTexSizeLoc = this.getUniformLocation("uTexSize");
gl.uniform2fv(uTexSizeLoc, texSize);
}
if (!textureCache[programCacheKey]) {
textureCache[programCacheKey] = [];
}
let textureCount = 0;
for (textureCount=0; textureCount<paramNames.length; textureCount++) {
let paramDim, paramSize, texture;
var argType = GPUUtils.getArgumentType(arguments[textureCount]);
if (argType == "Array") {
paramDim = this.getDimensions(arguments[textureCount], true);
paramSize = this.dimToTexSize(opt, paramDim);
if (textureCache[programCacheKey][textureCount]) {
texture = textureCache[programCacheKey][textureCount];
} else {
texture = gl.createTexture();
textureCache[programCacheKey][textureCount] = texture;
}
gl.activeTexture(gl["TEXTURE"+textureCount]);
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);
var paramLength = paramSize[0] * paramSize[1];
if (opt.floatTextures) {
paramLength *= 4;
}
var paramArray = new Float32Array(paramLength);
paramArray.set(flatten(arguments[textureCount]))
var argBuffer;
if (opt.floatTextures) {
argBuffer = new Float32Array(paramArray);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, paramSize[0], paramSize[1], 0, gl.RGBA, gl.FLOAT, argBuffer);
} else {
argBuffer = new Uint8Array((new Float32Array(paramArray)).buffer);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, paramSize[0], paramSize[1], 0, gl.RGBA, gl.UNSIGNED_BYTE, argBuffer);
}
var paramLoc = this.getUniformLocation("user_" + paramNames[textureCount]);
var paramSizeLoc = this.getUniformLocation("user_" + paramNames[textureCount] + "Size");
var paramDimLoc = this.getUniformLocation("user_" + paramNames[textureCount] + "Dim");
if (!opt.hardcodeConstants) {
gl.uniform3fv(paramDimLoc, paramDim);
gl.uniform2fv(paramSizeLoc, paramSize);
}
gl.uniform1i(paramLoc, textureCount);
} else if (argType == "Number") {
var argLoc = this.getUniformLocation("user_"+paramNames[textureCount]);
gl.uniform1f(argLoc, arguments[textureCount]);
} else if (argType == "Texture") {
paramDim = this.getDimensions(arguments[textureCount], true);
paramSize = arguments[textureCount].size;
texture = arguments[textureCount].texture;
gl.activeTexture(gl["TEXTURE"+textureCount]);
gl.bindTexture(gl.TEXTURE_2D, texture);
var paramLoc = this.getUniformLocation("user_" + paramNames[textureCount]);
var paramSizeLoc = this.getUniformLocation("user_" + paramNames[textureCount] + "Size");
var paramDimLoc = this.getUniformLocation("user_" + paramNames[textureCount] + "Dim");
gl.uniform3fv(paramDimLoc, paramDim);
gl.uniform2fv(paramSizeLoc, paramSize);
gl.uniform1i(paramLoc, textureCount);
} else {
throw "Input type not supported (GPU): " + arguments[textureCount];
}
}
var outputTexture = textureCache[programCacheKey][textureCount];
if (!outputTexture) {
outputTexture = gl.createTexture();
textureCache[programCacheKey][textureCount] = outputTexture;
}
gl.activeTexture(gl["TEXTURE"+textureCount]);
gl.bindTexture(gl.TEXTURE_2D, outputTexture);
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 (opt.floatOutput) {
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 (opt.graphical) {
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
return;
}
var framebuffer = framebufferCache[programCacheKey];
if (!framebuffer) {
framebuffer = gl.createFramebuffer();
framebufferCache[programCacheKey] = framebuffer;
}
framebuffer.width = texSize[0];
framebuffer.height = texSize[1];
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, outputTexture, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
if (opt.outputToTexture) {
// Don't retain a handle on the output texture, we might need to render on the same texture later
delete textureCache[programCacheKey][textureCount];
return new GPUTexture(gpu, outputTexture, texSize, opt.dimensions);
} else {
var result;
if (opt.floatOutput) {
result = new Float32Array(texSize[0]*texSize[1]*4);
gl.readPixels(0, 0, texSize[0], texSize[1], gl.RGBA, gl.FLOAT, result);
} else {
var bytes = new Uint8Array(texSize[0]*texSize[1]*4);
gl.readPixels(0, 0, texSize[0], texSize[1], gl.RGBA, gl.UNSIGNED_BYTE, bytes);
result = Float32Array.prototype.slice.call(new Float32Array(bytes.buffer));
}
result = result.subarray(0, threadDim[0] * threadDim[1] * threadDim[2]);
if (opt.dimensions.length == 1) {
return result;
} else if (opt.dimensions.length == 2) {
return splitArray(result, opt.dimensions[0]);
} else if (opt.dimensions.length == 3) {
var cube = splitArray(result, opt.dimensions[0] * opt.dimensions[1]);
return cube.map(function(x) {
return splitArray(x, opt.dimensions[0]);
});
}
}
}
ret.canvas = canvas;
ret.webgl = gl;
return gpu.setupExecutorExtendedFunctions(ret, opt);
}
}

107
src/gpu-utils.js vendored
View File

@ -347,7 +347,7 @@ export default class GPUUtils {
}
// Default webgl options to use
static get initWebglDefaultOptions() {
static get initWebGlDefaultOptions() {
return {
alpha: false,
depth: false,
@ -381,8 +381,8 @@ export default class GPUUtils {
// Create a new canvas DOM
const webgl = (
canvasObj.getContext('experimental-webgl', GPUUtils.initWebglDefaultOptions)
|| canvasObj.getContext('webgl', GPUUtils.initWebglDefaultOptions)
canvasObj.getContext('experimental-webgl', GPUUtils.initWebGlDefaultOptions)
|| canvasObj.getContext('webgl', GPUUtils.initWebGlDefaultOptions)
);
// Get the extension that is needed
@ -413,14 +413,107 @@ export default class GPUUtils {
const x = gpu.createKernel(function() {
return 1;
}, {
'dimensions': [2],
'floatTextures': true,
'floatOutput': true,
'floatOutputForce': true
dimensions: [2],
floatTextures: true,
floatOutput: true,
floatOutputForce: true
}).dimensions([2])();
isFloatReadPixelsSupported = x[0] == 1;
return isFloatReadPixelsSupported;
}
static dimToTexSize(opt, dimensions, output) {
let numTexels = dimensions[0];
for (let i = 1; i < dimensions.length; i++) {
numTexels *= dimensions[i];
}
if (opt.floatTextures && (!output || opt.floatOutput)) {
numTexels = Math.ceil(numTexels / 4);
}
var w = Math.ceil(Math.sqrt(numTexels));
return [w, w];
}
static getDimensions(x, pad) {
var ret;
if (GPUUtils.isArray(x)) {
var dim = [];
var temp = x;
while (GPUUtils.isArray(temp)) {
dim.push(temp.length);
temp = temp[0];
}
ret = dim.reverse();
} else if (x instanceof GPUTexture) {
ret = x.dimensions;
} else {
throw "Unknown dimensions of " + x;
}
if (pad) {
ret = GPUUtils.clone(ret);
while (ret.length < 3) {
ret.push(1);
}
}
return ret;
}
static pad(arr, padding) {
function zeros(n) {
return Array.apply(null, Array(n)).map(Number.prototype.valueOf,0);
}
var len = arr.length + padding * 2;
var ret = arr.map(function(x) {
return [].concat(zeros(padding), x, zeros(padding));
});
for (var i=0; i<padding; i++) {
ret = [].concat([zeros(len)], ret, [zeros(len)]);
}
return ret;
}
static flatten(arr) {
const result = [];
if (GPUUtils.isArray(arr[0])) {
if (GPUUtils.isArray(arr[0][0])) {
if (Array.isArray(arr[0][0])) {
for (let i = 0; i < arr.length; i++) {
const nestedArray = arr[i];
for (let j = 0; j < nestedArray.length; j++) {
result.push.apply(result, nestedArray[j]);
}
}
} else {
for (let i = 0; i < arr.length; i++) {
result.push.apply(result, arr[i]);
}
}
} else {
result.push.apply(result, arr);
}
} else {
return arr;
}
return result;
}
static splitArray(array, part) {
const result = [];
for(let i = 0; i < array.length; i += part) {
result.push(array.slice(i, i + part));
}
return result;
}
}

51
src/gpu.js vendored
View File

@ -1,10 +1,19 @@
const GPUUtils = require('./gpu-utils');
const FunctionBuilder = require('./backend/function-builder');
const GPURunner = require('./backend/gpu/gpu-runner');
const CPURunner = require('./backend/cpu/cpu-runner');
///
/// Class: GPU
///
/// Initialises the GPU.js library class which manages the WebGL context for the created functions.
///
export default class GPU {
constructor() {
this.functionBuilder = new FunctionBuilder();
this.runner = GPUUtils.isWebGlSupported
? new GPURunner()
: new CPURunner();
}
///
/// Function: createKernel
///
@ -24,48 +33,52 @@ export default class GPU {
///
/// Parameters:
/// inputFunction {JS Function} The calling to perform the conversion
/// paramObj {Object} The parameter configuration object (see above)
/// settings {Object} The parameter configuration object (see above)
///
/// Returns:
/// callable function to run
///
createKernel(kernel, paramObj) {
createKernel(fn, settings) {
//
// basic parameters safety checks
//
if(kernel === undefined) {
throw 'Missing kernel parameter';
if(fn === undefined) {
throw 'Missing fn parameter';
}
if(!GPUUtils.isFunction(kernel)) {
throw 'kernel parameter not a function';
if(!GPUUtils.isFunction(fn)) {
throw 'fn parameter not a function';
}
if( paramObj === undefined ) {
paramObj = {};
if(settings === undefined) {
settings = {};
}
//
// Replace the kernel function and param object
//
this._kernelFunction = kernel;
this._kernelParamObj = paramObj || this._kernelParamObj || {};
this._fn = fn;
this._kernelParamObj = settings || this._kernelParamObj || {};
//
// Get the config, fallbacks to default value if not set
//
const mode = paramObj.mode && paramObj.mode.toLowerCase();
const mode = settings.mode && settings.mode.toLowerCase();
this.computeMode = mode || 'auto';
this.runner
.setFn(fn)
.buildKernel();
//
// Get the Synchronous executor
//
const ret = this.getSynchronousModeExecutor();
const ret =
// Allow class refence from function
ret.gpujs = this;
// Execute callback
ret.exec = ret.execute = GPUUtils.functionBinder( this.execute, this );
ret.exec = ret.execute = GPUUtils.functionBinder(this.execute, this);
// The Synchronous kernel
this._kernelSynchronousExecutor = ret; //For exec to reference
this._runner = ret; //For exec to reference
return ret;
}
@ -78,8 +91,8 @@ export default class GPU {
/// Returns:
/// {JS Function} The calling input function
///
getKernelFunction() {
return this._kernelFunction;
getFn() {
return this._fn;
}
///
@ -116,7 +129,7 @@ export default class GPU {
//
return GPUUtils.newPromise((accept,reject) => {
try {
accept( this._kernelSynchronousExecutor.apply(this, args) );
accept(this._runner.apply(this, args));
} catch (e) {
//
// Error : throw rejection
@ -143,8 +156,8 @@ export default class GPU {
/// Retuns:
/// {GPU} returns itself
///
addFunction( jsFunction, paramTypeArray, returnType ) {
this.functionBuilder.addFunction( null, jsFunction, paramTypeArray, returnType );
addFunction(jsFunction, paramTypeArray, returnType) {
this.functionBuilder.addFunction(null, jsFunction, paramTypeArray, returnType);
return this;
}

View File

@ -251,7 +251,8 @@ VariableDeclarationListNoIn
}
| VariableDeclarationListNoIn "," VariableDeclarationNoIn
{
$$ = $1.concat($3);
$1.push.apply($1, $3);
$$ = $1;
}
;

View File

@ -19,7 +19,7 @@ QUnit.test( "hello_world: just return magic 42", function( assert ) {
assert.notEqual( node.getJS_AST(), null, "AST fetch check" );
assert.equal(
node.getWebglFunctionString().replace(/\s+/g,' '),
node.getFunctionString().replace(/\s+/g,' '),
"float hello_world() { return 42.0; }",
"webgl function conversion check"
);
@ -46,7 +46,7 @@ QUnit.test( "hello_inner: call a funciton inside a function", function( assert )
assert.notEqual( node.getJS_AST(), null, "AST fetch check" );
assert.equal(
node.getWebglFunctionString().replace(/\s+/g,' '),
node.getFunctionString().replace(/\s+/g,' '),
"float hello_inner() { return inner(); }",
"webgl function conversion check"
);
@ -69,7 +69,7 @@ QUnit.test( "Math.round implementation: A function with arguments", function( as
assert.notEqual( node.getJS_AST(), null, "AST fetch check" );
assert.equal(
node.getWebglFunctionString().replace(/\s+/g,' ').replace(/user_/g,''),
node.getFunctionString().replace(/\s+/g,' ').replace(/user_/g,''),
"float round(float a) { return floor((a+0.5)); }",
"webgl function conversion check"
);
@ -92,7 +92,7 @@ QUnit.test( "Two arguments test", function( assert ) {
assert.notEqual( node.getJS_AST(), null, "AST fetch check" );
assert.equal(
node.getWebglFunctionString().replace(/\s+/g,' ').replace(/user_/g,''),
node.getFunctionString().replace(/\s+/g,' ').replace(/user_/g,''),
"float add_together(float a, float b) { return (a+b); }",
"webgl function conversion check"
);