gpu.js
Version:
GPU Accelerated JavaScript
1,059 lines (1,007 loc) • 33.9 kB
JavaScript
const { Kernel } = require('../kernel');
const { utils } = require('../../utils');
const { GLTextureArray2Float } = require('./texture/array-2-float');
const { GLTextureArray2Float2D } = require('./texture/array-2-float-2d');
const { GLTextureArray2Float3D } = require('./texture/array-2-float-3d');
const { GLTextureArray3Float } = require('./texture/array-3-float');
const { GLTextureArray3Float2D } = require('./texture/array-3-float-2d');
const { GLTextureArray3Float3D } = require('./texture/array-3-float-3d');
const { GLTextureArray4Float } = require('./texture/array-4-float');
const { GLTextureArray4Float2D } = require('./texture/array-4-float-2d');
const { GLTextureArray4Float3D } = require('./texture/array-4-float-3d');
const { GLTextureFloat } = require('./texture/float');
const { GLTextureFloat2D } = require('./texture/float-2d');
const { GLTextureFloat3D } = require('./texture/float-3d');
const { GLTextureMemoryOptimized } = require('./texture/memory-optimized');
const { GLTextureMemoryOptimized2D } = require('./texture/memory-optimized-2d');
const { GLTextureMemoryOptimized3D } = require('./texture/memory-optimized-3d');
const { GLTextureUnsigned } = require('./texture/unsigned');
const { GLTextureUnsigned2D } = require('./texture/unsigned-2d');
const { GLTextureUnsigned3D } = require('./texture/unsigned-3d');
const { GLTextureGraphical } = require('./texture/graphical');
/**
* @abstract
* @extends Kernel
*/
class GLKernel extends Kernel {
static get mode() {
return 'gpu';
}
static getIsFloatRead() {
const kernelString = `function kernelFunction() {
return 1;
}`;
const kernel = new this(kernelString, {
context: this.testContext,
canvas: this.testCanvas,
validate: false,
output: [1],
precision: 'single',
returnType: 'Number',
tactic: 'speed',
});
kernel.build();
kernel.run();
const result = kernel.renderOutput();
kernel.destroy(true);
return result[0] === 1;
}
static getIsIntegerDivisionAccurate() {
function kernelFunction(v1, v2) {
return v1[this.thread.x] / v2[this.thread.x];
}
const kernel = new this(kernelFunction.toString(), {
context: this.testContext,
canvas: this.testCanvas,
validate: false,
output: [2],
returnType: 'Number',
precision: 'unsigned',
tactic: 'speed',
});
const args = [
[6, 6030401],
[3, 3991]
];
kernel.build.apply(kernel, args);
kernel.run.apply(kernel, args);
const result = kernel.renderOutput();
kernel.destroy(true);
// have we not got whole numbers for 6/3 or 6030401/3991
// add more here if others see this problem
return result[0] === 2 && result[1] === 1511;
}
static getIsSpeedTacticSupported() {
function kernelFunction(value) {
return value[this.thread.x];
}
const kernel = new this(kernelFunction.toString(), {
context: this.testContext,
canvas: this.testCanvas,
validate: false,
output: [4],
returnType: 'Number',
precision: 'unsigned',
tactic: 'speed',
});
const args = [
[0, 1, 2, 3]
];
kernel.build.apply(kernel, args);
kernel.run.apply(kernel, args);
const result = kernel.renderOutput();
kernel.destroy(true);
return Math.round(result[0]) === 0 && Math.round(result[1]) === 1 && Math.round(result[2]) === 2 && Math.round(result[3]) === 3;
}
/**
* @abstract
*/
static get testCanvas() {
throw new Error(`"testCanvas" not defined on ${ this.name }`);
}
/**
* @abstract
*/
static get testContext() {
throw new Error(`"testContext" not defined on ${ this.name }`);
}
static getFeatures() {
const gl = this.testContext;
const isDrawBuffers = this.getIsDrawBuffers();
return Object.freeze({
isFloatRead: this.getIsFloatRead(),
isIntegerDivisionAccurate: this.getIsIntegerDivisionAccurate(),
isSpeedTacticSupported: this.getIsSpeedTacticSupported(),
isTextureFloat: this.getIsTextureFloat(),
isDrawBuffers,
kernelMap: isDrawBuffers,
channelCount: this.getChannelCount(),
maxTextureSize: this.getMaxTextureSize(),
lowIntPrecision: gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.LOW_INT),
lowFloatPrecision: gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.LOW_FLOAT),
mediumIntPrecision: gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.MEDIUM_INT),
mediumFloatPrecision: gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT),
highIntPrecision: gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_INT),
highFloatPrecision: gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT),
});
}
/**
* @abstract
*/
static setupFeatureChecks() {
throw new Error(`"setupFeatureChecks" not defined on ${ this.name }`);
}
static getSignature(kernel, argumentTypes) {
return kernel.getVariablePrecisionString() + (argumentTypes.length > 0 ? ':' + argumentTypes.join(',') : '');
}
/**
* @desc Fix division by factor of 3 FP accuracy bug
* @param {Boolean} fix - should fix
*/
setFixIntegerDivisionAccuracy(fix) {
this.fixIntegerDivisionAccuracy = fix;
return this;
}
/**
* @desc Toggle output mode
* @param {String} flag - 'single' or 'unsigned'
*/
setPrecision(flag) {
this.precision = flag;
return this;
}
/**
* @desc Toggle texture output mode
* @param {Boolean} flag - true to enable floatTextures
* @deprecated
*/
setFloatTextures(flag) {
utils.warnDeprecated('method', 'setFloatTextures', 'setOptimizeFloatMemory');
this.floatTextures = flag;
return this;
}
/**
* A highly readable very forgiving micro-parser for a glsl function that gets argument types
* @param {String} source
* @returns {{argumentTypes: String[], argumentNames: String[]}}
*/
static nativeFunctionArguments(source) {
const argumentTypes = [];
const argumentNames = [];
const states = [];
const isStartingVariableName = /^[a-zA-Z_]/;
const isVariableChar = /[a-zA-Z_0-9]/;
let i = 0;
let argumentName = null;
let argumentType = null;
while (i < source.length) {
const char = source[i];
const nextChar = source[i + 1];
const state = states.length > 0 ? states[states.length - 1] : null;
// begin MULTI_LINE_COMMENT handling
if (state === 'FUNCTION_ARGUMENTS' && char === '/' && nextChar === '*') {
states.push('MULTI_LINE_COMMENT');
i += 2;
continue;
} else if (state === 'MULTI_LINE_COMMENT' && char === '*' && nextChar === '/') {
states.pop();
i += 2;
continue;
}
// end MULTI_LINE_COMMENT handling
// begin COMMENT handling
else if (state === 'FUNCTION_ARGUMENTS' && char === '/' && nextChar === '/') {
states.push('COMMENT');
i += 2;
continue;
} else if (state === 'COMMENT' && char === '\n') {
states.pop();
i++;
continue;
}
// end COMMENT handling
// being FUNCTION_ARGUMENTS handling
else if (state === null && char === '(') {
states.push('FUNCTION_ARGUMENTS');
i++;
continue;
} else if (state === 'FUNCTION_ARGUMENTS') {
if (char === ')') {
states.pop();
break;
}
if (char === 'f' && nextChar === 'l' && source[i + 2] === 'o' && source[i + 3] === 'a' && source[i + 4] === 't' && source[i + 5] === ' ') {
states.push('DECLARE_VARIABLE');
argumentType = 'float';
argumentName = '';
i += 6;
continue;
} else if (char === 'i' && nextChar === 'n' && source[i + 2] === 't' && source[i + 3] === ' ') {
states.push('DECLARE_VARIABLE');
argumentType = 'int';
argumentName = '';
i += 4;
continue;
} else if (char === 'v' && nextChar === 'e' && source[i + 2] === 'c' && source[i + 3] === '2' && source[i + 4] === ' ') {
states.push('DECLARE_VARIABLE');
argumentType = 'vec2';
argumentName = '';
i += 5;
continue;
} else if (char === 'v' && nextChar === 'e' && source[i + 2] === 'c' && source[i + 3] === '3' && source[i + 4] === ' ') {
states.push('DECLARE_VARIABLE');
argumentType = 'vec3';
argumentName = '';
i += 5;
continue;
} else if (char === 'v' && nextChar === 'e' && source[i + 2] === 'c' && source[i + 3] === '4' && source[i + 4] === ' ') {
states.push('DECLARE_VARIABLE');
argumentType = 'vec4';
argumentName = '';
i += 5;
continue;
}
}
// end FUNCTION_ARGUMENTS handling
// begin DECLARE_VARIABLE handling
else if (state === 'DECLARE_VARIABLE') {
if (argumentName === '') {
if (char === ' ') {
i++;
continue;
}
if (!isStartingVariableName.test(char)) {
throw new Error('variable name is not expected string');
}
}
argumentName += char;
if (!isVariableChar.test(nextChar)) {
states.pop();
argumentNames.push(argumentName);
argumentTypes.push(typeMap[argumentType]);
}
}
// end DECLARE_VARIABLE handling
// Progress to next character
i++;
}
if (states.length > 0) {
throw new Error('GLSL function was not parsable');
}
return {
argumentNames,
argumentTypes,
};
}
static nativeFunctionReturnType(source) {
return typeMap[source.match(/int|float|vec[2-4]/)[0]];
}
static combineKernels(combinedKernel, lastKernel) {
combinedKernel.apply(null, arguments);
const {
texSize,
context,
threadDim
} = lastKernel.texSize;
let result;
if (lastKernel.precision === 'single') {
const w = texSize[0];
const h = Math.ceil(texSize[1] / 4);
result = new Float32Array(w * h * 4 * 4);
context.readPixels(0, 0, w, h * 4, context.RGBA, context.FLOAT, result);
} else {
const bytes = new Uint8Array(texSize[0] * texSize[1] * 4);
context.readPixels(0, 0, texSize[0], texSize[1], context.RGBA, context.UNSIGNED_BYTE, bytes);
result = new Float32Array(bytes.buffer);
}
result = result.subarray(0, threadDim[0] * threadDim[1] * threadDim[2]);
if (lastKernel.output.length === 1) {
return result;
} else if (lastKernel.output.length === 2) {
return utils.splitArray(result, lastKernel.output[0]);
} else if (lastKernel.output.length === 3) {
const cube = utils.splitArray(result, lastKernel.output[0] * lastKernel.output[1]);
return cube.map(function(x) {
return utils.splitArray(x, lastKernel.output[0]);
});
}
}
constructor(source, settings) {
super(source, settings);
this.transferValues = null;
this.formatValues = null;
/**
*
* @type {Texture}
*/
this.TextureConstructor = null;
this.renderOutput = null;
this.renderRawOutput = null;
this.texSize = null;
this.translatedSource = null;
this.compiledFragmentShader = null;
this.compiledVertexShader = null;
this.switchingKernels = null;
this._textureSwitched = null;
this._mappedTextureSwitched = null;
}
checkTextureSize() {
const { features } = this.constructor;
if (this.texSize[0] > features.maxTextureSize || this.texSize[1] > features.maxTextureSize) {
throw new Error(`Texture size [${this.texSize[0]},${this.texSize[1]}] generated by kernel is larger than supported size [${features.maxTextureSize},${features.maxTextureSize}]`);
}
}
translateSource() {
throw new Error(`"translateSource" not defined on ${this.constructor.name}`);
}
/**
* Picks a render strategy for the now finally parsed kernel
* @param args
* @return {null|KernelOutput}
*/
pickRenderStrategy(args) {
if (this.graphical) {
this.renderRawOutput = this.readPackedPixelsToUint8Array;
this.transferValues = (pixels) => pixels;
this.TextureConstructor = GLTextureGraphical;
return null;
}
if (this.precision === 'unsigned') {
this.renderRawOutput = this.readPackedPixelsToUint8Array;
this.transferValues = this.readPackedPixelsToFloat32Array;
if (this.pipeline) {
this.renderOutput = this.renderTexture;
if (this.subKernels !== null) {
this.renderKernels = this.renderKernelsToTextures;
}
switch (this.returnType) {
case 'LiteralInteger':
case 'Float':
case 'Number':
case 'Integer':
if (this.output[2] > 0) {
this.TextureConstructor = GLTextureUnsigned3D;
return null;
} else if (this.output[1] > 0) {
this.TextureConstructor = GLTextureUnsigned2D;
return null;
} else {
this.TextureConstructor = GLTextureUnsigned;
return null;
}
case 'Array(2)':
case 'Array(3)':
case 'Array(4)':
return this.requestFallback(args);
}
} else {
if (this.subKernels !== null) {
this.renderKernels = this.renderKernelsToArrays;
}
switch (this.returnType) {
case 'LiteralInteger':
case 'Float':
case 'Number':
case 'Integer':
this.renderOutput = this.renderValues;
if (this.output[2] > 0) {
this.TextureConstructor = GLTextureUnsigned3D;
this.formatValues = utils.erect3DPackedFloat;
return null;
} else if (this.output[1] > 0) {
this.TextureConstructor = GLTextureUnsigned2D;
this.formatValues = utils.erect2DPackedFloat;
return null;
} else {
this.TextureConstructor = GLTextureUnsigned;
this.formatValues = utils.erectPackedFloat;
return null;
}
case 'Array(2)':
case 'Array(3)':
case 'Array(4)':
return this.requestFallback(args);
}
}
} else if (this.precision === 'single') {
this.renderRawOutput = this.readFloatPixelsToFloat32Array;
this.transferValues = this.readFloatPixelsToFloat32Array;
if (this.pipeline) {
this.renderOutput = this.renderTexture;
if (this.subKernels !== null) {
this.renderKernels = this.renderKernelsToTextures;
}
switch (this.returnType) {
case 'LiteralInteger':
case 'Float':
case 'Number':
case 'Integer': {
if (this.optimizeFloatMemory) {
if (this.output[2] > 0) {
this.TextureConstructor = GLTextureMemoryOptimized3D;
return null;
} else if (this.output[1] > 0) {
this.TextureConstructor = GLTextureMemoryOptimized2D;
return null;
} else {
this.TextureConstructor = GLTextureMemoryOptimized;
return null;
}
} else {
if (this.output[2] > 0) {
this.TextureConstructor = GLTextureFloat3D;
return null;
} else if (this.output[1] > 0) {
this.TextureConstructor = GLTextureFloat2D;
return null;
} else {
this.TextureConstructor = GLTextureFloat;
return null;
}
}
}
case 'Array(2)': {
if (this.output[2] > 0) {
this.TextureConstructor = GLTextureArray2Float3D;
return null;
} else if (this.output[1] > 0) {
this.TextureConstructor = GLTextureArray2Float2D;
return null;
} else {
this.TextureConstructor = GLTextureArray2Float;
return null;
}
}
case 'Array(3)': {
if (this.output[2] > 0) {
this.TextureConstructor = GLTextureArray3Float3D;
return null;
} else if (this.output[1] > 0) {
this.TextureConstructor = GLTextureArray3Float2D;
return null;
} else {
this.TextureConstructor = GLTextureArray3Float;
return null;
}
}
case 'Array(4)': {
if (this.output[2] > 0) {
this.TextureConstructor = GLTextureArray4Float3D;
return null;
} else if (this.output[1] > 0) {
this.TextureConstructor = GLTextureArray4Float2D;
return null;
} else {
this.TextureConstructor = GLTextureArray4Float;
return null;
}
}
}
}
this.renderOutput = this.renderValues;
if (this.subKernels !== null) {
this.renderKernels = this.renderKernelsToArrays;
}
if (this.optimizeFloatMemory) {
switch (this.returnType) {
case 'LiteralInteger':
case 'Float':
case 'Number':
case 'Integer': {
if (this.output[2] > 0) {
this.TextureConstructor = GLTextureMemoryOptimized3D;
this.formatValues = utils.erectMemoryOptimized3DFloat;
return null;
} else if (this.output[1] > 0) {
this.TextureConstructor = GLTextureMemoryOptimized2D;
this.formatValues = utils.erectMemoryOptimized2DFloat;
return null;
} else {
this.TextureConstructor = GLTextureMemoryOptimized;
this.formatValues = utils.erectMemoryOptimizedFloat;
return null;
}
}
case 'Array(2)': {
if (this.output[2] > 0) {
this.TextureConstructor = GLTextureArray2Float3D;
this.formatValues = utils.erect3DArray2;
return null;
} else if (this.output[1] > 0) {
this.TextureConstructor = GLTextureArray2Float2D;
this.formatValues = utils.erect2DArray2;
return null;
} else {
this.TextureConstructor = GLTextureArray2Float;
this.formatValues = utils.erectArray2;
return null;
}
}
case 'Array(3)': {
if (this.output[2] > 0) {
this.TextureConstructor = GLTextureArray3Float3D;
this.formatValues = utils.erect3DArray3;
return null;
} else if (this.output[1] > 0) {
this.TextureConstructor = GLTextureArray3Float2D;
this.formatValues = utils.erect2DArray3;
return null;
} else {
this.TextureConstructor = GLTextureArray3Float;
this.formatValues = utils.erectArray3;
return null;
}
}
case 'Array(4)': {
if (this.output[2] > 0) {
this.TextureConstructor = GLTextureArray4Float3D;
this.formatValues = utils.erect3DArray4;
return null;
} else if (this.output[1] > 0) {
this.TextureConstructor = GLTextureArray4Float2D;
this.formatValues = utils.erect2DArray4;
return null;
} else {
this.TextureConstructor = GLTextureArray4Float;
this.formatValues = utils.erectArray4;
return null;
}
}
}
} else {
switch (this.returnType) {
case 'LiteralInteger':
case 'Float':
case 'Number':
case 'Integer': {
if (this.output[2] > 0) {
this.TextureConstructor = GLTextureFloat3D;
this.formatValues = utils.erect3DFloat;
return null;
} else if (this.output[1] > 0) {
this.TextureConstructor = GLTextureFloat2D;
this.formatValues = utils.erect2DFloat;
return null;
} else {
this.TextureConstructor = GLTextureFloat;
this.formatValues = utils.erectFloat;
return null;
}
}
case 'Array(2)': {
if (this.output[2] > 0) {
this.TextureConstructor = GLTextureArray2Float3D;
this.formatValues = utils.erect3DArray2;
return null;
} else if (this.output[1] > 0) {
this.TextureConstructor = GLTextureArray2Float2D;
this.formatValues = utils.erect2DArray2;
return null;
} else {
this.TextureConstructor = GLTextureArray2Float;
this.formatValues = utils.erectArray2;
return null;
}
}
case 'Array(3)': {
if (this.output[2] > 0) {
this.TextureConstructor = GLTextureArray3Float3D;
this.formatValues = utils.erect3DArray3;
return null;
} else if (this.output[1] > 0) {
this.TextureConstructor = GLTextureArray3Float2D;
this.formatValues = utils.erect2DArray3;
return null;
} else {
this.TextureConstructor = GLTextureArray3Float;
this.formatValues = utils.erectArray3;
return null;
}
}
case 'Array(4)': {
if (this.output[2] > 0) {
this.TextureConstructor = GLTextureArray4Float3D;
this.formatValues = utils.erect3DArray4;
return null;
} else if (this.output[1] > 0) {
this.TextureConstructor = GLTextureArray4Float2D;
this.formatValues = utils.erect2DArray4;
return null;
} else {
this.TextureConstructor = GLTextureArray4Float;
this.formatValues = utils.erectArray4;
return null;
}
}
}
}
} else {
throw new Error(`unhandled precision of "${this.precision}"`);
}
throw new Error(`unhandled return type "${this.returnType}"`);
}
/**
* @abstract
* @returns String
*/
getKernelString() {
throw new Error(`abstract method call`);
}
getMainResultTexture() {
switch (this.returnType) {
case 'LiteralInteger':
case 'Float':
case 'Integer':
case 'Number':
return this.getMainResultNumberTexture();
case 'Array(2)':
return this.getMainResultArray2Texture();
case 'Array(3)':
return this.getMainResultArray3Texture();
case 'Array(4)':
return this.getMainResultArray4Texture();
default:
throw new Error(`unhandled returnType type ${ this.returnType }`);
}
}
/**
* @abstract
* @returns String[]
*/
getMainResultKernelNumberTexture() {
throw new Error(`abstract method call`);
}
/**
* @abstract
* @returns String[]
*/
getMainResultSubKernelNumberTexture() {
throw new Error(`abstract method call`);
}
/**
* @abstract
* @returns String[]
*/
getMainResultKernelArray2Texture() {
throw new Error(`abstract method call`);
}
/**
* @abstract
* @returns String[]
*/
getMainResultSubKernelArray2Texture() {
throw new Error(`abstract method call`);
}
/**
* @abstract
* @returns String[]
*/
getMainResultKernelArray3Texture() {
throw new Error(`abstract method call`);
}
/**
* @abstract
* @returns String[]
*/
getMainResultSubKernelArray3Texture() {
throw new Error(`abstract method call`);
}
/**
* @abstract
* @returns String[]
*/
getMainResultKernelArray4Texture() {
throw new Error(`abstract method call`);
}
/**
* @abstract
* @returns String[]
*/
getMainResultSubKernelArray4Texture() {
throw new Error(`abstract method call`);
}
/**
* @abstract
* @returns String[]
*/
getMainResultGraphical() {
throw new Error(`abstract method call`);
}
/**
* @abstract
* @returns String[]
*/
getMainResultMemoryOptimizedFloats() {
throw new Error(`abstract method call`);
}
/**
* @abstract
* @returns String[]
*/
getMainResultPackedPixels() {
throw new Error(`abstract method call`);
}
getMainResultString() {
if (this.graphical) {
return this.getMainResultGraphical();
} else if (this.precision === 'single') {
if (this.optimizeFloatMemory) {
return this.getMainResultMemoryOptimizedFloats();
}
return this.getMainResultTexture();
} else {
return this.getMainResultPackedPixels();
}
}
getMainResultNumberTexture() {
return utils.linesToString(this.getMainResultKernelNumberTexture()) +
utils.linesToString(this.getMainResultSubKernelNumberTexture());
}
getMainResultArray2Texture() {
return utils.linesToString(this.getMainResultKernelArray2Texture()) +
utils.linesToString(this.getMainResultSubKernelArray2Texture());
}
getMainResultArray3Texture() {
return utils.linesToString(this.getMainResultKernelArray3Texture()) +
utils.linesToString(this.getMainResultSubKernelArray3Texture());
}
getMainResultArray4Texture() {
return utils.linesToString(this.getMainResultKernelArray4Texture()) +
utils.linesToString(this.getMainResultSubKernelArray4Texture());
}
/**
*
* @return {string}
*/
getFloatTacticDeclaration() {
const variablePrecision = this.getVariablePrecisionString(this.texSize, this.tactic);
return `precision ${variablePrecision} float;\n`;
}
/**
*
* @return {string}
*/
getIntTacticDeclaration() {
return `precision ${this.getVariablePrecisionString(this.texSize, this.tactic, true)} int;\n`;
}
/**
*
* @return {string}
*/
getSampler2DTacticDeclaration() {
return `precision ${this.getVariablePrecisionString(this.texSize, this.tactic)} sampler2D;\n`;
}
getSampler2DArrayTacticDeclaration() {
return `precision ${this.getVariablePrecisionString(this.texSize, this.tactic)} sampler2DArray;\n`;
}
renderTexture() {
return this.immutable ? this.texture.clone() : this.texture;
}
readPackedPixelsToUint8Array() {
if (this.precision !== 'unsigned') throw new Error('Requires this.precision to be "unsigned"');
const {
texSize,
context: gl
} = this;
const result = new Uint8Array(texSize[0] * texSize[1] * 4);
gl.readPixels(0, 0, texSize[0], texSize[1], gl.RGBA, gl.UNSIGNED_BYTE, result);
return result;
}
readPackedPixelsToFloat32Array() {
return new Float32Array(this.readPackedPixelsToUint8Array().buffer);
}
readFloatPixelsToFloat32Array() {
if (this.precision !== 'single') throw new Error('Requires this.precision to be "single"');
const {
texSize,
context: gl
} = this;
const w = texSize[0];
const h = texSize[1];
const result = new Float32Array(w * h * 4);
gl.readPixels(0, 0, w, h, gl.RGBA, gl.FLOAT, result);
return result;
}
/**
*
* @param {Boolean} [flip]
* @return {Uint8ClampedArray}
*/
getPixels(flip) {
const {
context: gl,
output
} = this;
const [width, height] = output;
const pixels = new Uint8Array(width * height * 4);
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
// flipped by default, so invert
return new Uint8ClampedArray((flip ? pixels : utils.flipPixels(pixels, width, height)).buffer);
}
renderKernelsToArrays() {
const result = {
result: this.renderOutput(),
};
for (let i = 0; i < this.subKernels.length; i++) {
result[this.subKernels[i].property] = this.mappedTextures[i].toArray();
}
return result;
}
renderKernelsToTextures() {
const result = {
result: this.renderOutput(),
};
if (this.immutable) {
for (let i = 0; i < this.subKernels.length; i++) {
result[this.subKernels[i].property] = this.mappedTextures[i].clone();
}
} else {
for (let i = 0; i < this.subKernels.length; i++) {
result[this.subKernels[i].property] = this.mappedTextures[i];
}
}
return result;
}
resetSwitchingKernels() {
const existingValue = this.switchingKernels;
this.switchingKernels = null;
return existingValue;
}
setOutput(output) {
const newOutput = this.toKernelOutput(output);
if (this.program) {
if (!this.dynamicOutput) {
throw new Error('Resizing a kernel with dynamicOutput: false is not possible');
}
const newThreadDim = [newOutput[0], newOutput[1] || 1, newOutput[2] || 1];
const newTexSize = utils.getKernelTextureSize({
optimizeFloatMemory: this.optimizeFloatMemory,
precision: this.precision,
}, newThreadDim);
const oldTexSize = this.texSize;
if (oldTexSize) {
const oldPrecision = this.getVariablePrecisionString(oldTexSize, this.tactic);
const newPrecision = this.getVariablePrecisionString(newTexSize, this.tactic);
if (oldPrecision !== newPrecision) {
if (this.debug) {
console.warn('Precision requirement changed, asking GPU instance to recompile');
}
this.switchKernels({
type: 'outputPrecisionMismatch',
precision: newPrecision,
needed: output
});
return;
}
}
this.output = newOutput;
this.threadDim = newThreadDim;
this.texSize = newTexSize;
const { context: gl } = this;
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
this.updateMaxTexSize();
this.framebuffer.width = this.texSize[0];
this.framebuffer.height = this.texSize[1];
gl.viewport(0, 0, this.maxTexSize[0], this.maxTexSize[1]);
this.canvas.width = this.maxTexSize[0];
this.canvas.height = this.maxTexSize[1];
if (this.texture) {
this.texture.delete();
}
this.texture = null;
this._setupOutputTexture();
if (this.mappedTextures && this.mappedTextures.length > 0) {
for (let i = 0; i < this.mappedTextures.length; i++) {
this.mappedTextures[i].delete();
}
this.mappedTextures = null;
this._setupSubOutputTextures();
}
} else {
this.output = newOutput;
}
return this;
}
renderValues() {
return this.formatValues(
this.transferValues(),
this.output[0],
this.output[1],
this.output[2]
);
}
switchKernels(reason) {
if (this.switchingKernels) {
this.switchingKernels.push(reason);
} else {
this.switchingKernels = [reason];
}
}
getVariablePrecisionString(textureSize = this.texSize, tactic = this.tactic, isInt = false) {
if (!tactic) {
if (!this.constructor.features.isSpeedTacticSupported) return 'highp';
const low = this.constructor.features[isInt ? 'lowIntPrecision' : 'lowFloatPrecision'];
const medium = this.constructor.features[isInt ? 'mediumIntPrecision' : 'mediumFloatPrecision'];
const high = this.constructor.features[isInt ? 'highIntPrecision' : 'highFloatPrecision'];
const requiredSize = Math.log2(textureSize[0] * textureSize[1]);
if (requiredSize <= low.rangeMax) {
return 'lowp';
} else if (requiredSize <= medium.rangeMax) {
return 'mediump';
} else if (requiredSize <= high.rangeMax) {
return 'highp';
} else {
throw new Error(`The required size exceeds that of the ability of your system`);
}
}
switch (tactic) {
case 'speed':
return 'lowp';
case 'balanced':
return 'mediump';
case 'precision':
return 'highp';
default:
throw new Error(`Unknown tactic "${tactic}" use "speed", "balanced", "precision", or empty for auto`);
}
}
/**
*
* @param {WebGLKernelValue} kernelValue
* @param {GLTexture} arg
*/
updateTextureArgumentRefs(kernelValue, arg) {
if (!this.immutable) return;
if (this.texture.texture === arg.texture) {
const { prevArg } = kernelValue;
if (prevArg) {
if (prevArg.texture._refs === 1) {
this.texture.delete();
this.texture = prevArg.clone();
this._textureSwitched = true;
}
prevArg.delete();
}
kernelValue.prevArg = arg.clone();
} else if (this.mappedTextures && this.mappedTextures.length > 0) {
const { mappedTextures } = this;
for (let i = 0; i < mappedTextures.length; i++) {
const mappedTexture = mappedTextures[i];
if (mappedTexture.texture === arg.texture) {
const { prevArg } = kernelValue;
if (prevArg) {
if (prevArg.texture._refs === 1) {
mappedTexture.delete();
mappedTextures[i] = prevArg.clone();
this._mappedTextureSwitched[i] = true;
}
prevArg.delete();
}
kernelValue.prevArg = arg.clone();
return;
}
}
}
}
onActivate(previousKernel) {
this._textureSwitched = true;
this.texture = previousKernel.texture;
if (this.mappedTextures) {
for (let i = 0; i < this.mappedTextures.length; i++) {
this._mappedTextureSwitched[i] = true;
}
this.mappedTextures = previousKernel.mappedTextures;
}
}
initCanvas() {}
}
const typeMap = {
int: 'Integer',
float: 'Number',
vec2: 'Array(2)',
vec3: 'Array(3)',
vec4: 'Array(4)',
};
module.exports = {
GLKernel
};