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