/**
* A class representing a shader.
*
* @property {String} name The name of the shader.
* @property {Object} uniforms A map mapping uniform names to Lore.Uniform instances.
*
*/
Lore.Shader = class Shader {
constructor(name, uniforms, vertexShader, fragmentShader) {
this.name = name;
this.uniforms = uniforms || {};
this.vertexShader = vertexShader || [];
this.fragmentShader = fragmentShader || [];
this.gl = null;
this.program = null;
this.initialized = false;
this.lastTime = new Date().getTime();
// Add the two default shaders (the same shaders as in getVertexShader)
this.uniforms['modelViewMatrix'] = new Lore.Uniform('modelViewMatrix',
(new Lore.Matrix4f()).entries, 'float_mat4');
this.uniforms['projectionMatrix'] = new Lore.Uniform('projectionMatrix',
(new Lore.Matrix4f()).entries, 'float_mat4');
}
clone() {
return new Lore.Shader(this.name, this.uniforms, this.vertexShader, this.fragmentShader);
}
getVertexShaderCode() {
return this.vertexShader.join('\n');
}
getFragmentShaderCode() {
return this.fragmentShader.join('\n');
}
getVertexShader(gl) {
let shader = gl.createShader(gl.VERTEX_SHADER);
let vertexShaderCode = 'uniform mat4 modelViewMatrix;\n' +
'uniform mat4 projectionMatrix;\n\n' +
this.getVertexShaderCode();
gl.shaderSource(shader, vertexShaderCode);
gl.compileShader(shader);
Lore.Shader.showCompilationInfo(gl, shader, this.name, 'Vertex Shader');
return shader;
}
getFragmentShader(gl) {
let shader = gl.createShader(gl.FRAGMENT_SHADER);
// Adding precision, see:
// http://stackoverflow.com/questions/27058064/why-do-i-need-to-define-a-precision-value-in-webgl-shaders
// and:
// http://stackoverflow.com/questions/13780609/what-does-precision-mediump-float-mean
let fragmentShaderCode = '#ifdef GL_OES_standard_derivatives\n#extension GL_OES_standard_derivatives : enable\n#endif\n\n' +
'#ifdef GL_ES\nprecision highp float;\n#endif\n\n' +
this.getFragmentShaderCode();
gl.shaderSource(shader, fragmentShaderCode);
gl.compileShader(shader);
Lore.Shader.showCompilationInfo(gl, shader, this.name, 'Fragment Shader');
return shader;
}
init(gl) {
this.gl = gl;
this.program = this.gl.createProgram();
let vertexShader = this.getVertexShader(this.gl);
let fragmentShader = this.getFragmentShader(this.gl);
if (!vertexShader || !fragmentShader) {
console.error('Failed to create the fragment or the vertex shader.');
return null;
}
this.gl.attachShader(this.program, vertexShader);
this.gl.attachShader(this.program, fragmentShader);
this.gl.linkProgram(this.program);
if (!this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS)) {
console.error('Could not link program.\n' +
'VALIDATE_STATUS: ' + this.gl.getProgramParameter(program, this.gl.VALIDATE_STATUS) + '\n' +
'ERROR: ' + this.gl.getError());
return null;
}
this.initialized = true;
}
updateUniforms(renderer) {
// Always update time uniform if it exists
if (this.uniforms['time']) {
let unif = this.uniforms['time'];
let currentTime = new Date().getTime();
unif.value += currentTime - this.lastTime;
this.lastTime = currentTime;
Lore.Uniform.Set(this.gl, this.program, unif);
unif.stale = false;
}
for (let uniform in this.uniforms) {
let unif = this.uniforms[uniform];
if (unif.stale) {
Lore.Uniform.Set(this.gl, this.program, unif);
}
}
}
use() {
this.gl.useProgram(this.program);
this.updateUniforms();
}
static showCompilationInfo(gl, shader, name, prefix) {
prefix = prefix || 'Shader';
// This was stolen from THREE.js
// https://github.com/mrdoob/three.js/blob/master/src/renderers/webgl/WebGLShader.js
if (gl.getShaderParameter(shader, gl.COMPILE_STATUS) === false) {
console.error(prefix + ' ' + name + ' did not compile.');
}
if (gl.getShaderInfoLog(shader) !== '') {
console.warn(prefix + ' ' + name + ' info log: ' + gl.getShaderInfoLog(shader));
}
}
}