let _genID = 0; function _parseError(out, type, errorLog) { if(!errorLog){ return; } errorLog.split('\n').forEach(msg => { if (msg.length < 5) { return; } let parts = /^ERROR:\s+(\d+):(\d+):\s*(.*)$/.exec(msg); if (parts) { out.push({ type: type, fileID: parts[1] | 0, line: parts[2] | 0, message: parts[3].trim() }) } else if (msg.length > 0) { out.push({ type: type, fileID: -1, line: 0, message: msg }); } }); } export default class Program { /** * @param {ef.GraphicsDevice} device - graphic device * @param {object} options - shader definition * @param {string} options.vert - vertex shader source code * @param {string} options.frag - fragment shader shader source code * @example * let prog = new Program(device, { * vert: ` * attribute vec3 a_position; * void main() { * gl_Position = vec4( a_position, 1.0 ); * } * `, * frag: ` * precision mediump float; * void main() { * gl_FragColor = vec4( 1.0, 1.0, 1.0, 1.0 ); * } * ` * }); */ constructor(device, options) { this._device = device; // stores gl information: { location, type } this._attributes = []; this._uniforms = []; this._samplers = []; this._errors = []; this._linked = false; this._vertSource = options.vert; this._fragSource = options.frag; this._glID = null; this._id = _genID++; } get id() { return this._id; } link() { if (this._linked) { return; } let gl = this._device._gl; let vertShader = _createShader(gl, gl.VERTEX_SHADER, this._vertSource); let fragShader = _createShader(gl, gl.FRAGMENT_SHADER, this._fragSource); let program = gl.createProgram(); gl.attachShader(program, vertShader); gl.attachShader(program, fragShader); gl.linkProgram(program); let failed = false; let errors = this._errors; if (!gl.getShaderParameter(vertShader, gl.COMPILE_STATUS)) { _parseError(errors, 'vs', gl.getShaderInfoLog(vertShader)); failed = true; } if (!gl.getShaderParameter(fragShader, gl.COMPILE_STATUS)) { _parseError(errors, 'fs', gl.getShaderInfoLog(fragShader)); failed = true; } gl.deleteShader(vertShader); gl.deleteShader(fragShader); if (failed) { return errors; } if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { errors.push({info: `Failed to link shader program: ${gl.getProgramInfoLog(program)}`}); return errors; } this._glID = program; // parse attribute let numAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); for (let i = 0; i < numAttributes; ++i) { let info = gl.getActiveAttrib(program, i); let location = gl.getAttribLocation(program, info.name); this._attributes.push({ name: info.name, location: location, type: info.type, }); } // parse uniform let numUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); for (let i = 0; i < numUniforms; ++i) { let info = gl.getActiveUniform(program, i); let name = info.name; let location = gl.getUniformLocation(program, name); let isArray = name.substr(name.length - 3) === '[0]'; if (isArray) { name = name.substr(0, name.length - 3); } let uniform = { name: name, location: location, type: info.type, size: isArray ? info.size : undefined, // used when uniform is an array }; this._uniforms.push(uniform); } this._linked = true; } destroy() { let gl = this._device._gl; gl.deleteProgram(this._glID); this._linked = false; this._glID = null; this._attributes = []; this._uniforms = []; this._samplers = []; } } // ==================== // internal // ==================== function _createShader(gl, type, src) { let shader = gl.createShader(type); gl.shaderSource(shader, src); gl.compileShader(shader); return shader; }