diff -r 000000000000 -r 6474c204b198 browser/devtools/tilt/tilt-gl.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/browser/devtools/tilt/tilt-gl.js Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,1595 @@ +/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +const {Cc, Ci, Cu} = require("chrome"); + +let TiltUtils = require("devtools/tilt/tilt-utils"); +let {TiltMath, mat4} = require("devtools/tilt/tilt-math"); + +Cu.import("resource://gre/modules/Services.jsm"); + +const WEBGL_CONTEXT_NAME = "experimental-webgl"; + + +/** + * Module containing thin wrappers around low-level WebGL functions. + */ +let TiltGL = {}; +module.exports = TiltGL; + +/** + * Contains commonly used helper methods used in any 3D application. + * + * @param {HTMLCanvasElement} aCanvas + * the canvas element used for rendering + * @param {Function} onError + * optional, function called if initialization failed + * @param {Function} onLoad + * optional, function called if initialization worked + */ +TiltGL.Renderer = function TGL_Renderer(aCanvas, onError, onLoad) +{ + /** + * The WebGL context obtained from the canvas element, used for drawing. + */ + this.context = TiltGL.create3DContext(aCanvas); + + // check if the context was created successfully + if (!this.context) { + TiltUtils.Output.alert("Firefox", TiltUtils.L10n.get("initTilt.error")); + TiltUtils.Output.error(TiltUtils.L10n.get("initWebGL.error")); + + if ("function" === typeof onError) { + onError(); + } + return; + } + + // set the default clear color and depth buffers + this.context.clearColor(0, 0, 0, 0); + this.context.clearDepth(1); + + /** + * Variables representing the current framebuffer width and height. + */ + this.width = aCanvas.width; + this.height = aCanvas.height; + this.initialWidth = this.width; + this.initialHeight = this.height; + + /** + * The current model view matrix. + */ + this.mvMatrix = mat4.identity(mat4.create()); + + /** + * The current projection matrix. + */ + this.projMatrix = mat4.identity(mat4.create()); + + /** + * The current fill color applied to any objects which can be filled. + * These are rectangles, circles, boxes, 2d or 3d primitives in general. + */ + this._fillColor = []; + + /** + * The current stroke color applied to any objects which can be stroked. + * This property mostly refers to lines. + */ + this._strokeColor = []; + + /** + * Variable representing the current stroke weight. + */ + this._strokeWeightValue = 0; + + /** + * A shader useful for drawing vertices with only a color component. + */ + this._colorShader = new TiltGL.Program(this.context, { + vs: TiltGL.ColorShader.vs, + fs: TiltGL.ColorShader.fs, + attributes: ["vertexPosition"], + uniforms: ["mvMatrix", "projMatrix", "fill"] + }); + + // create helper functions to create shaders, meshes, buffers and textures + this.Program = + TiltGL.Program.bind(TiltGL.Program, this.context); + this.VertexBuffer = + TiltGL.VertexBuffer.bind(TiltGL.VertexBuffer, this.context); + this.IndexBuffer = + TiltGL.IndexBuffer.bind(TiltGL.IndexBuffer, this.context); + this.Texture = + TiltGL.Texture.bind(TiltGL.Texture, this.context); + + // set the default mvp matrices, tint, fill, stroke and other visual props. + this.defaults(); + + // the renderer was created successfully + if ("function" === typeof onLoad) { + onLoad(); + } +}; + +TiltGL.Renderer.prototype = { + + /** + * Clears the color and depth buffers. + */ + clear: function TGLR_clear() + { + let gl = this.context; + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + }, + + /** + * Sets if depth testing should be enabled or not. + * Disabling could be useful when handling transparency (for example). + * + * @param {Boolean} aEnabledFlag + * true if depth testing should be enabled + */ + depthTest: function TGLR_depthTest(aEnabledFlag) + { + let gl = this.context; + + if (aEnabledFlag) { + gl.enable(gl.DEPTH_TEST); + } else { + gl.disable(gl.DEPTH_TEST); + } + }, + + /** + * Sets if stencil testing should be enabled or not. + * + * @param {Boolean} aEnabledFlag + * true if stencil testing should be enabled + */ + stencilTest: function TGLR_stencilTest(aEnabledFlag) + { + let gl = this.context; + + if (aEnabledFlag) { + gl.enable(gl.STENCIL_TEST); + } else { + gl.disable(gl.STENCIL_TEST); + } + }, + + /** + * Sets cull face, either "front", "back" or disabled. + * + * @param {String} aModeFlag + * blending mode, either "front", "back", "both" or falsy + */ + cullFace: function TGLR_cullFace(aModeFlag) + { + let gl = this.context; + + switch (aModeFlag) { + case "front": + gl.enable(gl.CULL_FACE); + gl.cullFace(gl.FRONT); + break; + case "back": + gl.enable(gl.CULL_FACE); + gl.cullFace(gl.BACK); + break; + case "both": + gl.enable(gl.CULL_FACE); + gl.cullFace(gl.FRONT_AND_BACK); + break; + default: + gl.disable(gl.CULL_FACE); + } + }, + + /** + * Specifies the orientation of front-facing polygons. + * + * @param {String} aModeFlag + * either "cw" or "ccw" + */ + frontFace: function TGLR_frontFace(aModeFlag) + { + let gl = this.context; + + switch (aModeFlag) { + case "cw": + gl.frontFace(gl.CW); + break; + case "ccw": + gl.frontFace(gl.CCW); + break; + } + }, + + /** + * Sets blending, either "alpha" or "add" (additive blending). + * Anything else disables blending. + * + * @param {String} aModeFlag + * blending mode, either "alpha", "add" or falsy + */ + blendMode: function TGLR_blendMode(aModeFlag) + { + let gl = this.context; + + switch (aModeFlag) { + case "alpha": + gl.enable(gl.BLEND); + gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + break; + case "add": + gl.enable(gl.BLEND); + gl.blendFunc(gl.SRC_ALPHA, gl.ONE); + break; + default: + gl.disable(gl.BLEND); + } + }, + + /** + * Helper function to activate the color shader. + * + * @param {TiltGL.VertexBuffer} aVerticesBuffer + * a buffer of vertices positions + * @param {Array} aColor + * the color fill to be used as [r, g, b, a] with 0..1 range + * @param {Array} aMvMatrix + * the model view matrix + * @param {Array} aProjMatrix + * the projection matrix + */ + useColorShader: function TGLR_useColorShader( + aVerticesBuffer, aColor, aMvMatrix, aProjMatrix) + { + let program = this._colorShader; + + // use this program + program.use(); + + // bind the attributes and uniforms as necessary + program.bindVertexBuffer("vertexPosition", aVerticesBuffer); + program.bindUniformMatrix("mvMatrix", aMvMatrix || this.mvMatrix); + program.bindUniformMatrix("projMatrix", aProjMatrix || this.projMatrix); + program.bindUniformVec4("fill", aColor || this._fillColor); + }, + + /** + * Draws bound vertex buffers using the specified parameters. + * + * @param {Number} aDrawMode + * WebGL enum, like TRIANGLES + * @param {Number} aCount + * the number of indices to be rendered + */ + drawVertices: function TGLR_drawVertices(aDrawMode, aCount) + { + this.context.drawArrays(aDrawMode, 0, aCount); + }, + + /** + * Draws bound vertex buffers using the specified parameters. + * This function also makes use of an index buffer. + * + * @param {Number} aDrawMode + * WebGL enum, like TRIANGLES + * @param {TiltGL.IndexBuffer} aIndicesBuffer + * indices for the vertices buffer + */ + drawIndexedVertices: function TGLR_drawIndexedVertices( + aDrawMode, aIndicesBuffer) + { + let gl = this.context; + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, aIndicesBuffer._ref); + gl.drawElements(aDrawMode, aIndicesBuffer.numItems, gl.UNSIGNED_SHORT, 0); + }, + + /** + * Sets the current fill color. + * + * @param {Array} aColor + * the color fill to be used as [r, g, b, a] with 0..1 range + * @param {Number} aMultiplyAlpha + * optional, scalar to multiply the alpha element with + */ + fill: function TGLR_fill(aColor, aMultiplyAlpha) + { + let fill = this._fillColor; + + fill[0] = aColor[0]; + fill[1] = aColor[1]; + fill[2] = aColor[2]; + fill[3] = aColor[3] * (aMultiplyAlpha || 1); + }, + + /** + * Sets the current stroke color. + * + * @param {Array} aColor + * the color stroke to be used as [r, g, b, a] with 0..1 range + * @param {Number} aMultiplyAlpha + * optional, scalar to multiply the alpha element with + */ + stroke: function TGLR_stroke(aColor, aMultiplyAlpha) + { + let stroke = this._strokeColor; + + stroke[0] = aColor[0]; + stroke[1] = aColor[1]; + stroke[2] = aColor[2]; + stroke[3] = aColor[3] * (aMultiplyAlpha || 1); + }, + + /** + * Sets the current stroke weight (line width). + * + * @param {Number} aWeight + * the stroke weight + */ + strokeWeight: function TGLR_strokeWeight(aWeight) + { + if (this._strokeWeightValue !== aWeight) { + this._strokeWeightValue = aWeight; + this.context.lineWidth(aWeight); + } + }, + + /** + * Sets a default perspective projection, with the near frustum rectangle + * mapped to the canvas width and height bounds. + */ + perspective: function TGLR_perspective() + { + let fov = 45; + let w = this.width; + let h = this.height; + let x = w / 2; + let y = h / 2; + let z = y / Math.tan(TiltMath.radians(fov) / 2); + let aspect = w / h; + let znear = z / 10; + let zfar = z * 10; + + mat4.perspective(fov, aspect, znear, zfar, this.projMatrix, -1); + mat4.translate(this.projMatrix, [-x, -y, -z]); + mat4.identity(this.mvMatrix); + }, + + /** + * Sets a default orthographic projection (recommended for 2d rendering). + */ + ortho: function TGLR_ortho() + { + mat4.ortho(0, this.width, this.height, 0, -1, 1, this.projMatrix); + mat4.identity(this.mvMatrix); + }, + + /** + * Sets a custom projection matrix. + * @param {Array} matrix: the custom projection matrix to be used + */ + projection: function TGLR_projection(aMatrix) + { + mat4.set(aMatrix, this.projMatrix); + mat4.identity(this.mvMatrix); + }, + + /** + * Resets the model view matrix to identity. + * This is a default matrix with no rotation, no scaling, at (0, 0, 0); + */ + origin: function TGLR_origin() + { + mat4.identity(this.mvMatrix); + }, + + /** + * Transforms the model view matrix with a new matrix. + * Useful for creating custom transformations. + * + * @param {Array} matrix: the matrix to be multiply the model view with + */ + transform: function TGLR_transform(aMatrix) + { + mat4.multiply(this.mvMatrix, aMatrix); + }, + + /** + * Translates the model view by the x, y and z coordinates. + * + * @param {Number} x + * the x amount of translation + * @param {Number} y + * the y amount of translation + * @param {Number} z + * optional, the z amount of translation + */ + translate: function TGLR_translate(x, y, z) + { + mat4.translate(this.mvMatrix, [x, y, z || 0]); + }, + + /** + * Rotates the model view by a specified angle on the x, y and z axis. + * + * @param {Number} angle + * the angle expressed in radians + * @param {Number} x + * the x axis of the rotation + * @param {Number} y + * the y axis of the rotation + * @param {Number} z + * the z axis of the rotation + */ + rotate: function TGLR_rotate(angle, x, y, z) + { + mat4.rotate(this.mvMatrix, angle, [x, y, z]); + }, + + /** + * Rotates the model view by a specified angle on the x axis. + * + * @param {Number} aAngle + * the angle expressed in radians + */ + rotateX: function TGLR_rotateX(aAngle) + { + mat4.rotateX(this.mvMatrix, aAngle); + }, + + /** + * Rotates the model view by a specified angle on the y axis. + * + * @param {Number} aAngle + * the angle expressed in radians + */ + rotateY: function TGLR_rotateY(aAngle) + { + mat4.rotateY(this.mvMatrix, aAngle); + }, + + /** + * Rotates the model view by a specified angle on the z axis. + * + * @param {Number} aAngle + * the angle expressed in radians + */ + rotateZ: function TGLR_rotateZ(aAngle) + { + mat4.rotateZ(this.mvMatrix, aAngle); + }, + + /** + * Scales the model view by the x, y and z coordinates. + * + * @param {Number} x + * the x amount of scaling + * @param {Number} y + * the y amount of scaling + * @param {Number} z + * optional, the z amount of scaling + */ + scale: function TGLR_scale(x, y, z) + { + mat4.scale(this.mvMatrix, [x, y, z || 1]); + }, + + /** + * Performs a custom interpolation between two matrices. + * The result is saved in the first operand. + * + * @param {Array} aMat + * the first matrix + * @param {Array} aMat2 + * the second matrix + * @param {Number} aLerp + * interpolation amount between the two inputs + * @param {Number} aDamping + * optional, scalar adjusting the interpolation amortization + * @param {Number} aBalance + * optional, scalar adjusting the interpolation shift ammount + */ + lerp: function TGLR_lerp(aMat, aMat2, aLerp, aDamping, aBalance) + { + if (aLerp < 0 || aLerp > 1) { + return; + } + + // calculate the interpolation factor based on the damping and step + let f = Math.pow(1 - Math.pow(aLerp, aDamping || 1), 1 / aBalance || 1); + + // interpolate each element from the two matrices + for (let i = 0, len = this.projMatrix.length; i < len; i++) { + aMat[i] = aMat[i] + f * (aMat2[i] - aMat[i]); + } + }, + + /** + * Resets the drawing style to default. + */ + defaults: function TGLR_defaults() + { + this.depthTest(true); + this.stencilTest(false); + this.cullFace(false); + this.frontFace("ccw"); + this.blendMode("alpha"); + this.fill([1, 1, 1, 1]); + this.stroke([0, 0, 0, 1]); + this.strokeWeight(1); + this.perspective(); + this.origin(); + }, + + /** + * Draws a quad composed of four vertices. + * Vertices must be in clockwise order, or else drawing will be distorted. + * Do not abuse this function, it is quite slow. + * + * @param {Array} aV0 + * the [x, y, z] position of the first triangle point + * @param {Array} aV1 + * the [x, y, z] position of the second triangle point + * @param {Array} aV2 + * the [x, y, z] position of the third triangle point + * @param {Array} aV3 + * the [x, y, z] position of the fourth triangle point + */ + quad: function TGLR_quad(aV0, aV1, aV2, aV3) + { + let gl = this.context; + let fill = this._fillColor; + let stroke = this._strokeColor; + let vert = new TiltGL.VertexBuffer(gl, [aV0[0], aV0[1], aV0[2] || 0, + aV1[0], aV1[1], aV1[2] || 0, + aV2[0], aV2[1], aV2[2] || 0, + aV3[0], aV3[1], aV3[2] || 0], 3); + + // use the necessary shader and draw the vertices + this.useColorShader(vert, fill); + this.drawVertices(gl.TRIANGLE_FAN, vert.numItems); + + this.useColorShader(vert, stroke); + this.drawVertices(gl.LINE_LOOP, vert.numItems); + + TiltUtils.destroyObject(vert); + }, + + /** + * Function called when this object is destroyed. + */ + finalize: function TGLR_finalize() + { + if (this.context) { + TiltUtils.destroyObject(this._colorShader); + } + } +}; + +/** + * Creates a vertex buffer containing an array of elements. + * + * @param {Object} aContext + * a WebGL context + * @param {Array} aElementsArray + * an array of numbers (floats) + * @param {Number} aItemSize + * how many items create a block + * @param {Number} aNumItems + * optional, how many items to use from the array + */ +TiltGL.VertexBuffer = function TGL_VertexBuffer( + aContext, aElementsArray, aItemSize, aNumItems) +{ + /** + * The parent WebGL context. + */ + this._context = aContext; + + /** + * The array buffer. + */ + this._ref = null; + + /** + * Array of number components contained in the buffer. + */ + this.components = null; + + /** + * Variables defining the internal structure of the buffer. + */ + this.itemSize = 0; + this.numItems = 0; + + // if the array is specified in the constructor, initialize directly + if (aElementsArray) { + this.initBuffer(aElementsArray, aItemSize, aNumItems); + } +}; + +TiltGL.VertexBuffer.prototype = { + + /** + * Initializes buffer data to be used for drawing, using an array of floats. + * The "aNumItems" param can be specified to use only a portion of the array. + * + * @param {Array} aElementsArray + * an array of floats + * @param {Number} aItemSize + * how many items create a block + * @param {Number} aNumItems + * optional, how many items to use from the array + */ + initBuffer: function TGLVB_initBuffer(aElementsArray, aItemSize, aNumItems) + { + let gl = this._context; + + // the aNumItems parameter is optional, we can compute it if not specified + aNumItems = aNumItems || aElementsArray.length / aItemSize; + + // create the Float32Array using the elements array + this.components = new Float32Array(aElementsArray); + + // create an array buffer and bind the elements as a Float32Array + this._ref = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, this._ref); + gl.bufferData(gl.ARRAY_BUFFER, this.components, gl.STATIC_DRAW); + + // remember some properties, useful when binding the buffer to a shader + this.itemSize = aItemSize; + this.numItems = aNumItems; + }, + + /** + * Function called when this object is destroyed. + */ + finalize: function TGLVB_finalize() + { + if (this._context) { + this._context.deleteBuffer(this._ref); + } + } +}; + +/** + * Creates an index buffer containing an array of indices. + * + * @param {Object} aContext + * a WebGL context + * @param {Array} aElementsArray + * an array of unsigned integers + * @param {Number} aNumItems + * optional, how many items to use from the array + */ +TiltGL.IndexBuffer = function TGL_IndexBuffer( + aContext, aElementsArray, aNumItems) +{ + /** + * The parent WebGL context. + */ + this._context = aContext; + + /** + * The element array buffer. + */ + this._ref = null; + + /** + * Array of number components contained in the buffer. + */ + this.components = null; + + /** + * Variables defining the internal structure of the buffer. + */ + this.itemSize = 0; + this.numItems = 0; + + // if the array is specified in the constructor, initialize directly + if (aElementsArray) { + this.initBuffer(aElementsArray, aNumItems); + } +}; + +TiltGL.IndexBuffer.prototype = { + + /** + * Initializes a buffer of vertex indices, using an array of unsigned ints. + * The item size will automatically default to 1, and the "numItems" will be + * equal to the number of items in the array if not specified. + * + * @param {Array} aElementsArray + * an array of numbers (unsigned integers) + * @param {Number} aNumItems + * optional, how many items to use from the array + */ + initBuffer: function TGLIB_initBuffer(aElementsArray, aNumItems) + { + let gl = this._context; + + // the aNumItems parameter is optional, we can compute it if not specified + aNumItems = aNumItems || aElementsArray.length; + + // create the Uint16Array using the elements array + this.components = new Uint16Array(aElementsArray); + + // create an array buffer and bind the elements as a Uint16Array + this._ref = gl.createBuffer(); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._ref); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.components, gl.STATIC_DRAW); + + // remember some properties, useful when binding the buffer to a shader + this.itemSize = 1; + this.numItems = aNumItems; + }, + + /** + * Function called when this object is destroyed. + */ + finalize: function TGLIB_finalize() + { + if (this._context) { + this._context.deleteBuffer(this._ref); + } + } +}; + +/** + * A program is composed of a vertex and a fragment shader. + * + * @param {Object} aProperties + * optional, an object containing the following properties: + * {String} vs: the vertex shader source code + * {String} fs: the fragment shader source code + * {Array} attributes: an array of attributes as strings + * {Array} uniforms: an array of uniforms as strings + */ +TiltGL.Program = function(aContext, aProperties) +{ + // make sure the properties parameter is a valid object + aProperties = aProperties || {}; + + /** + * The parent WebGL context. + */ + this._context = aContext; + + /** + * A reference to the actual GLSL program. + */ + this._ref = null; + + /** + * Each program has an unique id assigned. + */ + this._id = -1; + + /** + * Two arrays: an attributes array, containing all the cached attributes + * and a uniforms array, containing all the cached uniforms. + */ + this._attributes = null; + this._uniforms = null; + + // if the sources are specified in the constructor, initialize directly + if (aProperties.vs && aProperties.fs) { + this.initProgram(aProperties); + } +}; + +TiltGL.Program.prototype = { + + /** + * Initializes a shader program, using specified source code as strings. + * + * @param {Object} aProperties + * an object containing the following properties: + * {String} vs: the vertex shader source code + * {String} fs: the fragment shader source code + * {Array} attributes: an array of attributes as strings + * {Array} uniforms: an array of uniforms as strings + */ + initProgram: function TGLP_initProgram(aProperties) + { + this._ref = TiltGL.ProgramUtils.create(this._context, aProperties); + + // cache for faster access + this._id = this._ref.id; + this._attributes = this._ref.attributes; + this._uniforms = this._ref.uniforms; + + // cleanup + delete this._ref.id; + delete this._ref.attributes; + delete this._ref.uniforms; + }, + + /** + * Uses the shader program as current one for the WebGL context; it also + * enables vertex attributes necessary to enable when using this program. + * This method also does some useful caching, as the function "useProgram" + * could take quite a lot of time. + */ + use: function TGLP_use() + { + let id = this._id; + let utils = TiltGL.ProgramUtils; + + // check if the program wasn't already active + if (utils._activeProgram !== id) { + utils._activeProgram = id; + + // use the the program if it wasn't already set + this._context.useProgram(this._ref); + this.cleanupVertexAttrib(); + + // enable any necessary vertex attributes using the cache + for each (let attribute in this._attributes) { + this._context.enableVertexAttribArray(attribute); + utils._enabledAttributes.push(attribute); + } + } + }, + + /** + * Disables all currently enabled vertex attribute arrays. + */ + cleanupVertexAttrib: function TGLP_cleanupVertexAttrib() + { + let utils = TiltGL.ProgramUtils; + + for each (let attribute in utils._enabledAttributes) { + this._context.disableVertexAttribArray(attribute); + } + utils._enabledAttributes = []; + }, + + /** + * Binds a vertex buffer as an array buffer for a specific shader attribute. + * + * @param {String} aAtribute + * the attribute name obtained from the shader + * @param {Float32Array} aBuffer + * the buffer to be bound + */ + bindVertexBuffer: function TGLP_bindVertexBuffer(aAtribute, aBuffer) + { + // get the cached attribute value from the shader + let gl = this._context; + let attr = this._attributes[aAtribute]; + let size = aBuffer.itemSize; + + gl.bindBuffer(gl.ARRAY_BUFFER, aBuffer._ref); + gl.vertexAttribPointer(attr, size, gl.FLOAT, false, 0, 0); + }, + + /** + * Binds a uniform matrix to the current shader. + * + * @param {String} aUniform + * the uniform name to bind the variable to + * @param {Float32Array} m + * the matrix to be bound + */ + bindUniformMatrix: function TGLP_bindUniformMatrix(aUniform, m) + { + this._context.uniformMatrix4fv(this._uniforms[aUniform], false, m); + }, + + /** + * Binds a uniform vector of 4 elements to the current shader. + * + * @param {String} aUniform + * the uniform name to bind the variable to + * @param {Float32Array} v + * the vector to be bound + */ + bindUniformVec4: function TGLP_bindUniformVec4(aUniform, v) + { + this._context.uniform4fv(this._uniforms[aUniform], v); + }, + + /** + * Binds a simple float element to the current shader. + * + * @param {String} aUniform + * the uniform name to bind the variable to + * @param {Number} v + * the variable to be bound + */ + bindUniformFloat: function TGLP_bindUniformFloat(aUniform, f) + { + this._context.uniform1f(this._uniforms[aUniform], f); + }, + + /** + * Binds a uniform texture for a sampler to the current shader. + * + * @param {String} aSampler + * the sampler name to bind the texture to + * @param {TiltGL.Texture} aTexture + * the texture to be bound + */ + bindTexture: function TGLP_bindTexture(aSampler, aTexture) + { + let gl = this._context; + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, aTexture._ref); + gl.uniform1i(this._uniforms[aSampler], 0); + }, + + /** + * Function called when this object is destroyed. + */ + finalize: function TGLP_finalize() + { + if (this._context) { + this._context.useProgram(null); + this._context.deleteProgram(this._ref); + } + } +}; + +/** + * Utility functions for handling GLSL shaders and programs. + */ +TiltGL.ProgramUtils = { + + /** + * Initializes a shader program, using specified source code as strings, + * returning the newly created shader program, by compiling and linking the + * vertex and fragment shader. + * + * @param {Object} aContext + * a WebGL context + * @param {Object} aProperties + * an object containing the following properties: + * {String} vs: the vertex shader source code + * {String} fs: the fragment shader source code + * {Array} attributes: an array of attributes as strings + * {Array} uniforms: an array of uniforms as strings + */ + create: function TGLPU_create(aContext, aProperties) + { + // make sure the properties parameter is a valid object + aProperties = aProperties || {}; + + // compile the two shaders + let vertShader = this.compile(aContext, aProperties.vs, "vertex"); + let fragShader = this.compile(aContext, aProperties.fs, "fragment"); + let program = this.link(aContext, vertShader, fragShader); + + aContext.deleteShader(vertShader); + aContext.deleteShader(fragShader); + + return this.cache(aContext, aProperties, program); + }, + + /** + * Compiles a shader source of a specific type, either vertex or fragment. + * + * @param {Object} aContext + * a WebGL context + * @param {String} aShaderSource + * the source code for the shader + * @param {String} aShaderType + * the shader type ("vertex" or "fragment") + * + * @return {WebGLShader} the compiled shader + */ + compile: function TGLPU_compile(aContext, aShaderSource, aShaderType) + { + let gl = aContext, shader, status; + + // make sure the shader source is valid + if ("string" !== typeof aShaderSource || aShaderSource.length < 1) { + TiltUtils.Output.error( + TiltUtils.L10n.get("compileShader.source.error")); + return null; + } + + // also make sure the necessary shader mime type is valid + if (aShaderType === "vertex") { + shader = gl.createShader(gl.VERTEX_SHADER); + } else if (aShaderType === "fragment") { + shader = gl.createShader(gl.FRAGMENT_SHADER); + } else { + TiltUtils.Output.error( + TiltUtils.L10n.format("compileShader.type.error", [aShaderSource])); + return null; + } + + // set the shader source and compile it + gl.shaderSource(shader, aShaderSource); + gl.compileShader(shader); + + // remember the shader source (useful for debugging and caching) + shader.src = aShaderSource; + + // verify the compile status; if something went wrong, log the error + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + status = gl.getShaderInfoLog(shader); + + TiltUtils.Output.error( + TiltUtils.L10n.format("compileShader.compile.error", [status])); + return null; + } + + // return the newly compiled shader from the specified source + return shader; + }, + + /** + * Links two compiled vertex or fragment shaders together to form a program. + * + * @param {Object} aContext + * a WebGL context + * @param {WebGLShader} aVertShader + * the compiled vertex shader + * @param {WebGLShader} aFragShader + * the compiled fragment shader + * + * @return {WebGLProgram} the newly created and linked shader program + */ + link: function TGLPU_link(aContext, aVertShader, aFragShader) + { + let gl = aContext, program, status; + + // create a program and attach the compiled vertex and fragment shaders + program = gl.createProgram(); + + // attach the vertex and fragment shaders to the program + gl.attachShader(program, aVertShader); + gl.attachShader(program, aFragShader); + gl.linkProgram(program); + + // verify the link status; if something went wrong, log the error + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { + status = gl.getProgramInfoLog(program); + + TiltUtils.Output.error( + TiltUtils.L10n.format("linkProgram.error", [status])); + return null; + } + + // generate an id for the program + program.id = this._count++; + + return program; + }, + + /** + * Caches shader attributes and uniforms as properties for a program object. + * + * @param {Object} aContext + * a WebGL context + * @param {Object} aProperties + * an object containing the following properties: + * {Array} attributes: optional, an array of attributes as strings + * {Array} uniforms: optional, an array of uniforms as strings + * @param {WebGLProgram} aProgram + * the shader program used for caching + * + * @return {WebGLProgram} the same program + */ + cache: function TGLPU_cache(aContext, aProperties, aProgram) + { + // make sure the properties parameter is a valid object + aProperties = aProperties || {}; + + // make sure the attributes and uniforms cache objects are created + aProgram.attributes = {}; + aProgram.uniforms = {}; + + Object.defineProperty(aProgram.attributes, "length", + { value: 0, writable: true, enumerable: false, configurable: true }); + + Object.defineProperty(aProgram.uniforms, "length", + { value: 0, writable: true, enumerable: false, configurable: true }); + + + let attr = aProperties.attributes; + let unif = aProperties.uniforms; + + if (attr) { + for (let i = 0, len = attr.length; i < len; i++) { + // try to get a shader attribute from the program + let param = attr[i]; + let loc = aContext.getAttribLocation(aProgram, param); + + if ("number" === typeof loc && loc > -1) { + // if we get an attribute location, store it + // bind the new parameter only if it was not already defined + if (aProgram.attributes[param] === undefined) { + aProgram.attributes[param] = loc; + aProgram.attributes.length++; + } + } + } + } + + if (unif) { + for (let i = 0, len = unif.length; i < len; i++) { + // try to get a shader uniform from the program + let param = unif[i]; + let loc = aContext.getUniformLocation(aProgram, param); + + if ("object" === typeof loc && loc) { + // if we get a uniform object, store it + // bind the new parameter only if it was not already defined + if (aProgram.uniforms[param] === undefined) { + aProgram.uniforms[param] = loc; + aProgram.uniforms.length++; + } + } + } + } + + return aProgram; + }, + + /** + * The total number of programs created. + */ + _count: 0, + + /** + * Represents the current active shader, identified by an id. + */ + _activeProgram: -1, + + /** + * Represents the current enabled attributes. + */ + _enabledAttributes: [] +}; + +/** + * This constructor creates a texture from an Image. + * + * @param {Object} aContext + * a WebGL context + * @param {Object} aProperties + * optional, an object containing the following properties: + * {Image} source: the source image for the texture + * {String} format: the format of the texture ("RGB" or "RGBA") + * {String} fill: optional, color to fill the transparent bits + * {String} stroke: optional, color to draw an outline + * {Number} strokeWeight: optional, the width of the outline + * {String} minFilter: either "nearest" or "linear" + * {String} magFilter: either "nearest" or "linear" + * {String} wrapS: either "repeat" or "clamp" + * {String} wrapT: either "repeat" or "clamp" + * {Boolean} mipmap: true if should generate mipmap + */ +TiltGL.Texture = function(aContext, aProperties) +{ + // make sure the properties parameter is a valid object + aProperties = aProperties || {}; + + /** + * The parent WebGL context. + */ + this._context = aContext; + + /** + * A reference to the WebGL texture object. + */ + this._ref = null; + + /** + * Each texture has an unique id assigned. + */ + this._id = -1; + + /** + * Variables specifying the width and height of the texture. + * If these values are less than 0, the texture hasn't loaded yet. + */ + this.width = -1; + this.height = -1; + + /** + * Specifies if the texture has loaded or not. + */ + this.loaded = false; + + // if the image is specified in the constructor, initialize directly + if ("object" === typeof aProperties.source) { + this.initTexture(aProperties); + } else { + TiltUtils.Output.error( + TiltUtils.L10n.get("initTexture.source.error")); + } +}; + +TiltGL.Texture.prototype = { + + /** + * Initializes a texture from a pre-existing image or canvas. + * + * @param {Image} aImage + * the source image or canvas + * @param {Object} aProperties + * an object containing the following properties: + * {Image} source: the source image for the texture + * {String} format: the format of the texture ("RGB" or "RGBA") + * {String} fill: optional, color to fill the transparent bits + * {String} stroke: optional, color to draw an outline + * {Number} strokeWeight: optional, the width of the outline + * {String} minFilter: either "nearest" or "linear" + * {String} magFilter: either "nearest" or "linear" + * {String} wrapS: either "repeat" or "clamp" + * {String} wrapT: either "repeat" or "clamp" + * {Boolean} mipmap: true if should generate mipmap + */ + initTexture: function TGLT_initTexture(aProperties) + { + this._ref = TiltGL.TextureUtils.create(this._context, aProperties); + + // cache for faster access + this._id = this._ref.id; + this.width = this._ref.width; + this.height = this._ref.height; + this.loaded = true; + + // cleanup + delete this._ref.id; + delete this._ref.width; + delete this._ref.height; + delete this.onload; + }, + + /** + * Function called when this object is destroyed. + */ + finalize: function TGLT_finalize() + { + if (this._context) { + this._context.deleteTexture(this._ref); + } + } +}; + +/** + * Utility functions for creating and manipulating textures. + */ +TiltGL.TextureUtils = { + + /** + * Initializes a texture from a pre-existing image or canvas. + * + * @param {Object} aContext + * a WebGL context + * @param {Image} aImage + * the source image or canvas + * @param {Object} aProperties + * an object containing some of the following properties: + * {Image} source: the source image for the texture + * {String} format: the format of the texture ("RGB" or "RGBA") + * {String} fill: optional, color to fill the transparent bits + * {String} stroke: optional, color to draw an outline + * {Number} strokeWeight: optional, the width of the outline + * {String} minFilter: either "nearest" or "linear" + * {String} magFilter: either "nearest" or "linear" + * {String} wrapS: either "repeat" or "clamp" + * {String} wrapT: either "repeat" or "clamp" + * {Boolean} mipmap: true if should generate mipmap + * + * @return {WebGLTexture} the created texture + */ + create: function TGLTU_create(aContext, aProperties) + { + // make sure the properties argument is an object + aProperties = aProperties || {}; + + if (!aProperties.source) { + return null; + } + + let gl = aContext; + let width = aProperties.source.width; + let height = aProperties.source.height; + let format = gl[aProperties.format || "RGB"]; + + // make sure the image is power of two before binding to a texture + let source = this.resizeImageToPowerOfTwo(aProperties); + + // first, create the texture to hold the image data + let texture = gl.createTexture(); + + // attach the image data to the newly create texture + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texImage2D(gl.TEXTURE_2D, 0, format, format, gl.UNSIGNED_BYTE, source); + this.setTextureParams(gl, aProperties); + + // do some cleanup + gl.bindTexture(gl.TEXTURE_2D, null); + + // remember the width and the height + texture.width = width; + texture.height = height; + + // generate an id for the texture + texture.id = this._count++; + + return texture; + }, + + /** + * Sets texture parameters for the current texture binding. + * Optionally, you can also (re)set the current texture binding manually. + * + * @param {Object} aContext + * a WebGL context + * @param {Object} aProperties + * an object containing the texture properties + */ + setTextureParams: function TGLTU_setTextureParams(aContext, aProperties) + { + // make sure the properties argument is an object + aProperties = aProperties || {}; + + let gl = aContext; + let minFilter = gl.TEXTURE_MIN_FILTER; + let magFilter = gl.TEXTURE_MAG_FILTER; + let wrapS = gl.TEXTURE_WRAP_S; + let wrapT = gl.TEXTURE_WRAP_T; + + // bind a new texture if necessary + if (aProperties.texture) { + gl.bindTexture(gl.TEXTURE_2D, aProperties.texture.ref); + } + + // set the minification filter + if ("nearest" === aProperties.minFilter) { + gl.texParameteri(gl.TEXTURE_2D, minFilter, gl.NEAREST); + } else if ("linear" === aProperties.minFilter && aProperties.mipmap) { + gl.texParameteri(gl.TEXTURE_2D, minFilter, gl.LINEAR_MIPMAP_LINEAR); + } else { + gl.texParameteri(gl.TEXTURE_2D, minFilter, gl.LINEAR); + } + + // set the magnification filter + if ("nearest" === aProperties.magFilter) { + gl.texParameteri(gl.TEXTURE_2D, magFilter, gl.NEAREST); + } else { + gl.texParameteri(gl.TEXTURE_2D, magFilter, gl.LINEAR); + } + + // set the wrapping on the x-axis for the texture + if ("repeat" === aProperties.wrapS) { + gl.texParameteri(gl.TEXTURE_2D, wrapS, gl.REPEAT); + } else { + gl.texParameteri(gl.TEXTURE_2D, wrapS, gl.CLAMP_TO_EDGE); + } + + // set the wrapping on the y-axis for the texture + if ("repeat" === aProperties.wrapT) { + gl.texParameteri(gl.TEXTURE_2D, wrapT, gl.REPEAT); + } else { + gl.texParameteri(gl.TEXTURE_2D, wrapT, gl.CLAMP_TO_EDGE); + } + + // generate mipmap if necessary + if (aProperties.mipmap) { + gl.generateMipmap(gl.TEXTURE_2D); + } + }, + + /** + * This shim renders a content window to a canvas element, but clamps the + * maximum width and height of the canvas to the WebGL MAX_TEXTURE_SIZE. + * + * @param {Window} aContentWindow + * the content window to get a texture from + * @param {Number} aMaxImageSize + * the maximum image size to be used + * + * @return {Image} the new content window image + */ + createContentImage: function TGLTU_createContentImage( + aContentWindow, aMaxImageSize) + { + // calculate the total width and height of the content page + let size = TiltUtils.DOM.getContentWindowDimensions(aContentWindow); + + // use a custom canvas element and a 2d context to draw the window + let canvas = TiltUtils.DOM.initCanvas(null); + canvas.width = TiltMath.clamp(size.width, 0, aMaxImageSize); + canvas.height = TiltMath.clamp(size.height, 0, aMaxImageSize); + + // use the 2d context.drawWindow() magic + let ctx = canvas.getContext("2d"); + ctx.drawWindow(aContentWindow, 0, 0, canvas.width, canvas.height, "#fff"); + + return canvas; + }, + + /** + * Scales an image's width and height to next power of two. + * If the image already has power of two sizes, it is immediately returned, + * otherwise, a new image is created. + * + * @param {Image} aImage + * the image to be scaled + * @param {Object} aProperties + * an object containing the following properties: + * {Image} source: the source image to resize + * {Boolean} resize: true to resize the image if it has npot dimensions + * {String} fill: optional, color to fill the transparent bits + * {String} stroke: optional, color to draw an image outline + * {Number} strokeWeight: optional, the width of the outline + * + * @return {Image} the resized image + */ + resizeImageToPowerOfTwo: function TGLTU_resizeImageToPowerOfTwo(aProperties) + { + // make sure the properties argument is an object + aProperties = aProperties || {}; + + if (!aProperties.source) { + return null; + } + + let isPowerOfTwoWidth = TiltMath.isPowerOfTwo(aProperties.source.width); + let isPowerOfTwoHeight = TiltMath.isPowerOfTwo(aProperties.source.height); + + // first check if the image is not already power of two + if (!aProperties.resize || (isPowerOfTwoWidth && isPowerOfTwoHeight)) { + return aProperties.source; + } + + // calculate the power of two dimensions for the npot image + let width = TiltMath.nextPowerOfTwo(aProperties.source.width); + let height = TiltMath.nextPowerOfTwo(aProperties.source.height); + + // create a canvas, then we will use a 2d context to scale the image + let canvas = TiltUtils.DOM.initCanvas(null, { + width: width, + height: height + }); + + let ctx = canvas.getContext("2d"); + + // optional fill (useful when handling transparent images) + if (aProperties.fill) { + ctx.fillStyle = aProperties.fill; + ctx.fillRect(0, 0, width, height); + } + + // draw the image with power of two dimensions + ctx.drawImage(aProperties.source, 0, 0, width, height); + + // optional stroke (useful when creating textures for edges) + if (aProperties.stroke) { + ctx.strokeStyle = aProperties.stroke; + ctx.lineWidth = aProperties.strokeWeight; + ctx.strokeRect(0, 0, width, height); + } + + return canvas; + }, + + /** + * The total number of textures created. + */ + _count: 0 +}; + +/** + * A color shader. The only useful thing it does is set the gl_FragColor. + * + * @param {Attribute} vertexPosition: the vertex position + * @param {Uniform} mvMatrix: the model view matrix + * @param {Uniform} projMatrix: the projection matrix + * @param {Uniform} color: the color to set the gl_FragColor to + */ +TiltGL.ColorShader = { + + /** + * Vertex shader. + */ + vs: [ + "attribute vec3 vertexPosition;", + + "uniform mat4 mvMatrix;", + "uniform mat4 projMatrix;", + + "void main() {", + " gl_Position = projMatrix * mvMatrix * vec4(vertexPosition, 1.0);", + "}" + ].join("\n"), + + /** + * Fragment shader. + */ + fs: [ + "#ifdef GL_ES", + "precision lowp float;", + "#endif", + + "uniform vec4 fill;", + + "void main() {", + " gl_FragColor = fill;", + "}" + ].join("\n") +}; + +TiltGL.isWebGLForceEnabled = function TGL_isWebGLForceEnabled() +{ + return Services.prefs.getBoolPref("webgl.force-enabled"); +}; + +/** + * Tests if the WebGL OpenGL or Angle renderer is available using the + * GfxInfo service. + * + * @return {Boolean} true if WebGL is available + */ +TiltGL.isWebGLSupported = function TGL_isWebGLSupported() +{ + let supported = false; + + try { + let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); + let angle = gfxInfo.FEATURE_WEBGL_ANGLE; + let opengl = gfxInfo.FEATURE_WEBGL_OPENGL; + + // if either the Angle or OpenGL renderers are available, WebGL should work + supported = gfxInfo.getFeatureStatus(angle) === gfxInfo.FEATURE_NO_INFO || + gfxInfo.getFeatureStatus(opengl) === gfxInfo.FEATURE_NO_INFO; + } catch(e) { + if (e && e.message) { TiltUtils.Output.error(e.message); } + return false; + } + return supported; +}; + +/** + * Helper function to create a 3D context. + * + * @param {HTMLCanvasElement} aCanvas + * the canvas to get the WebGL context from + * @param {Object} aFlags + * optional, flags used for initialization + * + * @return {Object} the WebGL context, or null if anything failed + */ +TiltGL.create3DContext = function TGL_create3DContext(aCanvas, aFlags) +{ + TiltGL.clearCache(); + + // try to get a valid context from an existing canvas + let context = null; + + try { + context = aCanvas.getContext(WEBGL_CONTEXT_NAME, aFlags); + } catch(e) { + if (e && e.message) { TiltUtils.Output.error(e.message); } + return null; + } + return context; +}; + +/** + * Clears the cache and sets all the variables to default. + */ +TiltGL.clearCache = function TGL_clearCache() +{ + TiltGL.ProgramUtils._activeProgram = -1; + TiltGL.ProgramUtils._enabledAttributes = []; +};