mirror of
https://github.com/gpujs/gpu.js.git
synced 2026-01-25 16:08:02 +00:00
More refactoring towards es6
This commit is contained in:
parent
5c458c60c1
commit
8be5dcec0c
196
src/backend/base-function-node.js
Normal file
196
src/backend/base-function-node.js
Normal 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;
|
||||
}
|
||||
}
|
||||
92
src/backend/base-kernel.js
Normal file
92
src/backend/base-kernel.js
Normal 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');
|
||||
}
|
||||
}
|
||||
82
src/backend/base-runner.js
Normal file
82
src/backend/base-runner.js
Normal 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);
|
||||
}
|
||||
}
|
||||
26
src/backend/cpu/cpu-function-node.js
Normal file
26
src/backend/cpu/cpu-function-node.js
Normal 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 {
|
||||
|
||||
}
|
||||
120
src/backend/cpu/cpu-kernel.js
Normal file
120
src/backend/cpu/cpu-kernel.js
Normal 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;
|
||||
}
|
||||
}
|
||||
35
src/backend/cpu/cpu-runner.js
Normal file
35
src/backend/cpu/cpu-runner.js
Normal 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';
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
520
src/backend/gpu/gpu-kernel.js
Normal file
520
src/backend/gpu/gpu-kernel.js
Normal 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]);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
67
src/backend/gpu/gpu-runner.js
Normal file
67
src/backend/gpu/gpu-runner.js
Normal 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';
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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
107
src/gpu-utils.js
vendored
@ -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
51
src/gpu.js
vendored
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -251,7 +251,8 @@ VariableDeclarationListNoIn
|
||||
}
|
||||
| VariableDeclarationListNoIn "," VariableDeclarationNoIn
|
||||
{
|
||||
$$ = $1.concat($3);
|
||||
$1.push.apply($1, $3);
|
||||
$$ = $1;
|
||||
}
|
||||
;
|
||||
|
||||
|
||||
@ -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"
|
||||
);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user