Created
December 24, 2018 22:45
-
-
Save robertleeplummerjr/0f1904191c75ede6b66f0f43279ca1da to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module.exports = () => { | |
function kernelRunShortcut(kernel) { | |
const shortcut = function() { | |
return kernel.run.apply(kernel, arguments); | |
}; | |
utils.allPropertiesOf(kernel).forEach((key) => { | |
if (key[0] === '_' && key[1] === '_') return; | |
if (typeof kernel[key] === 'function') { | |
if (key.substring(0, 3) === 'add' || key.substring(0, 3) === 'set') { | |
shortcut[key] = function() { | |
kernel[key].apply(kernel, arguments); | |
return shortcut; | |
}; | |
} else { | |
shortcut[key] = kernel[key].bind(kernel); | |
} | |
} else { | |
shortcut.__defineGetter__(key, () => { | |
return kernel[key]; | |
}); | |
shortcut.__defineSetter__(key, (value) => { | |
kernel[key] = value; | |
}); | |
} | |
}); | |
shortcut.kernel = kernel; | |
return shortcut; | |
}; | |
const utils = { | |
allPropertiesOf: function(obj) { | |
const props = []; | |
do { | |
props.push.apply(props, Object.getOwnPropertyNames(obj)); | |
} while (obj = Object.getPrototypeOf(obj)); | |
return props; | |
}, | |
clone: function(obj) { | |
if (obj === null || typeof obj !== 'object' || obj.hasOwnProperty('isActiveClone')) return obj; | |
const temp = obj.constructor(); // changed | |
for (let key in obj) { | |
if (Object.prototype.hasOwnProperty.call(obj, key)) { | |
obj.isActiveClone = null; | |
temp[key] = Utils.clone(obj[key]); | |
delete obj.isActiveClone; | |
} | |
} | |
return temp; | |
}, | |
splitArray: function(array, part) { | |
const result = []; | |
for (let i = 0; i < array.length; i += part) { | |
result.push(new array.constructor(array.buffer, i * 4 + array.byteOffset, part)); | |
} | |
return result; | |
}, | |
getArgumentType: function(arg) { | |
if (Utils.isArray(arg)) { | |
if (arg[0].nodeName === 'IMG') { | |
return 'HTMLImageArray'; | |
} | |
return 'Array'; | |
} else if (typeof arg === 'number') { | |
if (Number.isInteger(arg)) { | |
return 'Integer'; | |
} | |
return 'Float'; | |
} else if (arg instanceof Texture) { | |
return arg.type; | |
} else if (arg instanceof Input) { | |
return 'Input'; | |
} else if (arg.nodeName === 'IMG') { | |
return 'HTMLImage'; | |
} else { | |
return 'Unknown'; | |
} | |
}, | |
getDimensions: function(x, pad) { | |
let ret; | |
if (Utils.isArray(x)) { | |
const dim = []; | |
let temp = x; | |
while (Utils.isArray(temp)) { | |
dim.push(temp.length); | |
temp = temp[0]; | |
} | |
ret = dim.reverse(); | |
} else if (x instanceof Texture) { | |
ret = x.output; | |
} else if (x instanceof Input) { | |
ret = x.size; | |
} else { | |
throw 'Unknown dimensions of ' + x; | |
} | |
if (pad) { | |
ret = Utils.clone(ret); | |
while (ret.length < 3) { | |
ret.push(1); | |
} | |
} | |
// return ret; | |
return new Int32Array(ret); | |
}, | |
dimToTexSize: function(opt, dimensions, output) { | |
let numTexels = dimensions[0]; | |
let w = dimensions[0]; | |
let h = dimensions[1]; | |
for (let i = 1; i < dimensions.length; i++) { | |
numTexels *= dimensions[i]; | |
} | |
if (opt.floatTextures && (!output || opt.floatOutput)) { | |
w = numTexels = Math.ceil(numTexels / 4); | |
} | |
// if given dimensions == a 2d image | |
if (h > 1 && w * h === numTexels) { | |
return [w, h]; | |
} | |
// find as close to square width, height sizes as possible | |
const sqrt = Math.sqrt(numTexels); | |
let high = Math.ceil(sqrt); | |
let low = Math.floor(sqrt); | |
while (high * low > numTexels) { | |
high--; | |
low = Math.ceil(numTexels / high); | |
} | |
w = low; | |
h = Math.ceil(numTexels / w); | |
return [w, h]; | |
}, | |
flattenTo: function(array, target) { | |
if (Utils.isArray(array[0])) { | |
if (Utils.isArray(array[0][0])) { | |
Utils.flatten3dArrayTo(array, target); | |
} else { | |
Utils.flatten2dArrayTo(array, target); | |
} | |
} else { | |
target.set(array); | |
} | |
}, | |
flatten2dArrayTo: function(array, target) { | |
let offset = 0; | |
for (let y = 0; y < array.length; y++) { | |
target.set(array[y], offset); | |
offset += array[y].length; | |
} | |
}, | |
flatten3dArrayTo: function(array, target) { | |
let offset = 0; | |
for (let z = 0; z < array.length; z++) { | |
for (let y = 0; y < array[z].length; y++) { | |
target.set(array[z][y], offset); | |
offset += array[z][y].length; | |
} | |
} | |
}, | |
systemEndianness: 'LE', | |
initWebGl: function(canvasObj) { | |
// First time setup, does the browser support check memorizer | |
if (typeof _isCanvasSupported !== 'undefined' || canvasObj === null) { | |
if (!_isCanvasSupported) { | |
return null; | |
} | |
} | |
// Fail fast for invalid canvas object | |
if (!UtilsCore.isCanvas(canvasObj)) { | |
throw new Error('Invalid canvas object - ' + canvasObj); | |
} | |
// Create a new canvas DOM | |
let webGl = null; | |
const defaultOptions = UtilsCore.initWebGlDefaultOptions(); | |
try { | |
webGl = canvasObj.getContext('experimental-webgl', defaultOptions); | |
} catch (e) { | |
// 'experimental-webgl' is not a supported context type | |
// fallback to 'webgl2' or 'webgl' below | |
} | |
// native webgl | |
try { | |
webGl = require('gl')(2, 2); | |
webGl.getExtension('STACKGL_resize_drawingbuffer'); | |
} catch (e) {} | |
if (webGl === null) { | |
webGl = ( | |
canvasObj.getContext('webgl2', defaultOptions) || | |
canvasObj.getContext('webgl', defaultOptions) | |
); | |
} | |
if (webGl) { | |
// Get the extension that is needed | |
webGl.OES_texture_float = webGl.getExtension('OES_texture_float'); | |
webGl.OES_texture_float_linear = webGl.getExtension('OES_texture_float_linear'); | |
webGl.OES_element_index_uint = webGl.getExtension('OES_element_index_uint'); | |
} | |
// Returns the canvas | |
return webGl; | |
}, | |
isArray: function(array) { | |
if (isNaN(array.length)) { | |
return false; | |
} | |
return true; | |
}, | |
checkOutput: function(output) { | |
if (!output || !Array.isArray(output)) throw new Error('kernel.output not an array'); | |
for (let i = 0; i < output.length; i++) { | |
if (isNaN(output[i]) || output[i] < 1) { | |
throw new Error(`kernel.output[${ i }] incorrectly defined as \`${ output[i] }\`, needs to be numeric, and greater than 0`); | |
} | |
} | |
} | |
}; | |
const Utils = utils; | |
const canvases = []; | |
const maxTexSizes = {}; | |
let Texture = function() {}; | |
let Input = function() {}; | |
class Kernel { | |
constructor() { | |
this.maxTexSize = null; | |
this.argumentsLength = 0; | |
this.constantsLength = 0; | |
this._canvas = null; | |
this._webGl = null; | |
this.program = null; | |
this.outputToTexture = false; | |
this.paramNames = ["a","b"]; | |
this.paramTypes = ["Array","Array"]; | |
this.texSize = [2,3]; | |
this.output = [6]; | |
this.compiledFragShaderString = `precision highp float; | |
precision highp int; | |
precision highp sampler2D; | |
const float LOOP_MAX = 1000.0; | |
uniform ivec3 uOutputDim; | |
uniform ivec2 uTexSize; | |
varying vec2 vTexCoord; | |
vec4 round(vec4 x) { | |
return floor(x + 0.5); | |
} | |
float round(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); | |
} | |
float integerMod(float x, float y) { | |
float res = floor(mod(x, y)); | |
return res * (res > floor(y) - 1.0 ? 0.0 : 1.0); | |
} | |
int integerMod(int x, int y) { | |
return x - (y * int(x / y)); | |
} | |
float div_with_int_check(float x, float y) { | |
if (floor(x) == x && floor(y) == y && integerMod(x, y) == 0.0) { | |
return float(int(x)/int(y)); | |
} | |
return x / y; | |
} | |
// Here be dragons! | |
// DO NOT OPTIMIZE THIS CODE | |
// YOU WILL BREAK SOMETHING ON SOMEBODY'S MACHINE | |
// LEAVE IT AS IT IS, LEST YOU WASTE YOUR OWN TIME | |
const vec2 MAGIC_VEC = vec2(1.0, -256.0); | |
const vec4 SCALE_FACTOR = vec4(1.0, 256.0, 65536.0, 0.0); | |
const vec4 SCALE_FACTOR_INV = vec4(1.0, 0.00390625, 0.0000152587890625, 0.0); // 1, 1/256, 1/65536 | |
float decode32(vec4 rgba) { | |
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; | |
} | |
vec4 encode32(float f) { | |
float F = abs(f); | |
float sign = f < 0.0 ? 1.0 : 0.0; | |
float exponent = floor(log2(F)); | |
float mantissa = (exp2(-exponent) * F); | |
// exponent += floor(log2(mantissa)); | |
vec4 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 | |
return rgba; | |
} | |
// Dragons end here | |
float decode(vec4 rgba, int x, int bitRatio) { | |
if (bitRatio == 1) { | |
return decode32(rgba); | |
} | |
int channel = integerMod(x, bitRatio); | |
if (bitRatio == 4) { | |
if (channel == 0) return rgba.r * 255.0; | |
if (channel == 1) return rgba.g * 255.0; | |
if (channel == 2) return rgba.b * 255.0; | |
if (channel == 3) return rgba.a * 255.0; | |
} | |
else { | |
if (channel == 0) return rgba.r * 255.0 + rgba.g * 65280.0; | |
if (channel == 1) return rgba.b * 255.0 + rgba.a * 65280.0; | |
} | |
} | |
int index; | |
ivec3 threadId; | |
ivec3 indexTo3D(int idx, ivec3 texDim) { | |
int z = int(idx / (texDim.x * texDim.y)); | |
idx -= z * int(texDim.x * texDim.y); | |
int y = int(idx / texDim.x); | |
int x = int(integerMod(idx, texDim.x)); | |
return ivec3(x, y, z); | |
} | |
float get(sampler2D tex, ivec2 texSize, ivec3 texDim, int bitRatio, int z, int y, int x) { | |
ivec3 xyz = ivec3(x, y, z); | |
int index = xyz.x + texDim.x * (xyz.y + texDim.y * xyz.z); | |
int w = texSize.x; | |
vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; | |
vec4 texel = texture2D(tex, st / vec2(texSize)); | |
return decode(texel, x, bitRatio); | |
} | |
vec4 getImage2D(sampler2D tex, ivec2 texSize, ivec3 texDim, int z, int y, int x) { | |
ivec3 xyz = ivec3(x, y, z); | |
int index = xyz.x + texDim.x * (xyz.y + texDim.y * xyz.z); | |
int w = texSize.x; | |
vec2 st = vec2(float(integerMod(index, w)), float(index / w)) + 0.5; | |
return texture2D(tex, st / vec2(texSize)); | |
} | |
float get(sampler2D tex, ivec2 texSize, ivec3 texDim, int bitRatio, int y, int x) { | |
return get(tex, texSize, texDim, bitRatio, int(0), y, x); | |
} | |
vec4 getImage2D(sampler2D tex, ivec2 texSize, ivec3 texDim, int y, int x) { | |
return getImage2D(tex, texSize, texDim, int(0), y, x); | |
} | |
float get(sampler2D tex, ivec2 texSize, ivec3 texDim, int bitRatio, int x) { | |
return get(tex, texSize, texDim, bitRatio, int(0), int(0), x); | |
} | |
vec4 getImage2D(sampler2D tex, ivec2 texSize, ivec3 texDim, int x) { | |
return getImage2D(tex, texSize, texDim, int(0), int(0), x); | |
} | |
vec4 actualColor; | |
void color(float r, float g, float b, float a) { | |
actualColor = vec4(r,g,b,a); | |
} | |
void color(float r, float g, float b) { | |
color(r,g,b,1.0); | |
} | |
void color(sampler2D image) { | |
actualColor = texture2D(image, vTexCoord); | |
} | |
uniform sampler2D user_a; | |
uniform ivec2 user_aSize; | |
uniform ivec3 user_aDim; | |
uniform int user_aBitRatio; | |
uniform sampler2D user_b; | |
uniform ivec2 user_bSize; | |
uniform ivec3 user_bDim; | |
uniform int user_bBitRatio; | |
const float constants_width = 6.0; | |
float kernelResult = 0.0; | |
float customAdder(sampler2D user_a, sampler2D user_b) { | |
float user_sum=0.0; | |
for (float user_i=0.0;(user_i<constants_width);user_i++){ | |
user_sum+=(get(user_a, ivec2(user_aSize[0],user_aSize[1]), ivec3(user_aDim[0],user_aDim[1],user_aDim[2]), user_aBitRatio, threadId.x)*get(user_b, ivec2(user_bSize[0],user_bSize[1]), ivec3(user_bDim[0],user_bDim[1],user_bDim[2]), user_bBitRatio, threadId.x));} | |
return user_sum; | |
} | |
void kernel() { | |
kernelResult = customAdder(user_a, user_b);return; | |
} | |
void main(void) { | |
index = int(vTexCoord.s * float(uTexSize.x)) + int(vTexCoord.t * float(uTexSize.y)) * uTexSize.x; | |
threadId = indexTo3D(index, uOutputDim); | |
kernel(); | |
gl_FragColor = encode32(kernelResult); | |
}`; | |
this.compiledVertShaderString = `precision highp float; | |
precision highp int; | |
precision highp sampler2D; | |
attribute vec2 aPos; | |
attribute vec2 aTexCoord; | |
varying vec2 vTexCoord; | |
uniform vec2 ratio; | |
void main(void) { | |
gl_Position = vec4((aPos + vec2(1)) * ratio + vec2(-1), 0, 1); | |
vTexCoord = aTexCoord; | |
}`; | |
this.programUniformLocationCache = {}; | |
this.textureCache = {}; | |
this.subKernelOutputTextures = null; | |
this.subKernelOutputVariableNames = null; | |
this.uniform1fCache = {}; | |
this.uniform1iCache = {}; | |
this.uniform2fCache = {}; | |
this.uniform2fvCache = {}; | |
this.uniform2ivCache = {}; | |
this.uniform3fvCache = {}; | |
this.uniform3ivCache = {}; | |
} | |
_getFragShaderString() { return this.compiledFragShaderString; } | |
_getVertShaderString() { return this.compiledVertShaderString; } | |
validateOptions() {} | |
setupParams() {} | |
setupConstants() {} | |
setCanvas(canvas) { this._canvas = canvas; return this; } | |
setWebGl(webGl) { this._webGl = webGl; return this; } | |
setTexture(Type) { Texture = Type; } | |
setInput(Type) { Input = Type; } | |
getUniformLocation(name) { | |
if (this.programUniformLocationCache.hasOwnProperty(name)) { | |
return this.programUniformLocationCache[name]; | |
} | |
return this.programUniformLocationCache[name] = this._webGl.getUniformLocation(this.program, name); | |
} | |
build() { | |
this.validateOptions(); | |
this.setupConstants(); | |
this.setupParams(arguments); | |
this.updateMaxTexSize(); | |
const texSize = this.texSize; | |
const gl = this._webGl; | |
const canvas = this._canvas; | |
gl.enable(gl.SCISSOR_TEST); | |
gl.viewport(0, 0, this.maxTexSize[0], this.maxTexSize[1]); | |
canvas.width = this.maxTexSize[0]; | |
canvas.height = this.maxTexSize[1]; | |
const threadDim = this.threadDim = utils.clone(this.output); | |
while (threadDim.length < 3) { | |
threadDim.push(1); | |
} | |
if (this.functionBuilder) this._addKernels(); | |
const compiledVertShaderString = this._getVertShaderString(arguments); | |
const vertShader = gl.createShader(gl.VERTEX_SHADER); | |
gl.shaderSource(vertShader, compiledVertShaderString); | |
gl.compileShader(vertShader); | |
this.vertShader = vertShader; | |
const compiledFragShaderString = this._getFragShaderString(arguments); | |
const fragShader = gl.createShader(gl.FRAGMENT_SHADER); | |
gl.shaderSource(fragShader, compiledFragShaderString); | |
gl.compileShader(fragShader); | |
this.fragShader = fragShader; | |
if (!gl.getShaderParameter(vertShader, gl.COMPILE_STATUS)) { | |
console.log(compiledVertShaderString); | |
console.error('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(vertShader)); | |
throw new Error('Error compiling vertex shader'); | |
} | |
if (!gl.getShaderParameter(fragShader, gl.COMPILE_STATUS)) { | |
console.log(compiledFragShaderString); | |
console.error('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(fragShader)); | |
throw new Error('Error compiling fragment shader'); | |
} | |
if (this.debug) { | |
console.log('Options:'); | |
console.dir(this); | |
console.log('GLSL Shader Output:'); | |
console.log(compiledFragShaderString); | |
} | |
const program = this.program = gl.createProgram(); | |
gl.attachShader(program, vertShader); | |
gl.attachShader(program, fragShader); | |
gl.linkProgram(program); | |
this.framebuffer = gl.createFramebuffer(); | |
this.framebuffer.width = texSize[0]; | |
this.framebuffer.height = texSize[1]; | |
const vertices = new Float32Array([-1, -1, | |
1, -1, -1, 1, | |
1, 1 | |
]); | |
const texCoords = new Float32Array([ | |
0, 0, | |
1, 0, | |
0, 1, | |
1, 1 | |
]); | |
const texCoordOffset = vertices.byteLength; | |
let buffer = this.buffer; | |
if (!buffer) { | |
buffer = this.buffer = gl.createBuffer(); | |
gl.bindBuffer(gl.ARRAY_BUFFER, buffer); | |
gl.bufferData(gl.ARRAY_BUFFER, vertices.byteLength + texCoords.byteLength, gl.STATIC_DRAW); | |
} else { | |
gl.bindBuffer(gl.ARRAY_BUFFER, buffer); | |
} | |
gl.bufferSubData(gl.ARRAY_BUFFER, 0, vertices); | |
gl.bufferSubData(gl.ARRAY_BUFFER, texCoordOffset, texCoords); | |
const aPosLoc = gl.getAttribLocation(this.program, 'aPos'); | |
gl.enableVertexAttribArray(aPosLoc); | |
gl.vertexAttribPointer(aPosLoc, 2, gl.FLOAT, gl.FALSE, 0, 0); | |
const aTexCoordLoc = gl.getAttribLocation(this.program, 'aTexCoord'); | |
gl.enableVertexAttribArray(aTexCoordLoc); | |
gl.vertexAttribPointer(aTexCoordLoc, 2, gl.FLOAT, gl.FALSE, 0, texCoordOffset); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); | |
for (let p in this.constants) { | |
const value = this.constants[p] | |
const type = utils.getArgumentType(value); | |
if (type === 'Float' || type === 'Integer') { | |
continue; | |
} | |
gl.useProgram(this.program); | |
this._addConstant(this.constants[p], type, p); | |
this.constantsLength++; | |
} | |
if (!this.outputImmutable) { | |
this._setupOutputTexture(); | |
if ( | |
this.subKernelOutputVariableNames !== null && | |
this.subKernelOutputVariableNames.length > 0 | |
) { | |
this._setupSubOutputTextures(this.subKernelOutputVariableNames.length); | |
} | |
} | |
} | |
run() { | |
if (this.program === null) { | |
this.build.apply(this, arguments); | |
} | |
const paramNames = this.paramNames; | |
const paramTypes = this.paramTypes; | |
const texSize = this.texSize; | |
const gl = this._webGl; | |
gl.useProgram(this.program); | |
gl.scissor(0, 0, texSize[0], texSize[1]); | |
if (!this.hardcodeConstants) { | |
this.setUniform3iv('uOutputDim', this.threadDim); | |
this.setUniform2iv('uTexSize', texSize); | |
} | |
this.setUniform2f('ratio', texSize[0] / this.maxTexSize[0], texSize[1] / this.maxTexSize[1]); | |
this.argumentsLength = 0; | |
for (let texIndex = 0; texIndex < paramNames.length; texIndex++) { | |
this._addArgument(arguments[texIndex], paramTypes[texIndex], paramNames[texIndex]); | |
} | |
if (this.graphical) { | |
if (this.outputToTexture) { | |
gl.bindRenderbuffer(gl.RENDERBUFFER, null); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); | |
if (!this.outputTexture || this.outputImmutable) { | |
this._setupOutputTexture(); | |
} | |
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); | |
return new Texture(this.outputTexture, texSize, this.threadDim, this.output, this._webGl, 'ArrayTexture(4)'); | |
} | |
gl.bindRenderbuffer(gl.RENDERBUFFER, null); | |
gl.bindFramebuffer(gl.FRAMEBUFFER, null); | |
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); | |
return; | |
} | |
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); | |
if (this.outputImmutable) { | |
this._setupOutputTexture(); | |
} | |
const outputTexture = this.outputTexture; | |
if (this.subKernelOutputVariableNames !== null) { | |
if (this.outputImmutable) { | |
this.subKernelOutputTextures = []; | |
this._setupSubOutputTextures(this.subKernelOutputVariableNames.length); | |
} | |
this.drawBuffers.drawBuffersWEBGL(this.drawBuffersMap); | |
} | |
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); | |
if (this.subKernelOutputTextures !== null) { | |
if (this.subKernels !== null) { | |
const output = []; | |
output.result = this.renderOutput(outputTexture); | |
for (let i = 0; i < this.subKernels.length; i++) { | |
output.push(new Texture(this.subKernelOutputTextures[i], texSize, this.threadDim, this.output, this._webGl)); | |
} | |
return output; | |
} else if (this.subKernelProperties !== null) { | |
const output = { | |
result: this.renderOutput(outputTexture) | |
}; | |
let i = 0; | |
for (let p in this.subKernelProperties) { | |
if (!this.subKernelProperties.hasOwnProperty(p)) continue; | |
output[p] = new Texture(this.subKernelOutputTextures[i], texSize, this.threadDim, this.output, this._webGl); | |
i++; | |
} | |
return output; | |
} | |
} | |
return this.renderOutput(outputTexture); | |
} | |
_addArgument(value, type, name) { | |
const gl = this._webGl; | |
const argumentTexture = this.getArgumentTexture(name); | |
if (value instanceof Texture) { | |
type = value.type; | |
} | |
switch (type) { | |
case 'Array': | |
case 'Array(2)': | |
case 'Array(3)': | |
case 'Array(4)': | |
case 'Array2D': | |
case 'Array3D': | |
{ | |
const dim = utils.getDimensions(value, true); | |
const size = utils.dimToTexSize({ | |
floatTextures: this.floatTextures, | |
floatOutput: this.floatOutput | |
}, dim); | |
gl.activeTexture(gl.TEXTURE0 + this.constantsLength + this.argumentsLength); | |
gl.bindTexture(gl.TEXTURE_2D, argumentTexture); | |
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); | |
let length = size[0] * size[1]; | |
const { | |
valuesFlat, | |
bitRatio | |
} = this._formatArrayTransfer(value, length); | |
let buffer; | |
if (this.floatTextures) { | |
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, size[0], size[1], 0, gl.RGBA, gl.FLOAT, valuesFlat); | |
} else { | |
buffer = new Uint8Array(valuesFlat.buffer); | |
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, size[0] / bitRatio, size[1], 0, gl.RGBA, gl.UNSIGNED_BYTE, buffer); | |
} | |
if (!this.hardcodeConstants) { | |
this.setUniform3iv(`user_${name}Dim`, dim); | |
this.setUniform2iv(`user_${name}Size`, size); | |
} | |
this.setUniform1i(`user_${name}BitRatio`, bitRatio); | |
this.setUniform1i(`user_${name}`, this.argumentsLength); | |
break; | |
} | |
case 'Integer': | |
case 'Float': | |
case 'Number': | |
{ | |
this.setUniform1f(`user_${name}`, value); | |
break; | |
} | |
case 'Input': | |
{ | |
const input = value; | |
const dim = input.size; | |
const size = utils.dimToTexSize({ | |
floatTextures: this.floatTextures, | |
floatOutput: this.floatOutput | |
}, dim); | |
gl.activeTexture(gl.TEXTURE0 + this.constantsLength + this.argumentsLength); | |
gl.bindTexture(gl.TEXTURE_2D, argumentTexture); | |
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); | |
let length = size[0] * size[1]; | |
const { | |
valuesFlat, | |
bitRatio | |
} = this._formatArrayTransfer(value.value, length); | |
if (this.floatTextures) { | |
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, size[0], size[1], 0, gl.RGBA, gl.FLOAT, input); | |
} else { | |
const buffer = new Uint8Array(valuesFlat.buffer); | |
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, size[0] / bitRatio, size[1], 0, gl.RGBA, gl.UNSIGNED_BYTE, buffer); | |
} | |
if (!this.hardcodeConstants) { | |
this.setUniform3iv(`user_${name}Dim`, dim); | |
this.setUniform2iv(`user_${name}Size`, size); | |
} | |
this.setUniform1i(`user_${name}BitRatio`, bitRatio); | |
this.setUniform1i(`user_${name}`, this.argumentsLength); | |
break; | |
} | |
case 'HTMLImage': | |
{ | |
const inputImage = value; | |
const dim = [inputImage.width, inputImage.height, 1]; | |
const size = [inputImage.width, inputImage.height]; | |
gl.activeTexture(gl.TEXTURE0 + this.constantsLength + this.argumentsLength); | |
gl.bindTexture(gl.TEXTURE_2D, argumentTexture); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); | |
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); | |
// Upload the image into the texture. | |
const mipLevel = 0; // the largest mip | |
const internalFormat = gl.RGBA; // format we want in the texture | |
const srcFormat = gl.RGBA; // format of data we are supplying | |
const srcType = gl.UNSIGNED_BYTE; // type of data we are supplying | |
gl.texImage2D(gl.TEXTURE_2D, | |
mipLevel, | |
internalFormat, | |
srcFormat, | |
srcType, | |
inputImage); | |
this.setUniform3iv(`user_${name}Dim`, dim); | |
this.setUniform2iv(`user_${name}Size`, size); | |
this.setUniform1i(`user_${name}`, this.argumentsLength); | |
break; | |
} | |
case 'ArrayTexture(4)': | |
case 'NumberTexture': | |
{ | |
const inputTexture = value; | |
const dim = inputTexture.dimensions; | |
const size = inputTexture.size; | |
gl.activeTexture(gl.TEXTURE0 + this.constantsLength + this.argumentsLength); | |
gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); | |
this.setUniform3iv(`user_${name}Dim`, dim); | |
this.setUniform2iv(`user_${name}Size`, size); | |
this.setUniform1i(`user_${name}BitRatio`, 1); // aways float32 | |
this.setUniform1i(`user_${name}`, this.argumentsLength); | |
break; | |
} | |
default: | |
throw new Error('Input type not supported (WebGL): ' + value); | |
} | |
this.argumentsLength++; | |
} | |
_formatArrayTransfer(value, length) { | |
let bitRatio = 1; // bit storage ratio of source to target 'buffer', i.e. if 8bit array -> 32bit tex = 4 | |
let valuesFlat = value; | |
if (utils.isArray(value[0]) || this.floatTextures) { | |
// not already flat | |
valuesFlat = new Float32Array(length); | |
utils.flattenTo(value, valuesFlat); | |
} else { | |
switch (value.constructor) { | |
case Uint8Array: | |
case Int8Array: | |
bitRatio = 4; | |
break; | |
case Uint16Array: | |
case Int16Array: | |
bitRatio = 2; | |
case Float32Array: | |
case Int32Array: | |
break; | |
default: | |
valuesFlat = new Float32Array(length); | |
utils.flattenTo(value, valuesFlat); | |
} | |
} | |
return { | |
bitRatio, | |
valuesFlat | |
}; | |
} | |
getArgumentTexture(name) { | |
return this.getTextureCache(`ARGUMENT_${ name }`); | |
} | |
getTextureCache(name) { | |
if (this.textureCache.hasOwnProperty(name)) { | |
return this.textureCache[name]; | |
} | |
return this.textureCache[name] = this._webGl.createTexture(); | |
} | |
getOutputTexture() { | |
return this.outputTexture; | |
} | |
renderOutput(outputTexture) { | |
const texSize = this.texSize; | |
const gl = this._webGl; | |
const threadDim = this.threadDim; | |
const output = this.output; | |
if (this.outputToTexture) { | |
return new Texture(outputTexture, texSize, this.threadDim, output, this._webGl); | |
} else { | |
let result; | |
if (this.floatOutput) { | |
const w = texSize[0]; | |
const h = Math.ceil(texSize[1] / 4); | |
result = new Float32Array(w * h * 4); | |
gl.readPixels(0, 0, w, h, gl.RGBA, gl.FLOAT, result); | |
} else { | |
const bytes = new Uint8Array(texSize[0] * texSize[1] * 4); | |
gl.readPixels(0, 0, texSize[0], texSize[1], gl.RGBA, gl.UNSIGNED_BYTE, bytes); | |
result = new Float32Array(bytes.buffer); | |
} | |
result = result.subarray(0, threadDim[0] * threadDim[1] * threadDim[2]); | |
if (output.length === 1) { | |
return result; | |
} else if (output.length === 2) { | |
return utils.splitArray(result, output[0]); | |
} else if (output.length === 3) { | |
const cube = utils.splitArray(result, output[0] * output[1]); | |
return cube.map(function(x) { | |
return utils.splitArray(x, output[0]); | |
}); | |
} | |
} | |
} | |
updateMaxTexSize() { | |
const texSize = this.texSize; | |
const canvas = this._canvas; | |
if (this.maxTexSize === null) { | |
let canvasIndex = canvases.indexOf(canvas); | |
if (canvasIndex === -1) { | |
canvasIndex = canvases.length; | |
canvases.push(canvas); | |
maxTexSizes[canvasIndex] = [texSize[0], texSize[1]]; | |
} | |
this.maxTexSize = maxTexSizes[canvasIndex]; | |
} | |
if (this.maxTexSize[0] < texSize[0]) { | |
this.maxTexSize[0] = texSize[0]; | |
} | |
if (this.maxTexSize[1] < texSize[1]) { | |
this.maxTexSize[1] = texSize[1]; | |
} | |
} | |
_setupOutputTexture() { | |
const gl = this._webGl; | |
const texSize = this.texSize; | |
const texture = this.outputTexture = this._webGl.createTexture(); | |
gl.activeTexture(gl.TEXTURE0 + this.constantsLength + this.paramNames.length); | |
gl.bindTexture(gl.TEXTURE_2D, texture); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); | |
if (this.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); | |
} | |
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); | |
} | |
detachTextureCache(name) { | |
delete this.textureCache[name]; | |
} | |
setUniform1f(name, value) { | |
if (this.uniform1fCache.hasOwnProperty(name)) { | |
const cache = this.uniform1fCache[name]; | |
if (value === cache) { | |
return; | |
} | |
} | |
this.uniform1fCache[name] = value; | |
const loc = this.getUniformLocation(name); | |
this._webGl.uniform1f(loc, value); | |
} | |
setUniform1i(name, value) { | |
if (this.uniform1iCache.hasOwnProperty(name)) { | |
const cache = this.uniform1iCache[name]; | |
if (value === cache) { | |
return; | |
} | |
} | |
this.uniform1iCache[name] = value; | |
const loc = this.getUniformLocation(name); | |
this._webGl.uniform1i(loc, value); | |
} | |
setUniform2f(name, value1, value2) { | |
if (this.uniform2fCache.hasOwnProperty(name)) { | |
const cache = this.uniform2fCache[name]; | |
if ( | |
value1 === cache[0] && | |
value2 === cache[1] | |
) { | |
return; | |
} | |
} | |
this.uniform2fCache[name] = [value1, value2]; | |
const loc = this.getUniformLocation(name); | |
this._webGl.uniform2f(loc, value1, value2); | |
} | |
setUniform2fv(name, value) { | |
if (this.uniform2fvCache.hasOwnProperty(name)) { | |
const cache = this.uniform2fvCache[name]; | |
if ( | |
value[0] === cache[0] && | |
value[1] === cache[1] | |
) { | |
return; | |
} | |
} | |
this.uniform2fvCache[name] = value; | |
const loc = this.getUniformLocation(name); | |
this._webGl.uniform2fv(loc, value); | |
} | |
setUniform2iv(name, value) { | |
if (this.uniform2ivCache.hasOwnProperty(name)) { | |
const cache = this.uniform2ivCache[name]; | |
if ( | |
value[0] === cache[0] && | |
value[1] === cache[1] | |
) { | |
return; | |
} | |
} | |
this.uniform2ivCache[name] = value; | |
const loc = this.getUniformLocation(name); | |
this._webGl.uniform2iv(loc, value); | |
} | |
setUniform3fv(name, value) { | |
if (this.uniform3fvCache.hasOwnProperty(name)) { | |
const cache = this.uniform3fvCache[name]; | |
if ( | |
value[0] === cache[0] && | |
value[1] === cache[1] && | |
value[2] === cache[2] | |
) { | |
return; | |
} | |
} | |
this.uniform3fvCache[name] = value; | |
const loc = this.getUniformLocation(name); | |
this._webGl.uniform3fv(loc, value); | |
} | |
setUniform3iv(name, value) { | |
if (this.uniform3ivCache.hasOwnProperty(name)) { | |
const cache = this.uniform3ivCache[name]; | |
if ( | |
value[0] === cache[0] && | |
value[1] === cache[1] && | |
value[2] === cache[2] | |
) { | |
return; | |
} | |
} | |
this.uniform3ivCache[name] = value; | |
const loc = this.getUniformLocation(name); | |
this._webGl.uniform3iv(loc, value); | |
} | |
}; | |
return kernelRunShortcut(new Kernel()); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment