luma.gl/modules/engine/test/lib/model.spec.ts

715 lines
18 KiB
TypeScript

/*
import test from 'tape-promise/tape';
import {webgl1Device} from '@luma.gl/test-utils';
import {GL} from '@luma.gl/constants';
import {luma} from '@luma.gl/core';
// TODO - Model test should not depend on Cube
import {Model, ProgramManager} from '@luma.gl/engine';
import {Buffer} from '@luma.gl/webgl';
import {CubeGeometry} from '@luma.gl/engine';
import {picking} from '@luma.gl/shadertools';
const stats = luma.stats.get('Resource Counts');
const DUMMY_VS = `
void main() { gl_Position = vec4(1.0); }
`;
const DUMMY_FS = `
precision highp float;
void main() { gl_FragColor = vec4(1.0); }
`;
const VS_300 = `#version 300 es
in vec4 positions;
in vec2 uvs;
out vec2 vUV;
void main() {
vUV = uvs;
gl_Position = positions;
}
`;
const FS_300 = `#version 300 es
precision highp float;
in vec2 vUV;
uniform sampler2D tex;
out vec4 fragColor;
void main() {
fragColor = texture(tex, vUV);
}
`;
test('Model#construct/destruct', (t) => {
// Avoid re-using program from ProgramManager
const vs = '/* DO_NOT_CACHE Model#construct/destruct * void main() {gl_Position = vec4(0.0);}';
const fs = '/* DO_NOT_CACHE Model#construct/destruct * void main() {gl_FragColor = vec4(0.0);}';
const model = new Model(webgl1Device, {
drawMode: GL.POINTS,
vertexCount: 0,
vs,
fs
});
t.ok(model, 'Model constructor does not throw errors');
t.ok(model.id, 'Model has an id');
t.ok(model.getProgram().handle, 'Created new program');
model.destroy();
t.notOk(model.vertexArray.handle, 'Deleted vertexArray');
t.notOk(model.program.handle, 'Deleted program');
t.end();
});
test('Model#multiple delete', (t) => {
// Avoid re-using program from ProgramManager
const vs = '/* DO_NOT_CACHE Model#construct/destruct * void main() {gl_Position = vec4(0.0);}';
const fs = '/* DO_NOT_CACHE Model#construct/destruct * void main() {gl_FragColor = vec4(0.0);}';
const model1 = new Model(webgl1Device, {
drawMode: GL.POINTS,
vertexCount: 0,
vs,
fs
});
const model2 = new Model(webgl1Device, {
drawMode: GL.POINTS,
vertexCount: 0,
vs,
fs
});
model1.destroy();
t.ok(model2.program.handle, 'program still in use');
model1.destroy();
t.ok(model2.program.handle, 'program still in use');
model2.destroy();
t.notOk(model2.program.handle, 'program is released');
t.end();
});
test('Model#setAttribute', (t) => {
const buffer1 = webgl1Device.createBuffer({
accessor: {size: 2},
data: new Float32Array(4).fill(1)
});
const buffer2 = webgl1Device.createBuffer({data: new Float32Array(8)});
const initialActiveBuffers = stats.get('Buffers Active').count;
const model = new Model(webgl1Device, {
vs: DUMMY_VS,
fs: DUMMY_FS,
geometry: new CubeGeometry()
});
t.is(
stats.get('Buffers Active').count - initialActiveBuffers,
4,
'Created new buffers for attributes'
);
model.setAttributes({
instanceSizes: [buffer1, {size: 1}],
instancePositions: buffer2,
instanceWeight: new Float32Array([10]),
instanceColors: {getValue: () => new Float32Array([0, 0, 0, 1])}
});
t.deepEqual(model.getAttributes(), {}, 'no longer stores local attributes');
t.is(stats.get('Buffers Active').count - initialActiveBuffers, 4, 'Did not create new buffers');
model.destroy();
buffer1.destroy();
buffer2.destroy();
t.end();
});
test('Model#setters, getters', (t) => {
const model = new Model(webgl1Device, {vs: DUMMY_VS, fs: DUMMY_FS});
model.setUniforms({
isPickingActive: 1
});
t.deepEqual(model.getUniforms(), {isPickingActive: 1}, 'uniforms are set');
model.setInstanceCount(4);
t.is(model.getInstanceCount(), 4, 'instance count is set');
model.setDrawMode(1);
t.is(model.getDrawMode(), 1, 'draw mode is set');
model.destroy();
t.end();
});
test('Model#draw', (t) => {
const model = new Model(webgl1Device, {
vs: DUMMY_VS,
fs: DUMMY_FS,
geometry: new CubeGeometry(),
timerQueryEnabled: true
});
model.draw();
model.render({
isPickingActive: 1
});
t.deepEqual(model.getUniforms(), {isPickingActive: 1}, 'uniforms are set');
t.end();
});
test('Model#program management', (t) => {
const pm = new ProgramManager(webgl1Device);
const vs = `
uniform float x;
void main() {
gl_Position = vec4(x);
}
`;
const fs = `
void main() {
gl_FragColor = vec4(1.0);
}
`;
const model1 = new Model(webgl1Device, {
programManager: pm,
vs,
fs,
uniforms: {
x: 0.5
}
});
const model2 = new Model(webgl1Device, {
programManager: pm,
vs,
fs,
uniforms: {
x: -0.5
}
});
t.ok(model1.program === model2.program, 'Programs are shared.');
model1.draw();
t.deepEqual(model1.getUniforms(), model1.program.uniforms, 'Program uniforms set');
model2.draw();
t.deepEqual(model2.getUniforms(), model2.program.uniforms, 'Program uniforms set');
model2.setProgram({
vs,
fs,
defines: {
MY_DEFINE: true
}
});
t.ok(model1.program === model2.program, 'Program not updated before draw.');
model2.draw();
t.ok(model1.program !== model2.program, 'Program updated after draw.');
t.deepEqual(model2.getUniforms(), model2.program.uniforms, 'Program uniforms set');
// This part is checking that the use counts
// don't get bloated by multiple checks.
const model3 = new Model(webgl1Device, {
programManager: pm,
vs,
fs,
defines: {
MODEL3_DEFINE1: true
}
});
const oldProgram = model3.program;
// Check for program updates a few times
model3.draw();
model3.draw();
model3.setProgram({
vs,
fs,
defines: {
MODEL3_DEFINE2: true
}
});
model3.draw();
t.ok(model3.program !== oldProgram, 'Program updated after draw.');
t.ok(oldProgram.handle === null, 'Old program released after update');
t.end();
});
test('Model#program management - getModuleUniforms', (t) => {
const pm = new ProgramManager(webgl1Device);
const vs = 'void main() {}';
const fs = 'void main() {}';
const model = new Model(webgl1Device, {
programManager: pm,
vs,
fs
});
t.deepEqual(model.getModuleUniforms(), {}, 'Module uniforms empty with no modules');
model.setProgram({
vs,
fs,
modules: [picking]
});
t.deepEqual(
model.getModuleUniforms(),
picking.getUniforms(),
'Module uniforms correct after update'
);
t.end();
});
test('Model#getBuffersFromGeometry', (t) => {
let buffers = getBuffersFromGeometry(webgl1Device.gl, {
indices: new Uint16Array([0, 1, 2, 3]),
attributes: {
positions: {size: 3, value: new Float32Array(12)},
colors: {constant: true, value: [255, 255, 255, 255]},
uvs: {size: 2, value: new Float32Array(8)}
}
});
t.deepEqual(buffers.colors, [255, 255, 255, 255], 'colors mapped');
t.ok(buffers.positions[0] instanceof Buffer, 'positions mapped');
t.is(buffers.positions[1].size, 3, 'positions mapped');
t.ok(buffers.uvs[0] instanceof Buffer, 'uvs mapped');
t.is(buffers.uvs[1].size, 2, 'uvs mapped');
t.ok(buffers.indices[0] instanceof Buffer, 'indices mapped');
buffers.positions[0].destroy();
buffers.uvs[0].destroy();
buffers.indices[0].destroy();
// Inferring attribute size
buffers = getBuffersFromGeometry(webgl1Device.gl, {
attributes: {
indices: {value: new Uint16Array([0, 1, 2, 3])},
normals: {value: new Float32Array(12)},
texCoords: {value: new Float32Array(8)}
}
});
t.is(buffers.indices[1].size, 1, 'texCoords size');
t.is(buffers.normals[1].size, 3, 'normals size');
t.is(buffers.texCoords[1].size, 2, 'texCoords size');
buffers.indices[0].destroy();
buffers.normals[0].destroy();
buffers.texCoords[0].destroy();
t.throws(
() =>
getBuffersFromGeometry(webgl1Device.gl, {
indices: [0, 1, 2, 3]
}),
'invalid indices'
);
t.throws(
() =>
getBuffersFromGeometry(webgl1Device.gl, {
attributes: {
heights: {value: new Float32Array([0, 1, 2, 3])}
}
}),
'invalid size'
);
t.end();
});
test('Model#transpileToGLSL100', (t) => {
let model;
t.throws(() => {
model = new Model(webgl1Device, {
vs: VS_300,
fs: FS_300
});
}, "Can't compile 300 shader with WebGL 1");
t.doesNotThrow(() => {
model = new Model(webgl1Device, {
vs: VS_300,
fs: FS_300,
transpileToGLSL100: true
});
}, 'Can compile transpiled 300 shader with WebGL 1');
@ts-expect-error object possibly undefined
t.ok(model.program, 'Created a program');
t.end();
});
*/
/*
test.skip('PipelineFactory#basic', (t) => {
const pipelineFactory = new PipelineFactory(webgl1Device);
const program1 = pipelineFactory.createRenderPipeline({vs, fs, topology: 'triangle-list'});
t.ok(program1, 'Got a pipeline');
const pipeline2 = pipelineFactory.createRenderPipeline({vs, fs, topology: 'triangle-list'});
t.ok(program1 === pipeline2, 'Got cached pipeline');
const definePipeline1 = pipelineFactory.createRenderPipeline({
vs,
fs,
topology: 'triangle-list',
defines: {
MY_DEFINE: true
}
});
t.ok(program1 !== definePipeline1, 'Define triggers new pipeline');
const definePipeline2 = pipelineFactory.createRenderPipeline({
vs,
fs,
topology: 'triangle-list',
defines: {
MY_DEFINE: true
}
});
t.ok(definePipeline1 === definePipeline2, 'Got cached pipeline with defines');
const modulePipeline1 = pipelineFactory.createRenderPipeline({
vs,
fs,
topology: 'triangle-list',
modules: [picking]
});
t.ok(program1 !== modulePipeline1, 'Module triggers new pipeline');
t.ok(definePipeline1 !== modulePipeline1, 'Module triggers new pipeline');
const modulePipeline2 = pipelineFactory.createRenderPipeline({
vs,
fs,
topology: 'triangle-list',
modules: [picking]
});
t.ok(modulePipeline1 === modulePipeline2, 'Got cached pipeline with modules');
const defineModulePipeline1 = pipelineFactory.createRenderPipeline({
vs,
fs,
topology: 'triangle-list',
modules: [picking],
defines: {
MY_DEFINE: true
}
});
t.ok(program1 !== defineModulePipeline1, 'Module and define triggers new pipeline');
t.ok(definePipeline1 !== defineModulePipeline1, 'Module and define triggers new pipeline');
t.ok(modulePipeline1 !== defineModulePipeline1, 'Module and define triggers new pipeline');
const defineModulePipeline2 = pipelineFactory.createRenderPipeline({
vs,
fs,
topology: 'triangle-list',
modules: [picking],
defines: {
MY_DEFINE: true
}
});
t.ok(
defineModulePipeline1 === defineModulePipeline2,
'Got cached pipeline with modules and defines'
);
t.end();
});
*/
/*
import {dirlight, picking} from '@luma.gl/shadertools';
const VS_300 = glsl`#version 300 es
in vec4 positions;
in vec2 uvs;
out vec2 vUV;
void main() {
vUV = uvs;
gl_Position = positions;
}
`;
const FS_300 = glsl`#version 300 es
precision highp float;
in vec2 vUV;
uniform sampler2D tex;
out vec4 fragColor;
void main() {
fragColor = texture(tex, vUV);
}
`;
// TODO - Move to model: transpilation functionality was moved to model
test('PipelineFactory#hooks', (t) => {
const pipelineFactory = new PipelineFactory(webgl1Device);
const preHookPipeline = pipelineFactory.createRenderPipeline({vs, fs, topology: 'triangle-list'});
pipelineFactory.addShaderHook('vs:LUMAGL_pickColor(inout vec4 color)');
pipelineFactory.addShaderHook('fs:LUMAGL_fragmentColor(inout vec4 color)', {
header: 'if (color.a == 0.0) discard;\n',
footer: 'color.a *= 1.2;\n'
});
const postHookPipeline = pipelineFactory.createRenderPipeline({vs, fs, topology: 'triangle-list'});
t.ok(preHookPipeline !== postHookPipeline, 'Adding hooks changes hash');
const pickingInjection = Object.assign(
{
inject: {
'vs:LUMAGL_pickColor': 'picking_setPickingColor(color.rgb);',
'fs:LUMAGL_fragmentColor': {
injection: 'color = picking_filterColor(color);',
order: Number.POSITIVE_INFINITY
}
}
},
picking
);
const noModulePipeline = pipelineFactory.createRenderPipeline({vs, fs, topology: 'triangle-list'});
t.ok(preHookPipeline !== noModulePipeline, 'Adding hooks changes hash');
const noModuleVs = noModulePipeline.vs.source;
const noModuleFs = noModulePipeline.fs.source;
t.ok(noModuleVs.indexOf('LUMAGL_pickColor') > -1, 'hook function injected into vertex shader');
t.ok(
noModuleFs.indexOf('LUMAGL_fragmentColor') > -1,
'hook function injected into fragment shader'
);
t.ok(
noModuleVs.indexOf('picking_setPickingColor(color.rgb)') === -1,
'injection code not included in vertex shader without module'
);
t.ok(
noModuleFs.indexOf('color = picking_filterColor(color)') === -1,
'injection code not included in fragment shader without module'
);
const modulesPipeline = pipelineFactory.createRenderPipeline({
vs,
fs,
topology: 'triangle-list',
modules: [pickingInjection]
});
const modulesVs = modulesPipeline.vs.source;
const modulesFs = modulesPipeline.fs.source;
t.ok(modulesVs.indexOf('LUMAGL_pickColor') > -1, 'hook function injected into vertex shader');
t.ok(
modulesFs.indexOf('LUMAGL_fragmentColor') > -1,
'hook function injected into fragment shader'
);
t.ok(
modulesVs.indexOf('picking_setPickingColor(color.rgb)') > -1,
'injection code included in vertex shader with module'
);
t.ok(
modulesFs.indexOf('color = picking_filterColor(color)') > -1,
'injection code included in fragment shader with module'
);
t.ok(
modulesFs.indexOf('if (color.a == 0.0) discard;') > -1,
'hook header injected into fragment shader'
);
t.ok(
modulesFs.indexOf('color.a *= 1.2;') > modulesFs.indexOf('color = picking_filterColor(color)'),
'hook footer injected after injection code'
);
const injectPipeline = pipelineFactory.createRenderPipeline({
vs,
fs,
topology: 'triangle-list',
inject: {
'vs:LUMAGL_pickColor': 'color *= 0.1;',
'fs:LUMAGL_fragmentColor': 'color += 0.1;'
}
});
const injectVs = injectPipeline.vs.source;
const injectFs = injectPipeline.fs.source;
t.ok(injectVs.indexOf('color *= 0.1') > -1, 'argument injection code included in shader hook');
t.ok(injectFs.indexOf('color += 0.1') > -1, 'argument injection code included in shader hook');
const injectDefinePipeline1 = pipelineFactory.createRenderPipeline({
vs,
fs,
topology: 'triangle-list',
inject: {
'vs:LUMAGL_pickColor': 'color *= 0.1;'
}
});
const injectDefinePipeline2 = pipelineFactory.createRenderPipeline({
vs,
fs,
topology: 'triangle-list',
defines: {
'vs:LUMAGL_pickColor': 'color *= 0.1;'
}
});
t.ok(injectDefinePipeline1 !== injectDefinePipeline2, 'Injects and defines hashed separately.');
t.end();
});
// TODO - Move to model: transpilation functionality was moved to model
test.skip('PipelineFactory#defaultModules', (t) => {
const pipelineFactory = new PipelineFactory(webgl1Device);
const {pipeline} = pipelineFactory.createRenderPipeline({vs, fs, topology: 'triangle-list'});
const preDefaultModulePipeline = pipelineFactory.createRenderPipeline({
vs,
fs,
topology: 'triangle-list',
modules: [dirlight]
});
const preDefaultModuleSource = preDefaultModulePipeline.fs.source;
pipelineFactory.addDefaultModule(dirlight);
const defaultModulePipeline = pipelineFactory.createRenderPipeline({vs, fs, topology: 'triangle-list'});
const modulePipeline = pipelineFactory.createRenderPipeline({
vs,
fs,
topology: 'triangle-list',
modules: [dirlight]
});
t.ok(pipeline !== defaultModulePipeline, 'Pipeline with new default module properly cached');
t.ok(
preDefaultModulePipeline !== defaultModulePipeline,
'Adding a default module changes the pipeline hash'
);
t.ok(
preDefaultModulePipeline.fs.source === defaultModulePipeline.fs.source,
'Default module injected correctly'
);
t.ok(
modulePipeline === defaultModulePipeline,
'Pipeline with new default module matches regular module'
);
pipelineFactory.removeDefaultModule(dirlight);
const noDefaultModulePipeline = pipelineFactory.createRenderPipeline({vs, fs, topology: 'triangle-list'});
t.ok(pipeline.fs.source === noDefaultModulePipeline.fs.source, 'Default module was removed');
t.ok(modulePipeline.fs.source !== noDefaultModulePipeline.fs.source, 'Default module was removed');
// Reset pipeline manager
pipelineFactory.release(pipeline);
pipelineFactory.release(modulePipeline);
pipelineFactory.release(defaultModulePipeline);
pipelineFactory.release(noDefaultModulePipeline);
pipelineFactory.addDefaultModule(dirlight);
const uncachedPipeline = pipelineFactory.createRenderPipeline({vs, fs, topology: 'triangle-list'});
const defaultModuleSource = uncachedPipeline.fs.source;
t.ok(defaultModulePipeline !== uncachedPipeline, 'Pipeline is not cached');
t.ok(preDefaultModuleSource === defaultModuleSource, 'Default modules create correct source');
t.end();
});
// TODO - Move to model: transpilation functionality was moved to model
test.skip('PipelineFactory#transpileToGLSL100', (t) => {
const pipelineFactory = new PipelineFactory(webgl1Device);
t.throws(() => {
pipelineFactory.createRenderPipeline({
vs: VS_300,
fs: FS_300,
topology: 'triangle-list'
});
}, 'Can\'t compile 300 shader with WebGL 1');
t.doesNotThrow(() => {
pipelineFactory.createRenderPipeline({
vs: VS_300,
fs: FS_300,
topology: 'triangle-list',
transpileToGLSL100: true
});
}, 'Can compile transpiled 300 shader with WebGL 1');
const programTranspiled = pipelineFactory.createRenderPipeline({vs, fs, topology: 'triangle-list', transpileToGLSL100: true});
const programUntranspiled = pipelineFactory.createRenderPipeline({vs, fs, topology: 'triangle-list'});
const programTranspiled2 = pipelineFactory.createRenderPipeline({vs, fs, topology: 'triangle-list', transpileToGLSL100: true});
t.equals(programTranspiled, programTranspiled2, 'Transpiled programs match');
t.notEquals(
programTranspiled,
programUntranspiled,
'Transpiled pipeline does not match untranspiled pipeline'
);
t.end();
});
*/