/*------------------------------------------------------------------------- * drawElements Quality gluShaderProgram.Program OpenGL ES Utilities * ------------------------------------------------ * * Copyright 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ 'use strict'; goog.provide('framework.opengl.gluShaderProgram'); goog.scope(function() { var gluShaderProgram = framework.opengl.gluShaderProgram; /** * gluShaderProgram.Shader type enum * @enum {number} */ gluShaderProgram.shaderType = { VERTEX: 0, FRAGMENT: 1 }; /** * gluShaderProgram.Shader type enum name * @param {gluShaderProgram.shaderType} shaderType * @return {string} */ gluShaderProgram.getShaderTypeName = function(shaderType) { var s_names = [ 'vertex', 'fragment' ]; return s_names[shaderType]; }; /** * Get GL shader type from gluShaderProgram.shaderType * @param {WebGL2RenderingContext} gl WebGL context * @param {gluShaderProgram.shaderType} type gluShaderProgram.Shader Type * @return {number} GL shader type */ gluShaderProgram.getGLShaderType = function(gl, type) { var _glShaderType; switch (type) { case gluShaderProgram.shaderType.VERTEX: _glShaderType = gl.VERTEX_SHADER; break; case gluShaderProgram.shaderType.FRAGMENT: _glShaderType = gl.FRAGMENT_SHADER; break; default: throw new Error('Unknown shader type ' + type); } return _glShaderType; }; /** * Declares shader information * @constructor * @param {gluShaderProgram.shaderType} type * @param {string=} source */ gluShaderProgram.ShaderInfo = function(type, source) { this.type = type; /** gluShaderProgram.Shader type. */ this.source = source; /** gluShaderProgram.Shader source. */ this.infoLog; /** Compile info log. */ this.compileOk = false; /** Did compilation succeed? */ this.compileTimeUs = 0; /** Compile time in microseconds (us). */ }; /** * Generates vertex shader info from source * @param {string} source * @return {gluShaderProgram.ShaderInfo} vertex shader info */ gluShaderProgram.genVertexSource = function(source) { /** @type {gluShaderProgram.ShaderInfo} */ var shader = new gluShaderProgram.ShaderInfo(gluShaderProgram.shaderType.VERTEX, source); return shader; }; /** * Generates fragment shader info from source * @param {string} source * @return {gluShaderProgram.ShaderInfo} fragment shader info */ gluShaderProgram.genFragmentSource = function(source) { /** @type {gluShaderProgram.ShaderInfo} */ var shader = new gluShaderProgram.ShaderInfo(gluShaderProgram.shaderType.FRAGMENT, source); return shader; }; /** * Generates shader from WebGL context and type * @constructor * @param {WebGL2RenderingContext} gl WebGL context * @param {gluShaderProgram.shaderType} type gluShaderProgram.Shader Type */ gluShaderProgram.Shader = function(gl, type) { this.gl = gl; this.info = new gluShaderProgram.ShaderInfo(type); /** Client-side clone of state for debug / perf reasons. */ this.shader = gl.createShader(gluShaderProgram.getGLShaderType(gl, type)); assertMsgOptions(gl.getError() == gl.NO_ERROR, 'gl.createShader()', false, true); this.setSources = function(source) { this.gl.shaderSource(this.shader, source); assertMsgOptions(this.gl.getError() == this.gl.NO_ERROR, 'glshaderSource()', false, true); this.info.source = source; }; this.getCompileStatus = function() { return this.info.compileOk; }; this.compile = function() { this.info.compileOk = false; this.info.compileTimeUs = 0; this.info.infoLog = ''; /** @type {Date} */ var compileStart = new Date(); this.gl.compileShader(this.shader); /** @type {Date} */ var compileEnd = new Date(); this.info.compileTimeUs = 1000 * (compileEnd.getTime() - compileStart.getTime()); assertMsgOptions(this.gl.getError() == this.gl.NO_ERROR, 'gl.compileShader()', false, true); var compileStatus = this.gl.getShaderParameter(this.shader, this.gl.COMPILE_STATUS); assertMsgOptions(this.gl.getError() == this.gl.NO_ERROR, 'glGetShaderParameter()', false, true); this.info.compileOk = compileStatus; this.info.infoLog = this.gl.getShaderInfoLog(this.shader); }; this.getShader = function() { return this.shader; }; this.destroy = function() { this.gl.deleteShader(this.shader); }; }; /** * Creates gluShaderProgram.ProgramInfo * @constructor */ gluShaderProgram.ProgramInfo = function() { /** @type {string} */ this.infoLog = ''; /** Link info log. */ /** @type {boolean} */ this.linkOk = false; /** Did link succeed? */ /** @type {number} */ this.linkTimeUs = 0; /** Link time in microseconds (us). */ }; /** * Creates program. * Inner methods: attach shaders, bind attributes location, link program and transform Feedback Varyings * @constructor * @param {WebGL2RenderingContext} gl WebGL context * @param {WebGLProgram=} programID */ gluShaderProgram.Program = function(gl, programID) { this.gl = gl; this.program = programID || null; this.info = new gluShaderProgram.ProgramInfo(); if (!programID) { this.program = gl.createProgram(); assertMsgOptions(gl.getError() == gl.NO_ERROR, 'gl.createProgram()', false, true); } }; /** * @return {WebGLProgram} */ gluShaderProgram.Program.prototype.getProgram = function() { return this.program; }; /** * @return {gluShaderProgram.ProgramInfo} */ gluShaderProgram.Program.prototype.getInfo = function() { return this.info; }; /** * @param {WebGLShader} shader */ gluShaderProgram.Program.prototype.attachShader = function(shader) { this.gl.attachShader(this.program, shader); assertMsgOptions(this.gl.getError() == this.gl.NO_ERROR, 'gl.attachShader()', false, true); }; /** * @param {WebGLShader} shader */ gluShaderProgram.Program.prototype.detachShader = function(shader) { this.gl.detachShader(this.program, shader); assertMsgOptions(this.gl.getError() == this.gl.NO_ERROR, 'gl.detachShader()', false, true); }; /** * @param {number} location * @param {string} name */ gluShaderProgram.Program.prototype.bindAttribLocation = function(location, name) { this.gl.bindAttribLocation(this.program, location, name); assertMsgOptions(this.gl.getError() == this.gl.NO_ERROR, 'gl.bindAttribLocation()', false, true); }; gluShaderProgram.Program.prototype.link = function() { this.info.linkOk = false; this.info.linkTimeUs = 0; this.info.infoLog = ''; /** @type {Date} */ var linkStart = new Date(); this.gl.linkProgram(this.program); /** @type {Date} */ var linkEnd = new Date(); this.info.linkTimeUs = 1000 * (linkEnd.getTime() - linkStart.getTime()); assertMsgOptions(this.gl.getError() == this.gl.NO_ERROR, 'gl.linkProgram()', false, true); var linkStatus = this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS); assertMsgOptions(this.gl.getError() == this.gl.NO_ERROR, 'gl.getProgramParameter()', false, true); this.info.linkOk = linkStatus; this.info.infoLog = this.gl.getProgramInfoLog(this.program); if (!this.info.linkOk) bufferedLogToConsole("program linking: " + this.info.infoLog); }; /** * return {boolean} */ gluShaderProgram.Program.prototype.getLinkStatus = function() { return this.info.linkOk; }; /** * @param {Array} varyings * @param {number} bufferMode */ gluShaderProgram.Program.prototype.transformFeedbackVaryings = function(varyings, bufferMode) { this.gl.transformFeedbackVaryings(this.program, varyings, bufferMode); assertMsgOptions(this.gl.getError() == this.gl.NO_ERROR, 'gl.transformFeedbackVaryings()', false, true); }; /** * Assigns gl WebGL context and programSources. Declares array of shaders and new program() * @constructor * @param {WebGL2RenderingContext} gl WebGL context * @param {gluShaderProgram.ProgramSources} programSources */ gluShaderProgram.ShaderProgram = function(gl, programSources) { this.gl = gl; this.programSources = programSources; this.shaders = []; this.program = new gluShaderProgram.Program(gl); /** @type {boolean} */ this.shadersOK = true; for (var i = 0; i < programSources.sources.length; i++) { /** @type {gluShaderProgram.Shader} */ var shader = new gluShaderProgram.Shader(gl, programSources.sources[i].type); shader.setSources(programSources.sources[i].source); shader.compile(); this.shaders.push(shader); this.shadersOK = this.shadersOK && shader.getCompileStatus(); if (!shader.getCompileStatus()) { bufferedLogToConsole('gluShaderProgram.Shader:\n' + programSources.sources[i].source); bufferedLogToConsole('Compile status: ' + shader.getCompileStatus()); bufferedLogToConsole('Shader infoLog: ' + shader.info.infoLog); } } if (this.shadersOK) { for (var i = 0; i < this.shaders.length; i++) this.program.attachShader(this.shaders[i].getShader()); for (var attrib in programSources.attribLocationBindings) this.program.bindAttribLocation(programSources.attribLocationBindings[attrib], attrib); if (programSources.transformFeedbackBufferMode) if (programSources.transformFeedbackBufferMode === gl.NONE) assertMsgOptions(programSources.transformFeedbackVaryings.length === 0, 'Transform feedback sanity check', false, true); else this.program.transformFeedbackVaryings(programSources.transformFeedbackVaryings, programSources.transformFeedbackBufferMode); /* TODO: GLES 3.1: set separable flag */ this.program.link(); } }; /** * return {WebGLProgram} */ gluShaderProgram.ShaderProgram.prototype.getProgram = function() { return this.program.program; }; /** * @return {gluShaderProgram.ProgramInfo} */ gluShaderProgram.ShaderProgram.prototype.getProgramInfo = function() { return this.program.info; }; gluShaderProgram.ShaderProgram.prototype.isOk = function() { return this.shadersOK && this.program.getLinkStatus(); }; gluShaderProgram.containerTypes = { ATTRIB_LOCATION_BINDING: 0, TRANSFORM_FEEDBACK_MODE: 1, TRANSFORM_FEEDBACK_VARYING: 2, TRANSFORM_FEEDBACK_VARYINGS: 3, SHADER_SOURCE: 4, PROGRAM_SEPARABLE: 5, PROGRAM_SOURCES: 6, CONTAINER_TYPE_LAST: 7, ATTACHABLE_BEGIN: 0, // ATTRIB_LOCATION_BINDING ATTACHABLE_END: 5 + 1 // PROGRAM_SEPARABLE + 1 }; /** * @constructor */ gluShaderProgram.AttribLocationBinding = function(name, location) { this.name = name; this.location = location; this.getContainerType = function() { return gluShaderProgram.containerTypes.ATTRIB_LOCATION_BINDING; }; }; /** * @constructor */ gluShaderProgram.TransformFeedbackMode = function(mode) { this.mode = mode; this.getContainerType = function() { return gluShaderProgram.containerTypes.TRANSFORM_FEEDBACK_MODE; }; }; /** * @constructor * @param {string} name */ gluShaderProgram.TransformFeedbackVarying = function(name) { /** @type {string} */ this.name = name; this.getContainerType = function() { return gluShaderProgram.containerTypes.TRANSFORM_FEEDBACK_VARYING; }; }; /** * @constructor * @param {Array} array */ gluShaderProgram.TransformFeedbackVaryings = function(array) { /** @type {Array} */ this.array = array; this.getContainerType = function() { return gluShaderProgram.containerTypes.TRANSFORM_FEEDBACK_VARYINGS; }; }; /** * @constructor */ gluShaderProgram.ProgramSeparable = function(separable) { this.separable = separable; this.getContainerType = function() { return gluShaderProgram.containerTypes.PROGRAM_SEPARABLE; }; }; /** * @constructor */ gluShaderProgram.VertexSource = function(str) { this.shaderType = gluShaderProgram.shaderType.VERTEX; this.source = str; this.getContainerType = function() { return gluShaderProgram.containerTypes.SHADER_SOURCE; }; }; /** * @constructor */ gluShaderProgram.FragmentSource = function(str) { this.shaderType = gluShaderProgram.shaderType.FRAGMENT; this.source = str; this.getContainerType = function() { return gluShaderProgram.containerTypes.SHADER_SOURCE; }; }; /** * Create gluShaderProgram.ProgramSources. * @constructor */ gluShaderProgram.ProgramSources = function() { /** @type {Array} */ this.sources = []; this.attribLocationBindings = []; /** @type {Array} */ this.transformFeedbackVaryings = []; this.transformFeedbackBufferMode = 0; this.separable = false; }; gluShaderProgram.ProgramSources.prototype.getContainerType = function() { return gluShaderProgram.containerTypes.PROGRAM_SOURCES; }; gluShaderProgram.ProgramSources.prototype.add = function(item) { var type = undefined; if (typeof(item.getContainerType) == 'function') { type = item.getContainerType(); if ( typeof(type) != 'number' || type < gluShaderProgram.containerTypes.ATTACHABLE_BEGIN || type >= gluShaderProgram.containerTypes.ATTACHABLE_END ) { type = undefined; } } switch (type) { case gluShaderProgram.containerTypes.ATTRIB_LOCATION_BINDING: this.attribLocationBindings.push(item); break; case gluShaderProgram.containerTypes.TRANSFORM_FEEDBACK_MODE: this.transformFeedbackBufferMode = item.mode; break; case gluShaderProgram.containerTypes.TRANSFORM_FEEDBACK_VARYING: this.transformFeedbackVaryings.push(item.name); break; case gluShaderProgram.containerTypes.TRANSFORM_FEEDBACK_VARYINGS: this.transformFeedbackVaryings = this.transformFeedbackVaryings.concat(item.array); break; case gluShaderProgram.containerTypes.SHADER_SOURCE: this.sources.push(new gluShaderProgram.ShaderInfo(item.shaderType, item.source)); break; case gluShaderProgram.containerTypes.PROGRAM_SEPARABLE: this.separable = item.separable; break; default: throw new Error('Type \"' + type + '\" cannot be added to gluShaderProgram.ProgramSources.'); break; } return this; }; /** * //! Helper for constructing vertex-fragment source pair. * @param {string} vertexSrc * @param {string} fragmentSrc * @return {gluShaderProgram.ProgramSources} */ gluShaderProgram.makeVtxFragSources = function(vertexSrc, fragmentSrc) { /** @type  {gluShaderProgram.ProgramSources} */ var sources = new gluShaderProgram.ProgramSources(); sources.sources.push(gluShaderProgram.genVertexSource(vertexSrc)); sources.sources.push(gluShaderProgram.genFragmentSource(fragmentSrc)); return sources; }; });