gpu.js/test/internal/backend/gl-kernel.js
Robert Plummer 7e62639033 fix: #585 check for inaccurate results for very small kernel
fix: #585 add features.isSpeedTacticSupported and tests
2020-03-30 08:21:09 -04:00

405 lines
13 KiB
JavaScript

const { assert, test, module: describe, only, skip } = require('qunit');
const sinon = require('sinon');
const { GLKernel, GPU } = require(process.cwd() + '/src');
describe('GLKernel');
test('nativeFunctionArguments() parse simple function', () => {
const result = GLKernel.nativeFunctionArguments(`vec2 myFunction(vec2 longName) {
return vec2(1, 1);
}`);
assert.deepEqual(result, {
argumentNames: ['longName'],
argumentTypes: ['Array(2)']
});
});
test('nativeFunctionArguments() parse simple function with argument that has number', () => {
const result = GLKernel.nativeFunctionArguments(`vec2 myFunction(vec2 longName123) {
return vec2(1, 1);
}`);
assert.deepEqual(result, {
argumentNames: ['longName123'],
argumentTypes: ['Array(2)']
});
});
test('nativeFunctionArguments() parse simple function, multiple arguments', () => {
const result = GLKernel.nativeFunctionArguments(`vec2 myFunction(vec3 a,vec3 b,float c) {
return vec2(1, 1);
}`);
assert.deepEqual(result, {
argumentNames: ['a', 'b', 'c'],
argumentTypes: ['Array(3)', 'Array(3)', 'Number']
});
});
test('nativeFunctionArguments() parse simple function, multiple arguments with comments', () => {
const result = GLKernel.nativeFunctionArguments(`vec2 myFunction(vec3 a /* vec4 b */,vec2 c, /* vec4 d */ float e) {
return vec2(1, 1);
}`);
assert.deepEqual(result, {
argumentNames: ['a', 'c', 'e'],
argumentTypes: ['Array(3)', 'Array(2)', 'Number']
});
});
test('nativeFunctionArguments() parse simple function, multiple arguments on multi line with spaces', () => {
const result = GLKernel.nativeFunctionArguments(`vec2 myFunction(
vec4 a,
vec3 b,
float c
) {
vec3 delta = a - b;
}`);
assert.deepEqual(result, {
argumentNames: ['a', 'b', 'c'],
argumentTypes: ['Array(4)', 'Array(3)', 'Number']
});
});
test('nativeFunctionArguments() parse simple function, multiple arguments on multi line with spaces and multi-line-comments', () => {
const result = GLKernel.nativeFunctionArguments(`vec2 myFunction(
vec2 a,
/* test 1 */
vec3 b,
/* test 2 */
float c
/* test 3 */
) {
vec3 delta = a - b;
}`);
assert.deepEqual(result, {
argumentNames: ['a', 'b', 'c'],
argumentTypes: ['Array(2)', 'Array(3)', 'Number']
});
});
test('nativeFunctionArguments() parse simple function, multiple arguments on multi line with spaces and in-line-comments', () => {
const result = GLKernel.nativeFunctionArguments(`vec2 myFunction(
vec2 a, // test 1
vec4 b, // test 2
int c // test 3
) {
vec3 delta = a - b;
}`);
assert.deepEqual(result, {
argumentNames: ['a', 'b', 'c'],
argumentTypes: ['Array(2)', 'Array(4)', 'Integer']
});
});
test('nativeFunctionArguments() parse simple function that is cut short', () => {
const result = GLKernel.nativeFunctionArguments(`vec2 myFunction(
vec2 a,
vec3 b,
float c
)`);
assert.deepEqual(result, {
argumentNames: ['a', 'b', 'c'],
argumentTypes: ['Array(2)', 'Array(3)', 'Number']
});
});
test('getVariablePrecisionString() when tactic is set to "speed" returns "lowp"', () => {
assert.equal(GLKernel.prototype.getVariablePrecisionString.call({ tactic: 'speed' }), 'lowp');
});
test('getVariablePrecisionString() when tactic is set to "balanced" returns "mediump"', () => {
assert.equal(GLKernel.prototype.getVariablePrecisionString.call({ tactic: 'balanced' }), 'mediump');
});
test('getVariablePrecisionString() when tactic is set to "precision" returns "highp"', () => {
assert.equal(GLKernel.prototype.getVariablePrecisionString.call({ tactic: 'precision' }), 'highp');
});
test('getVariablePrecisionString() when tactic is not set and texSize is within lowFloatPrecision', () => {
const mockInstance = {
tactic: null,
constructor: {
features: {
lowFloatPrecision: { rangeMax: Math.log2(3 * 3) },
mediumFloatPrecision: { rangeMax: Math.log2(4 * 4) },
highFloatPrecision: { rangeMax: Math.log2(5 * 5) },
isSpeedTacticSupported: true,
}
}
};
const textureSize = [2, 2];
assert.equal(GLKernel.prototype.getVariablePrecisionString.call(mockInstance, textureSize), 'lowp');
});
test('getVariablePrecisionString() when tactic is not set and texSize is within mediumFloatPrecision', () => {
const mockInstance = {
tactic: null,
constructor: {
features: {
lowFloatPrecision: { rangeMax: Math.log2(3 * 3) },
mediumFloatPrecision: { rangeMax: Math.log2(4 * 4) },
highFloatPrecision: { rangeMax: Math.log2(5 * 5) },
isSpeedTacticSupported: true,
}
}
};
const textureSize = [4, 4];
assert.equal(GLKernel.prototype.getVariablePrecisionString.call(mockInstance, textureSize), 'mediump');
});
test('getVariablePrecisionString() when tactic is not set and texSize is within highFloatPrecision', () => {
const mockInstance = {
tactic: null,
constructor: {
features: {
lowFloatPrecision: { rangeMax: Math.log2(3 * 3) },
mediumFloatPrecision: { rangeMax: Math.log2(4 * 4) },
highFloatPrecision: { rangeMax: Math.log2(5 * 5) },
isSpeedTacticSupported: true,
}
}
};
const textureSize = [5, 5];
assert.equal(GLKernel.prototype.getVariablePrecisionString.call(mockInstance, textureSize), 'highp');
});
test('getVariablePrecisionString() when tactic is not set and texSize is outside highFloatPrecision', () => {
const mockInstance = {
tactic: null,
constructor: {
features: {
lowFloatPrecision: { rangeMax: Math.log2(3 * 3) },
mediumFloatPrecision: { rangeMax: Math.log2(4 * 4) },
highFloatPrecision: { rangeMax: Math.log2(5 * 5) },
isSpeedTacticSupported: true,
}
}
};
const textureSize = [6, 6];
assert.throws(() => GLKernel.prototype.getVariablePrecisionString.call(mockInstance, textureSize));
});
test('getVariablePrecisionString() when tactic is not set and texSize is within lowIntPrecision', () => {
const mockInstance = {
tactic: null,
constructor: {
features: {
lowIntPrecision: { rangeMax: Math.log2(3 * 3) },
mediumIntPrecision: { rangeMax: Math.log2(4 * 4) },
highIntPrecision: { rangeMax: Math.log2(5 * 5) },
isSpeedTacticSupported: true,
}
}
};
const textureSize = [2, 2];
assert.equal(GLKernel.prototype.getVariablePrecisionString.call(mockInstance, textureSize, null, true), 'lowp');
});
test('getVariablePrecisionString() when tactic is not set and texSize is within mediumIntPrecision', () => {
const mockInstance = {
tactic: null,
constructor: {
features: {
lowIntPrecision: { rangeMax: Math.log2(3 * 3) },
mediumIntPrecision: { rangeMax: Math.log2(4 * 4) },
highIntPrecision: { rangeMax: Math.log2(5 * 5) },
isSpeedTacticSupported: true,
}
}
};
const textureSize = [4, 4];
assert.equal(GLKernel.prototype.getVariablePrecisionString.call(mockInstance, textureSize, null, true), 'mediump');
});
test('getVariablePrecisionString() when tactic is not set and texSize is within highIntPrecision', () => {
const mockInstance = {
tactic: null,
constructor: {
features: {
lowIntPrecision: { rangeMax: Math.log2(3 * 3) },
mediumIntPrecision: { rangeMax: Math.log2(4 * 4) },
highIntPrecision: { rangeMax: Math.log2(5 * 5) },
isSpeedTacticSupported: true,
}
}
};
const textureSize = [5, 5];
assert.equal(GLKernel.prototype.getVariablePrecisionString.call(mockInstance, textureSize, null, true), 'highp');
});
test('getVariablePrecisionString() when tactic is not set and texSize is outside highIntPrecision', () => {
const mockInstance = {
tactic: null,
constructor: {
features: {
lowIntPrecision: { rangeMax: Math.log2(3 * 3) },
mediumIntPrecision: { rangeMax: Math.log2(4 * 4) },
highIntPrecision: { rangeMax: Math.log2(5 * 5) },
isSpeedTacticSupported: true,
}
}
};
const textureSize = [6, 6];
assert.throws(() => GLKernel.prototype.getVariablePrecisionString.call(mockInstance, textureSize, null, true));
});
test('getVariablePrecisionString() when features.isSpeedTacticSupported is false returns "highp"', () => {
const mockInstance = {
tactic: null,
constructor: {
features: {
isSpeedTacticSupported: false,
}
}
};
const textureSize = [1, 1];
assert.equal(GLKernel.prototype.getVariablePrecisionString.call(mockInstance, textureSize, null, true), 'highp');
});
function testGetFeatures(canvas, context) {
const gpu = new GPU({ canvas, context });
const { Kernel } = gpu;
Kernel.setupFeatureChecks();
const features = Kernel.getFeatures();
assert.ok(typeof features.isFloatRead === 'boolean');
assert.ok(typeof features.isIntegerDivisionAccurate === 'boolean');
assert.ok(typeof features.isSpeedTacticSupported === 'boolean');
assert.ok(typeof features.isTextureFloat === 'boolean');
assert.ok(typeof features.isDrawBuffers === 'boolean');
assert.ok(typeof features.kernelMap === 'boolean');
assert.ok(typeof features.channelCount === 'number');
assert.ok(typeof features.maxTextureSize === 'number');
assert.ok(typeof features.lowIntPrecision.rangeMax === 'number');
assert.ok(typeof features.mediumIntPrecision.rangeMax === 'number');
assert.ok(typeof features.highIntPrecision.rangeMax === 'number');
assert.ok(typeof features.lowFloatPrecision.rangeMax === 'number');
assert.ok(typeof features.mediumFloatPrecision.rangeMax === 'number');
assert.ok(typeof features.highFloatPrecision.rangeMax === 'number');
gpu.destroy();
}
(GPU.isWebGLSupported ? test : skip)('getFeatures() webgl', () => {
const canvas = document.createElement('canvas');
const context = canvas.getContext('webgl');
testGetFeatures(canvas, context);
});
(GPU.isWebGL2Supported ? test : skip)('getFeatures() webgl2', () => {
const canvas = document.createElement('canvas');
const context = canvas.getContext('webgl2');
testGetFeatures(canvas, context);
});
(GPU.isHeadlessGLSupported ? test : skip)('getFeatures() headlessgl', () => {
const canvas = null;
const context = require('gl')(1, 1);
testGetFeatures(canvas, context);
});
test('setOutput() throws when not dynamicOutput and already compiled', () => {
assert.throws(() => {
GLKernel.prototype.setOutput.call({
program: {},
toKernelOutput: () => {},
dynamicOutput: false
});
}, new Error('Resizing a kernel with dynamicOutput: false is not possible'));
});
test('setOutput() when not dynamicOutput and not already compiled', () => {
const mockInstance = {
toKernelOutput: () => [100, 100],
dynamicOutput: false,
output: null,
};
GLKernel.prototype.setOutput.call(mockInstance, [100, 100]);
assert.deepEqual(mockInstance.output, [100, 100]);
});
test('setOutput() when does not need to trigger recompile', () => {
const mockContext = {
bindFramebuffer: sinon.spy(),
FRAMEBUFFER: 'FRAMEBUFFER',
viewport: sinon.spy()
};
const mockTexture = {
delete: sinon.spy(),
};
const mockMappedTexture = {
delete: sinon.spy()
};
const mock_setupOutputTexture = sinon.spy();
const mock_setupSubOutputTextures = sinon.spy();
const mockInstance = {
context: mockContext,
program: {},
texSize: [1, 1],
framebuffer: {
width: 0,
height: 0,
},
toKernelOutput: GLKernel.prototype.toKernelOutput,
dynamicOutput: true,
getVariablePrecisionString: () => {
return 'lowp';
},
switchKernels: sinon.spy(),
updateMaxTexSize: sinon.spy(),
maxTexSize: [123, 321],
canvas: {
width: 0,
height: 0,
},
texture: mockTexture,
mappedTextures: [
mockMappedTexture
],
_setupOutputTexture: mock_setupOutputTexture,
_setupSubOutputTextures: mock_setupSubOutputTextures,
};
GLKernel.prototype.setOutput.call(mockInstance, [100, 100]);
assert.equal(mockContext.bindFramebuffer.callCount, 1);
assert.equal(mockContext.bindFramebuffer.args[0][0], 'FRAMEBUFFER');
assert.equal(mockContext.bindFramebuffer.args[0][1], mockInstance.framebuffer);
assert.equal(mockInstance.updateMaxTexSize.callCount, 1);
assert.equal(mockInstance.framebuffer.width, 100);
assert.equal(mockInstance.framebuffer.height, 100);
assert.equal(mockContext.viewport.callCount, 1);
assert.equal(mockContext.viewport.args[0][0], 0);
assert.equal(mockContext.viewport.args[0][1], 0);
assert.equal(mockContext.viewport.args[0][2], 123);
assert.equal(mockContext.viewport.args[0][3], 321);
assert.equal(mockInstance.canvas.width, 123);
assert.equal(mockInstance.canvas.height, 321);
assert.equal(mockInstance.texture, null);
assert.equal(mockInstance.mappedTextures, null);
assert.ok(mockTexture.delete.called);
assert.ok(mockMappedTexture.delete.called);
assert.ok(mock_setupOutputTexture.called);
assert.ok(mock_setupSubOutputTextures.called);
});
test('setOutput() when needs to trigger recompile', () => {
const mockInstance = {
program: {},
texSize: [1, 1],
toKernelOutput: GLKernel.prototype.toKernelOutput,
dynamicOutput: true,
getVariablePrecisionString: (textureSize) => {
if (textureSize[0] === 1) return 'lowp';
return 'highp';
},
switchKernels: sinon.spy()
};
GLKernel.prototype.setOutput.call(mockInstance, [100, 100]);
assert.ok(mockInstance.switchKernels.callCount, 1);
});