API Documentation for: NEXT
Show:

File:WebGLInspector.js

/*
 * WebGLInspector
 * Visit http://createjs.com/ for documentation, updates and examples.
 *
 * Copyright (c) 2010 gskinner.com, inc.
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

/**
 * @module EaselJS
 */

this.createjs = this.createjs||{};

(function() {
	"use strict";

	/**
	 * A utility and helper class designed to work with {{#crossLink "StageGL"}}{{/crossLink}} to help investigate and
	 * test performance or display problems. It contains logging functions to analyze behaviour and performance testing
	 * utilities.
	 * @class WebGLInspector
	 * @constructor
	 * @param {StageGL} stage The default stage to use when none is supplied.
	 */
	function WebGLInspector(stage) {}
	var p = createjs.extend(WebGLInspector, createjs.EventDispatcher);

// properties:
	/**
	 * Alternate output for debugging situations where "console" is not available, i.e. Mobile or remote debugging.
	 * Expects object with a "log" function that takes any number of params.
	 * @property alternateOutput
	 * @type {Console}
	 * @default null
	 * @static
	 * @protected
	 */
	WebGLInspector.alternateOutput = undefined;

	/**
	 * Default stage to assume when non provided
	 * @type {StageGL}
	 * @private
	 */
	WebGLInspector.stage = undefined;

// public methods:
	/**
	 * Utility to call the right logging
	 * @params *
	 */
	WebGLInspector.log = function() {
		(WebGLInspector.alternateOutput ? WebGLInspector.alternateOutput.log : console.log).apply(this, arguments);
	};

	/**
	 * Perform all of the logging reports at once.
	 * @method log
	 * @param {StageGL} [stage=WebGLInspector.stage] The stage to log information for.
	 */
	WebGLInspector.logAll = function(stage) {
		if (!stage){ stage = WebGLInspector.stage; }

		WebGLInspector.log("Batches Per Draw", (stage._batchID/stage._drawID).toFixed(4));
		WebGLInspector.logContextInfo(stage._webGLContext);
		WebGLInspector.logDepth(stage.children, "");
		WebGLInspector.logTextureFill(stage);
	};

	/**
	 * Replace the stage's Draw command with a new draw command. This is useful for:
	 * <ul>
	 *     <li> Testing performance, with no render cost. See `WebGLInspector.drawEmpty` </li>
	 *     <li> Troubleshooting and tracking loaded textures. See `WebGLInspector.drawTexOnBuffer` </li>
	 *     <li> Misc feature or troubleshooting injection </li>
	 * </ul>
	 * @method replaceRenderBatchCall
	 * @param {StageGL} [stage=WebGLInspector.stage] The stage to log information for.
	 * @param {Function} newFunc .
	 */
	WebGLInspector.replaceRenderBatchCall = function(stage, newFunc) {
		if (!stage){ stage = WebGLInspector.stage; }

		if (newFunc === undefined && stage._renderBatch_) {
			stage._renderBatch = stage._renderBatch_;
			stage._renderBatch_ = undefined;
		} else {
			if (stage._renderBatch_ === undefined) {
				stage._renderBatch_ = stage._renderBatch;
			}
			stage._renderBatch = newFunc;
		}
	};

	/**
	 * Identical to replaceRenderBatchCall, but affects the Cover command.
	 * @method replaceRenderCoverCall
	 * @param {StageGL} [stage=WebGLInspector.stage] The stage to log information for.
	 * @param {Function} newFunc .
	 */
	WebGLInspector.replaceRenderCoverCall = function(stage, newFunc) {
		if (!stage){ stage = WebGLInspector.stage; }

		if (newFunc === undefined && stage._renderCover_) {
			stage._renderCover = stage._renderCover_;
			stage._renderCover_ = undefined;
		} else {
			if (stage._renderCover_ === undefined) {
				stage._renderCover_ = stage._renderCover;
			}
			stage._renderCover = newFunc;
		}
	};

	/**
	 * Recursively walk the entire display tree, log the attached items, and display it in a tree view.
	 * @method logDepth
	 * @param {Array} [children=WebGLInspector.stage.children] The children array to walk through.
	 * @param {String} prepend What to prepend to this output from this point onwards.
	 * @param {Function} customLog Which logging function to use, mainly for filtering or formatting output.
	 * Fallback hierarchy is customLog -> alternateOutput -> console.log.
	 */
	WebGLInspector.logDepth = function(children, prepend, customLog) {
		if (!children){ children = WebGLInspector.stage.children; }
		if (!prepend){ prepend = ""; }

		var l = children.length;
		for (var i=0; i<l; i++) {
			var child = children[i];
			(customLog !== undefined ? customLog : WebGLInspector.log)(prepend+"-", child);
			if (child.children && child.children.length) {
				WebGLInspector.logDepth(child.children, "|"+prepend, customLog);
			}
		}
	};

	/**
	 * Examine the context and provide information about its capabilities.
	 * @method logContextInfo
	 * @param {WebGLRenderingContext} gl The WebGL context to inspect.
	 */
	WebGLInspector.logContextInfo = function(gl) {
		if (!gl) { gl = WebGLInspector.stage._webGLContext; }
		var data = "== LOG:\n";
		data += "Max textures per draw: " + gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) +"\n";
		data += "Max textures active: " + gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS) +"\n";
		data += "\n";
		data += "Max texture size: " + (gl.getParameter(gl.MAX_TEXTURE_SIZE)/2) +"^2 \n";
		data += "Max cache size: " + (gl.getParameter(gl.MAX_RENDERBUFFER_SIZE)/2) +"^2 \n";
		data += "\n";
		data += "Max attributes per vertex: " + gl.getParameter(gl.MAX_VERTEX_ATTRIBS) +"\n";
		data += "WebGL Version string: " + gl.getParameter(gl.VERSION) +"\n";
		data += "======";
		WebGLInspector.log(data);
	};

	/**
	 * Simulate renders and watch what happens for textures moving around between draw calls. A texture moving between
	 * slots means it was removed and then re-added to draw calls. Performance may be better if it was allowed to stay
	 * on GPU, consider sprite sheeting it with something stable.
	 * @method logTextureFill
	 * @param {StageGL} [stage=WebGLInspector.stage] The stage to log information for.
	 */
	WebGLInspector.logTextureFill = function(stage) {
		if (!stage){ stage = WebGLInspector.stage; }

		var dict = stage._textureDictionary;
		var count = stage._batchTextureCount;
		WebGLInspector.log("textureMax:", count);
		var output = [];
		for (var n in dict) {
			var str = n.replace(window.location.origin, "");
			var tex = dict[n];
			var shifted = tex._lastActiveIndex?tex._lastActiveIndex === tex._activeIndex:false;
			output.push({src:str, element:tex, shifted:shifted});
			tex._lastActiveIndex = tex._activeIndex;
		}

		output.sort(function(a,b){
			if (a.element._drawID === stage._drawID) { return 1; }
			if (a.element._drawID < b.element._drawID) { return -1; }
			return 0;
		});

		var l = output.length;
		for (var i = 0; i<l; i++) {
			var out = output[i];
			var active = out.element._drawID === stage._drawID;
			WebGLInspector.log("["+out.src+"] "+ (active?"ACTIVE":"stale") +" "+ (out.shifted?"steady":"DRIFT"), out.element);
		}
	};

// protected methods:

// utility methods:
	/**
	 * Utility function for use with {{#crossLink "logDepth"))((/crossLink}}. Logs an item's position and registration.
	 * Useful to see if something is being forced off screen or has an integer position.
	 * @method dispProps
	 * @param {String} prepend The string to show before the item, usually formatting for a tree view.
	 * @param {DisplayObject} item The item we're currently logging about.
	 * @static
	 */
	WebGLInspector.dispProps = function(prepend, item){
		if (!prepend){ prepend = ""; }

		var p = "\tP:"+ item.x.toFixed(2)+"x"+item.y.toFixed(2) +"\t";
		var r = "\tR:"+ item.regX.toFixed(2)+"x"+item.regY.toFixed(2) +"\t";

		WebGLInspector.log(prepend, item.toString()+"\t", p,r);
	};

	/**
	 * Utility function for use with {{#crossLink "replaceRenderBatchCall"))((/crossLink}}.
	 * Performs no GL draw command.
	 */
	WebGLInspector.drawEmptyBatch = function() {
		WebGLInspector.log("BlankBatch["+ this._drawID +":"+ this._batchID +"] : "+ this.batchReason);
		this._batchVertexCount = 0;
		this._batchID++;
	};

	/**
	 * Utility function for use with {{#crossLink "replaceRenderCoverCall"))((/crossLink}}.
	 * Performs no GL draw command.
	 */
	WebGLInspector.drawEmptyCover = function() {
		WebGLInspector.log("BlankCover["+ this._drawID +":"+ this._batchID +"] : "+ this.batchReason);
		this._batchID++;
	};

	/**
	 * Utility function for use with {{#crossLink "replaceRenderBatchCall"))((/crossLink}}.
	 */
	WebGLInspector.drawTexBuffer = function() {
		var gl = this._webGLContext;
		var texSize = 2048;

		// backup
		var batchVertexCount = this._batchVertexCount;
		var projectionMatrix = this._projectionMatrix;
		var shader = this._activeShader;
		var vertices = this._vertices;
		var indices = this._indices;
		var uvs = this._uvs;
		var alphas = this._alphas;
		var reason = this.batchReason;

		// create
		if (this._inspectorFrame === undefined) {
			this._inspectorFrame = this.getRenderBufferTexture(texSize, texSize);
		} else {
			gl.bindFramebuffer(gl.FRAMEBUFFER, this._inspectorFrame._frameBuffer);
			gl.clear(gl.COLOR_BUFFER_BIT);
		}

		// configure
		this._activeShader = this._mainShader;
		gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);
		gl.blendFuncSeparate(gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
		gl.viewport(0, 0, texSize, texSize);

		this._projectionMatrix = new Float32Array([2/texSize, 0, 0, 0, 0, -2/texSize, 0, 0, 0, 0, 1, 0, -1, 1, 0, 1]);
		this._vertices = new Float32Array(this._batchTextureCount * 2 * createjs.StageGL.INDICIES_PER_CARD);
		this._indices = new Float32Array(this._batchTextureCount * 1 * createjs.StageGL.INDICIES_PER_CARD);
		this._uvs = new Float32Array(this._batchTextureCount * 2 * createjs.StageGL.INDICIES_PER_CARD);
		this._alphas = new Float32Array(this._batchTextureCount * 1 * createjs.StageGL.INDICIES_PER_CARD);
		this.batchReason = "LoadedTextureDebug";

		var squareBase = Math.ceil(Math.sqrt(this._batchTextureCount));
		for(var i=0; i<this._batchTextureCount; i++) {
			var i1 = i*6, i2 = i1*2;
			var row = i % squareBase, col = Math.floor(i / squareBase), size = (1/squareBase) * texSize;
			this._vertices[i2] =	(row)*size;					this._vertices[i2+1] =	(col)*size;
			this._vertices[i2+2] =	(row)*size;					this._vertices[i2+3] =	(col+1)*size;
			this._vertices[i2+4] =	(row+1)*size;				this._vertices[i2+5] =	(col)*size;
			this._vertices[i2+6] =	this._vertices[i2+2];		this._vertices[i2+7] =	this._vertices[i2+3];
			this._vertices[i2+8] =	this._vertices[i2+4];		this._vertices[i2+9] =	this._vertices[i2+5];
			this._vertices[i2+10] =	(row+1)*size;				this._vertices[i2+11] =	(col+1)*size;
			this._uvs[i2] =		0;			this._uvs[i2+1] =	1;
			this._uvs[i2+2] =	0;			this._uvs[i2+3] =	0;
			this._uvs[i2+4] =	1;			this._uvs[i2+5] =	1;
			this._uvs[i2+6] =	0;			this._uvs[i2+7] =	0;
			this._uvs[i2+8] =	1;			this._uvs[i2+9] =	1;
			this._uvs[i2+10] =	1;			this._uvs[i2+11] =	0;
			this._indices[i1] = this._indices[i1+1] = this._indices[i1+2] = this._indices[i1+3] = this._indices[i1+4] = this._indices[i1+5] = i;
			this._alphas[i1] = this._alphas[i1+1] = this._alphas[i1+2] = this._alphas[i1+3] = this._alphas[i1+4] = this._alphas[i1+5] = 1;
		}

		// output
		this._batchVertexCount = this._batchTextureCount * createjs.StageGL.INDICIES_PER_CARD;
		this._renderBatch_();
		this._batchID--;

		// reset and perform
		gl.bindFramebuffer(gl.FRAMEBUFFER, this._batchTextureOutput._frameBuffer);

		var shaderData = this._builtShaders[this._renderMode];
		gl.blendEquationSeparate(shaderData.eqRGB, shaderData.eqA);
		gl.blendFuncSeparate(shaderData.srcRGB, shaderData.dstRGB, shaderData.srcA, shaderData.dstA);
		gl.viewport(0, 0, this._viewportWidth, this._viewportHeight);

		this._activeShader = shader;
		this._batchVertexCount = batchVertexCount;
		this._projectionMatrix = projectionMatrix;
		this._vertices = vertices;
		this._indices = indices;
		this._uvs = uvs;
		this._alphas = alphas;
		this.batchReason = reason;

		this._renderBatch_();
	};

	createjs.WebGLInspector = createjs.promote(WebGLInspector, "EventDispatcher");
}());