so after some trials and tests, the lag is due to the creation of shaders only, not of materials, so the priority was to cache the shaders.
I made then a ShaderService that I use to create a new material with custom GLSL assets.
The service checks if a shader with those assets has been already created and returns it, otherwise it creates a new shader and adds it to the cache.
Here is the service:
var ShaderService = (function(){
var _shaders = {};
var _app;
/**
* In case of multiple application on the same page,
* is better to set the app as soon as possible on startup.
*/
function setApp(app) {
_app = app;
}
/**
* It creates a new material by reusing existing shader instance or by creating a new one.
*/
function createMaterial(vertexAsset, fragmentAsset, shaderAttributes) {
var shader = _getShader(vertexAsset, fragmentAsset, shaderAttributes);
var material = new pc.Material();
material.setShader(shader);
return material;
}
/**
* It uses the assets id to create a unique key and search for a ready instance of the shader.
* If it's not available, it creates a new shader and caches it.
*/
function _getShader(vertexAsset, fragmentAsset, attributes) {
attributes = attributes || {
aPosition: pc.SEMANTIC_POSITION,
aUv0: pc.SEMANTIC_TEXCOORD0
};
var key = vertexAsset.id + '_' + fragmentAsset.id;
if (!_shaders[key]) {
_app = _app || pc.Application.getApplication();
var gd = _app.graphicsDevice;
var vertexShader = vertexAsset.resource;
var fragmentShader = "precision " + gd.precision + " float;\n";
fragmentShader = fragmentShader + fragmentAsset.resource;
// A shader definition used to create a new shader.
var shaderDefinition = {
attributes: attributes,
vshader: vertexShader,
fshader: fragmentShader
};
_shaders[key] = new pc.Shader(gd, shaderDefinition);
}
return _shaders[key];
}
return {
setApp: setApp,
createMaterial: createMaterial
};
})();
Here is an example of custom shader script (applied to a 2D element) that uses the ShaderService:
var CustomShader = pc.createScript('customShader');
CustomShader.attributes.add('vs', {
type: 'asset',
assetType: 'shader',
title: 'Vertex Shader'
});
CustomShader.attributes.add('fs', {
type: 'asset',
assetType: 'shader',
title: 'Fragment Shader'
});
CustomShader.attributes.add('customSettings', {
type: 'number',
title: 'custom settings',
default: 1
});
// initialize code called once per entity
CustomShader.prototype.initialize = function() {
// it creates a new material by reusing the shader if it was already created
this.material = ShaderService.createMaterial(this.vs, this.fs);
this.material.blend = true;
this.material.blendSrc = pc.gfx.BLENDMODE_SRC_ALPHA;
this.material.blendDst = pc.gfx.BLENDMODE_ONE_MINUS_SRC_ALPHA;
// it assigns the new material to the element
this.entity.element.material = this.material;
// subscribe for attributes changes, so to update the material parameters
this.on('attr', this._updateMaterial, this);
// initializes the material's parameters
this._updateMaterial();
};
CustomShader.prototype._updateMaterial = function() {
this.material.setParameter('customSettings', this.customSettings);
};
CustomShader.prototype.swap = function(old) {
this.material = old.material;
old.off('attr');
this.on('attr', this._updateMaterial, this);
};
Waiting for a native and easier visual way offered by the editor, this seems to be a viable solution to me.