var vgl = require('./vgl');
var inherit = require('../inherit');
var timestamp = require('../timestamp');
/**
* Create a new instance of class shader.
*
* @class
* @alias vgl.shader
* @extends vgl.object
* @param {number} type The GL shader type.
* @returns {vgl.shader}
*/
vgl.shader = function (type) {
'use strict';
if (!(this instanceof vgl.shader)) {
return new vgl.shader(type);
}
vgl.object.call(this);
var m_shaderContexts = [],
m_shaderType = type,
m_shaderSource = '';
/**
* A shader can be associated with multiple contexts. Each context needs to
* be compiled and attached separately. These are tracked in the
* m_shaderContexts array.
*
* @param {vgl.renderState} renderState a renderState that includes a
* m_context value.
* @returns {object} an object with context, compileTimestamp, and, if
* compiled, a shaderHandle entry.
*/
this._getContextEntry = function (renderState) {
var context = renderState.m_context, i, entry;
for (i = 0; i < m_shaderContexts.length; i += 1) {
if (m_shaderContexts[i].context === context) {
return m_shaderContexts[i];
}
}
entry = {
context: context,
compileTimestamp: timestamp()
};
m_shaderContexts.push(entry);
return entry;
};
/**
* Remove the context from the list of tracked contexts. This allows the
* associated shader handle to be GCed. Does nothing if the context is not
* in the list of tracked contexts.
*
* @param {vgl.renderState} renderState A renderState that includes a
* m_context value.
*/
this.removeContext = function (renderState) {
var context = renderState.m_context, i;
for (i = 0; i < m_shaderContexts.length; i += 1) {
if (m_shaderContexts[i].context === context) {
m_shaderContexts.splice(i, 1);
return;
}
}
};
/**
* Get shader handle.
*
* @param {vgl.renderState} renderState
* @returns {number} GL shader handle
*/
this.shaderHandle = function (renderState) {
var entry = this._getContextEntry(renderState);
return entry.shaderHandle;
};
/**
* Set shader source.
*
* @param {string} source
*/
this.setShaderSource = function (source) {
m_shaderSource = source;
this.modified();
};
/**
* Compile the shader.
*
* @param {vgl.renderState} renderState
* @returns {number} GL shader handle.
*/
this.compile = function (renderState) {
var entry = this._getContextEntry(renderState);
if (this.getMTime() < entry.compileTimestamp.getMTime()) {
return entry.shaderHandle;
}
renderState.m_context.deleteShader(entry.shaderHandle);
entry.shaderHandle = renderState.m_context.createShader(m_shaderType);
renderState.m_context.shaderSource(entry.shaderHandle, m_shaderSource);
renderState.m_context.compileShader(entry.shaderHandle);
// See if it compiled successfully
if (!renderState.m_context.getShaderParameter(entry.shaderHandle,
vgl.GL.COMPILE_STATUS)) {
console.log('[ERROR] An error occurred compiling the shaders: ' +
renderState.m_context.getShaderInfoLog(entry.shaderHandle));
console.log(m_shaderSource);
renderState.m_context.deleteShader(entry.shaderHandle);
return null;
}
entry.compileTimestamp.modified();
return entry.shaderHandle;
};
/**
* Attach shader to the program.
*
* @param {vgl.renderState} renderState
* @param {number} programHandle GL shader handler.
*/
this.attachShader = function (renderState, programHandle) {
renderState.m_context.attachShader(
programHandle, this.shaderHandle(renderState));
};
};
inherit(vgl.shader, vgl.object);
/* We can use the same shader multiple times if it is identical. This caches
* the last N shaders and will reuse them when possible. The cache keeps the
* most recently requested shader at the front. If you are doing anything more
* to a shader then creating it and setting its source once, do not use this
* cache.
*/
(function () {
'use strict';
var m_shaderCache = [],
m_shaderCacheMaxSize = 10;
/**
* Get a shader from the cache. Create a new shader if necessary using a
* specific source.
*
* @param {number} type One of vgl.GL.*_SHADER
* @param {WebGLRenderingContext} context The GL context for the shader.
* @param {string} source The source code of the shader.
* @returns {number} GL shader handle
*/
vgl.getCachedShader = function (type, context, source) {
for (var i = 0; i < m_shaderCache.length; i += 1) {
if (m_shaderCache[i].type === type &&
m_shaderCache[i].context === context &&
m_shaderCache[i].source === source) {
if (i) {
m_shaderCache.splice(0, 0, m_shaderCache.splice(i, 1)[0]);
}
return m_shaderCache[0].shader;
}
}
var shader = new vgl.shader(type);
shader.setShaderSource(source);
m_shaderCache.unshift({
type: type,
context: context,
source: source,
shader: shader
});
if (m_shaderCache.length >= m_shaderCacheMaxSize) {
m_shaderCache.splice(m_shaderCacheMaxSize,
m_shaderCache.length - m_shaderCacheMaxSize);
}
return shader;
};
/**
* Clear the shader cache.
*
* @param {WebGLRenderingContext} context The GL context to clear, or null
* for clear all.
*/
vgl.clearCachedShaders = function (context) {
for (var i = m_shaderCache.length - 1; i >= 0; i -= 1) {
if (context === null || context === undefined ||
m_shaderCache[i].context === context) {
m_shaderCache.splice(i, 1);
}
}
};
})();