338 lines
11 KiB
JavaScript
338 lines
11 KiB
JavaScript
import vendor from '../core/vendor';
|
|
import Base from '../core/Base';
|
|
|
|
var SHADER_STATE_TO_ENABLE = 1;
|
|
var SHADER_STATE_KEEP_ENABLE = 2;
|
|
var SHADER_STATE_PENDING = 3;
|
|
|
|
// Enable attribute operation is global to all programs
|
|
// Here saved the list of all enabled attribute index
|
|
// http://www.mjbshaw.com/2013/03/webgl-fixing-invalidoperation.html
|
|
var enabledAttributeList = {};
|
|
|
|
// some util functions
|
|
function addLineNumbers(string) {
|
|
var chunks = string.split('\n');
|
|
for (var i = 0, il = chunks.length; i < il; i ++) {
|
|
// Chrome reports shader errors on lines
|
|
// starting counting from 1
|
|
chunks[i] = (i + 1) + ': ' + chunks[i];
|
|
}
|
|
return chunks.join('\n');
|
|
}
|
|
|
|
// Return true or error msg if error happened
|
|
function checkShaderErrorMsg(_gl, shader, shaderString) {
|
|
if (!_gl.getShaderParameter(shader, _gl.COMPILE_STATUS)) {
|
|
return [_gl.getShaderInfoLog(shader), addLineNumbers(shaderString)].join('\n');
|
|
}
|
|
}
|
|
|
|
var tmpFloat32Array16 = new vendor.Float32Array(16);
|
|
|
|
var GLProgram = Base.extend({
|
|
|
|
uniformSemantics: {},
|
|
attributes: {}
|
|
|
|
}, function () {
|
|
this._locations = {};
|
|
|
|
this._textureSlot = 0;
|
|
|
|
this._program = null;
|
|
}, {
|
|
|
|
bind: function (renderer) {
|
|
this._textureSlot = 0;
|
|
renderer.gl.useProgram(this._program);
|
|
},
|
|
|
|
hasUniform: function (symbol) {
|
|
var location = this._locations[symbol];
|
|
return location !== null && location !== undefined;
|
|
},
|
|
|
|
useTextureSlot: function (renderer, texture, slot) {
|
|
if (texture) {
|
|
renderer.gl.activeTexture(renderer.gl.TEXTURE0 + slot);
|
|
// Maybe texture is not loaded yet;
|
|
if (texture.isRenderable()) {
|
|
texture.bind(renderer);
|
|
}
|
|
else {
|
|
// Bind texture to null
|
|
texture.unbind(renderer);
|
|
}
|
|
}
|
|
},
|
|
|
|
currentTextureSlot: function () {
|
|
return this._textureSlot;
|
|
},
|
|
|
|
resetTextureSlot: function (slot) {
|
|
this._textureSlot = slot || 0;
|
|
},
|
|
|
|
takeCurrentTextureSlot: function (renderer, texture) {
|
|
var textureSlot = this._textureSlot;
|
|
|
|
this.useTextureSlot(renderer, texture, textureSlot);
|
|
|
|
this._textureSlot++;
|
|
|
|
return textureSlot;
|
|
},
|
|
|
|
setUniform: function (_gl, type, symbol, value) {
|
|
var locationMap = this._locations;
|
|
var location = locationMap[symbol];
|
|
// Uniform is not existed in the shader
|
|
if (location === null || location === undefined) {
|
|
return false;
|
|
}
|
|
|
|
switch (type) {
|
|
case 'm4':
|
|
if (!(value instanceof Float32Array)) {
|
|
// Use Float32Array is much faster than array when uniformMatrix4fv.
|
|
for (var i = 0; i < value.length; i++) {
|
|
tmpFloat32Array16[i] = value[i];
|
|
}
|
|
value = tmpFloat32Array16;
|
|
}
|
|
_gl.uniformMatrix4fv(location, false, value);
|
|
break;
|
|
case '2i':
|
|
_gl.uniform2i(location, value[0], value[1]);
|
|
break;
|
|
case '2f':
|
|
_gl.uniform2f(location, value[0], value[1]);
|
|
break;
|
|
case '3i':
|
|
_gl.uniform3i(location, value[0], value[1], value[2]);
|
|
break;
|
|
case '3f':
|
|
_gl.uniform3f(location, value[0], value[1], value[2]);
|
|
break;
|
|
case '4i':
|
|
_gl.uniform4i(location, value[0], value[1], value[2], value[3]);
|
|
break;
|
|
case '4f':
|
|
_gl.uniform4f(location, value[0], value[1], value[2], value[3]);
|
|
break;
|
|
case '1i':
|
|
_gl.uniform1i(location, value);
|
|
break;
|
|
case '1f':
|
|
_gl.uniform1f(location, value);
|
|
break;
|
|
case '1fv':
|
|
_gl.uniform1fv(location, value);
|
|
break;
|
|
case '1iv':
|
|
_gl.uniform1iv(location, value);
|
|
break;
|
|
case '2iv':
|
|
_gl.uniform2iv(location, value);
|
|
break;
|
|
case '2fv':
|
|
_gl.uniform2fv(location, value);
|
|
break;
|
|
case '3iv':
|
|
_gl.uniform3iv(location, value);
|
|
break;
|
|
case '3fv':
|
|
_gl.uniform3fv(location, value);
|
|
break;
|
|
case '4iv':
|
|
_gl.uniform4iv(location, value);
|
|
break;
|
|
case '4fv':
|
|
_gl.uniform4fv(location, value);
|
|
break;
|
|
case 'm2':
|
|
case 'm2v':
|
|
_gl.uniformMatrix2fv(location, false, value);
|
|
break;
|
|
case 'm3':
|
|
case 'm3v':
|
|
_gl.uniformMatrix3fv(location, false, value);
|
|
break;
|
|
case 'm4v':
|
|
// Raw value
|
|
if (Array.isArray(value) && Array.isArray(value[0])) {
|
|
var array = new vendor.Float32Array(value.length * 16);
|
|
var cursor = 0;
|
|
for (var i = 0; i < value.length; i++) {
|
|
var item = value[i];
|
|
for (var j = 0; j < 16; j++) {
|
|
array[cursor++] = item[j];
|
|
}
|
|
}
|
|
_gl.uniformMatrix4fv(location, false, array);
|
|
}
|
|
else { // ArrayBufferView
|
|
_gl.uniformMatrix4fv(location, false, value);
|
|
}
|
|
break;
|
|
}
|
|
return true;
|
|
},
|
|
|
|
setUniformOfSemantic: function (_gl, semantic, val) {
|
|
var semanticInfo = this.uniformSemantics[semantic];
|
|
if (semanticInfo) {
|
|
return this.setUniform(_gl, semanticInfo.type, semanticInfo.symbol, val);
|
|
}
|
|
return false;
|
|
},
|
|
|
|
// Used for creating VAO
|
|
// Enable the attributes passed in and disable the rest
|
|
// Example Usage:
|
|
// enableAttributes(renderer, ["position", "texcoords"])
|
|
enableAttributes: function (renderer, attribList, vao) {
|
|
var _gl = renderer.gl;
|
|
var program = this._program;
|
|
|
|
var locationMap = this._locations;
|
|
|
|
var enabledAttributeListInContext;
|
|
if (vao) {
|
|
enabledAttributeListInContext = vao.__enabledAttributeList;
|
|
}
|
|
else {
|
|
enabledAttributeListInContext = enabledAttributeList[renderer.__uid__];
|
|
}
|
|
if (!enabledAttributeListInContext) {
|
|
// In vertex array object context
|
|
// PENDING Each vao object needs to enable attributes again?
|
|
if (vao) {
|
|
enabledAttributeListInContext
|
|
= vao.__enabledAttributeList
|
|
= [];
|
|
}
|
|
else {
|
|
enabledAttributeListInContext
|
|
= enabledAttributeList[renderer.__uid__]
|
|
= [];
|
|
}
|
|
}
|
|
var locationList = [];
|
|
for (var i = 0; i < attribList.length; i++) {
|
|
var symbol = attribList[i];
|
|
if (!this.attributes[symbol]) {
|
|
locationList[i] = -1;
|
|
continue;
|
|
}
|
|
var location = locationMap[symbol];
|
|
if (location == null) {
|
|
location = _gl.getAttribLocation(program, symbol);
|
|
// Attrib location is a number from 0 to ...
|
|
if (location === -1) {
|
|
locationList[i] = -1;
|
|
continue;
|
|
}
|
|
locationMap[symbol] = location;
|
|
}
|
|
locationList[i] = location;
|
|
|
|
if (!enabledAttributeListInContext[location]) {
|
|
enabledAttributeListInContext[location] = SHADER_STATE_TO_ENABLE;
|
|
}
|
|
else {
|
|
enabledAttributeListInContext[location] = SHADER_STATE_KEEP_ENABLE;
|
|
}
|
|
}
|
|
|
|
for (var i = 0; i < enabledAttributeListInContext.length; i++) {
|
|
switch(enabledAttributeListInContext[i]){
|
|
case SHADER_STATE_TO_ENABLE:
|
|
_gl.enableVertexAttribArray(i);
|
|
enabledAttributeListInContext[i] = SHADER_STATE_PENDING;
|
|
break;
|
|
case SHADER_STATE_KEEP_ENABLE:
|
|
enabledAttributeListInContext[i] = SHADER_STATE_PENDING;
|
|
break;
|
|
// Expired
|
|
case SHADER_STATE_PENDING:
|
|
_gl.disableVertexAttribArray(i);
|
|
enabledAttributeListInContext[i] = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return locationList;
|
|
},
|
|
|
|
getAttribLocation: function (_gl, symbol) {
|
|
var locationMap = this._locations;
|
|
|
|
var location = locationMap[symbol];
|
|
if (location == null) {
|
|
location = _gl.getAttribLocation(this._program, symbol);
|
|
locationMap[symbol] = location;
|
|
}
|
|
|
|
return location;
|
|
},
|
|
|
|
buildProgram: function (_gl, shader, vertexShaderCode, fragmentShaderCode) {
|
|
var vertexShader = _gl.createShader(_gl.VERTEX_SHADER);
|
|
var program = _gl.createProgram();
|
|
|
|
_gl.shaderSource(vertexShader, vertexShaderCode);
|
|
_gl.compileShader(vertexShader);
|
|
|
|
var fragmentShader = _gl.createShader(_gl.FRAGMENT_SHADER);
|
|
_gl.shaderSource(fragmentShader, fragmentShaderCode);
|
|
_gl.compileShader(fragmentShader);
|
|
|
|
var msg = checkShaderErrorMsg(_gl, vertexShader, vertexShaderCode);
|
|
if (msg) {
|
|
return msg;
|
|
}
|
|
msg = checkShaderErrorMsg(_gl, fragmentShader, fragmentShaderCode);
|
|
if (msg) {
|
|
return msg;
|
|
}
|
|
|
|
_gl.attachShader(program, vertexShader);
|
|
_gl.attachShader(program, fragmentShader);
|
|
// Force the position bind to location 0;
|
|
if (shader.attributeSemantics['POSITION']) {
|
|
_gl.bindAttribLocation(program, 0, shader.attributeSemantics['POSITION'].symbol);
|
|
}
|
|
else {
|
|
// Else choose an attribute and bind to location 0;
|
|
var keys = Object.keys(this.attributes);
|
|
_gl.bindAttribLocation(program, 0, keys[0]);
|
|
}
|
|
|
|
_gl.linkProgram(program);
|
|
|
|
_gl.deleteShader(vertexShader);
|
|
_gl.deleteShader(fragmentShader);
|
|
|
|
this._program = program;
|
|
|
|
// Save code.
|
|
this.vertexCode = vertexShaderCode;
|
|
this.fragmentCode = fragmentShaderCode;
|
|
|
|
if (!_gl.getProgramParameter(program, _gl.LINK_STATUS)) {
|
|
return 'Could not link program\n' + _gl.getProgramInfoLog(program);
|
|
}
|
|
|
|
// Cache uniform locations
|
|
for (var i = 0; i < shader.uniforms.length; i++) {
|
|
var uniformSymbol = shader.uniforms[i];
|
|
this._locations[uniformSymbol] = _gl.getUniformLocation(program, uniformSymbol);
|
|
}
|
|
|
|
}
|
|
});
|
|
|
|
export default GLProgram; |