diff --git a/docs/api-reference/core/resource-management/program-manager.md b/docs/api-reference/core/resource-management/program-manager.md index 8c5737c3f9..ca4b3dd5f8 100644 --- a/docs/api-reference/core/resource-management/program-manager.md +++ b/docs/api-reference/core/resource-management/program-manager.md @@ -84,6 +84,7 @@ Get a program that fits the parameters provided. If one is already cached, retur * `defines`: Object indicating `#define` constants to include in the shaders. * `modules`: Array of module objects to include in the shaders. * `inject`: Object of hook injections to include in the shaders. +* `program`: A `Program` object to compare the new one two. If they're the same, use count won't be modified. If not, `program` will be released. ### `addDefaultModule(module: Object)` diff --git a/modules/core/src/lib/base-model.js b/modules/core/src/lib/base-model.js index ac515950b3..15594e40a7 100644 --- a/modules/core/src/lib/base-model.js +++ b/modules/core/src/lib/base-model.js @@ -121,7 +121,7 @@ export default class BaseModel { } setProgram(props) { - this.programProps = Object.assign({}, props, {}); + this.programProps = Object.assign({}, props); this._programDirty = true; } @@ -276,7 +276,7 @@ export default class BaseModel { this.getModuleUniforms = () => {}; this._programDirty = false; } else if (this.programManager) { - program = this.programManager.get({vs, fs, modules, inject, defines}); + program = this.programManager.get({vs, fs, modules, inject, defines, program: this.program}); this.getModuleUniforms = this.programManager.getUniforms(program); // Program always dirty if there's a program manager } else { diff --git a/modules/core/src/resource-management/program-manager.js b/modules/core/src/resource-management/program-manager.js index 9c25c755f0..1ebc11ba37 100644 --- a/modules/core/src/resource-management/program-manager.js +++ b/modules/core/src/resource-management/program-manager.js @@ -7,6 +7,7 @@ export default class ProgramManager { this._programCache = {}; this._getUniforms = {}; + this._registeredModules = {}; this._moduleInjections = { vs: {}, fs: {} @@ -35,7 +36,7 @@ export default class ProgramManager { } addModuleInjection(module, opts) { - const moduleName = module.name; + const moduleName = typeof module === 'string' ? module : module.name; const {hook, injection, order = 0} = opts; const shaderStage = hook.slice(0, 2); @@ -60,13 +61,22 @@ export default class ProgramManager { } get(props = {}) { - const {vs = '', fs = '', defines = {}, inject = {}, varyings = [], bufferMode = 0x8c8d} = props; // varyings/bufferMode for xform feedback, 0x8c8d = SEPARATE_ATTRIBS - + const { + vs = '', + fs = '', + defines = {}, + inject = {}, + varyings = [], + bufferMode = 0x8c8d, + program = null + } = props; // varyings/bufferMode for xform feedback, 0x8c8d = SEPARATE_ATTRIBS + + const oldProgramHash = program ? program.hash : ''; const modules = this._getModuleList(props.modules); // Combine with default modules const vsHash = this._getHash(vs); const fsHash = this._getHash(fs); - const moduleHashes = modules.map(m => this._getHash(m)).sort(); + const moduleHashes = modules.map(m => this._getHash(typeof m === 'string' ? m : m.name)).sort(); const varyingHashes = varyings.map(v => this._getHash(v)); const defineKeys = Object.keys(defines).sort(); @@ -90,6 +100,10 @@ export default class ProgramManager { this._hookStateCounter }B${bufferMode}`; + if (oldProgramHash === hash) { + return program; + } + if (!this._programCache[hash]) { const assembled = assembleShaders(this.gl, { vs, @@ -114,6 +128,11 @@ export default class ProgramManager { } this._useCounts[hash]++; + + if (program) { + this.release(program); + } + return this._programCache[hash]; } @@ -149,13 +168,14 @@ export default class ProgramManager { for (let i = 0, len = this._defaultModules.length; i < len; ++i) { const module = this._defaultModules[i]; + const name = typeof module === 'string' ? module : module.name; modules[count++] = module; - seen[module.name] = true; + seen[name] = true; } for (let i = 0, len = appModules.length; i < len; ++i) { const module = appModules[i]; - const name = module.name; + const name = typeof module === 'string' ? module : module.name; if (!seen[name]) { modules[count++] = module; seen[name] = true; diff --git a/modules/core/test/lib/model.spec.js b/modules/core/test/lib/model.spec.js index ba0ccbcc63..a723beb930 100644 --- a/modules/core/test/lib/model.spec.js +++ b/modules/core/test/lib/model.spec.js @@ -171,6 +171,35 @@ test('Model#program management', t => { 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(gl, { + 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(); }); diff --git a/modules/core/test/lib/program-manager.spec.js b/modules/core/test/lib/program-manager.spec.js index bb52d189bd..0071b903f2 100644 --- a/modules/core/test/lib/program-manager.spec.js +++ b/modules/core/test/lib/program-manager.spec.js @@ -1,6 +1,6 @@ /* eslint-disable camelcase */ import {ProgramManager} from '@luma.gl/core'; -import {dirlight, picking} from '@luma.gl/shadertools'; +import {dirlight, picking, registerShaderModules} from '@luma.gl/shadertools'; import {fixture} from 'test/setup'; import test from 'tape-catch'; @@ -203,6 +203,29 @@ test('ProgramManager#hooks', t => { t.end(); }); +test('ProgramManager#moduleRegistration', t => { + const {gl} = fixture; + const pm = new ProgramManager(gl); + + registerShaderModules([picking], {ignoreMultipleRegistrations: true}); + + const moduleProgram = pm.get({ + vs, + fs, + modules: [picking] + }); + + const moduleStringProgram = pm.get({ + vs, + fs, + modules: ['picking'] + }); + + t.ok(moduleProgram === moduleStringProgram, 'Strings accepted for registered modules'); + + t.end(); +}); + test('ProgramManager#defaultModules', t => { const {gl} = fixture; const pm = new ProgramManager(gl); diff --git a/modules/webgl/src/classes/program.js b/modules/webgl/src/classes/program.js index 74c598dbc9..0534964fa4 100644 --- a/modules/webgl/src/classes/program.js +++ b/modules/webgl/src/classes/program.js @@ -56,7 +56,7 @@ export default class Program extends Resource { initialize(props = {}) { const {hash, vs, fs, varyings, bufferMode = GL_SEPARATE_ATTRIBS} = props; - this.hash = hash || null; // Used by ProgramManager + this.hash = hash || ''; // Used by ProgramManager // Create shaders if needed this.vs =