/* * Copyright 2009, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * @fileoverview This file contains a class for displaying frames per second. */ o3djs.provide('o3djs.fps'); o3djs.require('o3djs.rendergraph'); o3djs.require('o3djs.canvas'); o3djs.require('o3djs.math'); o3djs.require('o3djs.primitives'); /** * A Module with a fps class for helping to easily display frames per second. * @namespace */ o3djs.fps = o3djs.fps || {}; /** * Number of frames to average over for computing FPS. * @type {number} */ o3djs.fps.NUM_FRAMES_TO_AVERAGE = 16; /** * Colors used for each second of the performance bar. * @type {!Array.} */ o3djs.fps.PERF_BAR_COLORS = [ [0, 0, 1, 1], [0, 1, 0, 1], [1, 1, 0, 1], [1, 0.5, 0, 1], [1, 0, 0, 1]]; /** * The shader code used by the pref quads. * @type {string} */ o3djs.fps.CONST_COLOR_EFFECT = 'float4x4 worldViewProjection : WorldViewProjection;\n' + 'float4 color;\n' + 'struct a2v {\n' + ' float4 pos : POSITION;\n' + '};\n'+ 'struct v2f {\n' + ' float4 pos : POSITION;\n' + '};\n' + 'v2f vsMain(a2v IN) {\n' + ' v2f OUT;\n' + ' OUT.pos = mul(IN.pos, worldViewProjection);\n' + ' return OUT;\n' + '}\n' + 'float4 psMain(v2f IN): COLOR {\n' + ' return color;\n' + '}\n' + '// #o3d VertexShaderEntryPoint vsMain\n' + '// #o3d PixelShaderEntryPoint psMain\n' + '// #o3d MatrixLoadOrder RowMajor\n'; /** * Creates an object for displaying frames per second. * * You can use it like this. *
 * <html><body>
 * <script type="text/javascript" src="o3djs/base.js">
 * </script>
 * <script type="text/javascript">
 * o3djs.require('o3djs.util');
 * o3djs.require('o3djs.rendergraph');
 * o3djs.require('o3djs.fps');
 * window.onload = init;
 * window.onunload = uninit;
 *
 * var g_client;
 * var g_fpsManager;
 *
 * function init() {
 *   o3djs.base.makeClients(initStep2);
 * }
 *
 * function initStep2(clientElements) {
 *   var clientElement = clientElements[0];
 *   var g_client = clientElement.client;
 *   var pack = g_client.createPack();
 *   var viewInfo = o3djs.rendergraph.createBasicView(
 *       pack,
 *       g_client.root,
 *       g_client.renderGraphRoot);
 *   g_fpsManager = o3djs.fps.createFPSManager(pack,
 *                                             g_client.width,
 *                                             g_client.height,
 *                                             g_client.renderGraphRoot);
 *   g_client.setRenderCallback(onRender);
 * }
 *
 * function onrender(renderEvent) {
 *   g_fpsManager.update(renderEvent);
 * }
 *
 * function uninit() {
 *   if (g_client) {
 *     g_client.cleanup();
 *   }
 * }
 * </script>
 * <div id="o3d" style="width: 600px; height: 600px"></div>
 * </body></html>
 * 
* * @param {!o3d.Pack} pack Pack to create objects in. * @param {number} clientWidth width of client area. * @param {number} clientHeight Height of client area. * @param {!o3d.RenderNode} opt_parent RenderNode to use as parent for * ViewInfo that will be used to render the FPS with. * @return {!o3djs.fps.FPSManager} The created FPSManager. */ o3djs.fps.createFPSManager = function(pack, clientWidth, clientHeight, opt_parent) { return new o3djs.fps.FPSManager(pack, clientWidth, clientHeight, opt_parent); }; /** * A class for displaying frames per second. * @constructor * @param {!o3d.Pack} pack Pack to create objects in. * @param {number} clientWidth width of client area. * @param {number} clientHeight Height of client area. * @param {!o3d.RenderNode} opt_parent RenderNode to use as parent for * ViewInfo that will be used to render the FPS with. */ o3djs.fps.FPSManager = function(pack, clientWidth, clientHeight, opt_parent) { // total time spent for last N frames. this.totalTime_ = 0.0; // total active time for last N frames. this.totalActiveTime_ = 0.0; // elapsed time for last N frames. this.timeTable_ = []; // active time for last N frames. this.activeTimeTable_ = []; // where to record next elapsed time. this.timeTableCursor_ = 0; // Initialize the FPS elapsed time history table. for (var tt = 0; tt < o3djs.fps.NUM_FRAMES_TO_AVERAGE; ++tt) { this.timeTable_[tt] = 0.0; this.activeTimeTable_[tt] = 0.0; } // The root transform for this sub graph. this.root_ = pack.createObject('Transform'); /** * The ViewInfo to display FPS. * @type {!o3djs.rendergraph.ViewInfo} */ this.viewInfo = o3djs.rendergraph.createBasicView(pack, this.root_, opt_parent); this.viewInfo.root.priority = 100000; this.viewInfo.clearBuffer.clearColorFlag = false; this.viewInfo.zOrderedState.getStateParam('CullMode').value = o3djs.base.o3d.State.CULL_NONE; this.viewInfo.drawContext.view = o3djs.math.matrix4.lookAt( [0, 0, 1], // eye [0, 0, 0], // target [0, 1, 0]); // up // create a view just for the FPS. That way it's indepdendent of other views. this.canvasLib_ = o3djs.canvas.create(pack, this.root_, this.viewInfo); this.paint_ = pack.createObject('CanvasPaint'); /** * The quad used to display the FPS. * */ this.fpsQuad = this.canvasLib_.createXYQuad(0, 0, -1, 64, 32, true); // create a unit plane with a const color effect we can use to draw // rectangles. this.colorEffect_ = pack.createObject('Effect'); this.colorEffect_.loadFromFXString(o3djs.fps.CONST_COLOR_EFFECT); this.colorMaterial_ = pack.createObject('Material'); this.colorMaterial_.effect = this.colorEffect_; this.colorMaterial_.drawList = this.viewInfo.zOrderedDrawList; this.colorEffect_.createUniformParameters(this.colorMaterial_); this.colorMaterial_.getParam('color').value = [1, 1, 1, 1]; this.colorQuadShape_ = o3djs.primitives.createPlane( pack, this.colorMaterial_, 1, 1, 1, 1, [[1, 0, 0, 0], [0, 0, 1, 0], [0, -1, 0, 0], [0.5, 0.5, 0, 1]]); var barXOffset = 10; var barYOffset = 2; var barWidth = clientWidth - barXOffset * 2; var barHeight = 7; this.numPerfBars_ = o3djs.fps.PERF_BAR_COLORS.length - 1; this.perfBarRoot_ = pack.createObject('Transform'); this.perfBarRoot_.parent = this.root_; this.perfBarBack_ = new o3djs.fps.ColorRect( pack, this.colorQuadShape_, this.perfBarRoot_, barXOffset, barYOffset, -3, barWidth, barHeight, [0, 0, 0, 1]); this.perfMarker_ = []; for (var ii = 0; ii < this.numPerfBars_; ++ii) { this.perfMarker_[ii] = new o3djs.fps.ColorRect( pack, this.colorQuadShape_, this.perfBarRoot_, barXOffset + barWidth / (this.numPerfBars_ + 1) * (ii + 1), barYOffset - 1, -1, 1, barHeight + 2, [1, 1, 1, 1]); } this.perfBar_ = new o3djs.fps.ColorRect( pack, this.colorQuadShape_, this.perfBarRoot_, barXOffset + 1, barYOffset + 1, -2, 1, barHeight - 2, [1, 1, 0, 1]); this.perfBarWidth_ = barWidth - 2; this.perfBarHeight_ = barHeight - 2; this.perfBarXOffset_ = barXOffset; this.perfBarYOffset_ = barYOffset; // set the size and position. this.resize(clientWidth, clientHeight); this.setPosition(10, 10); }; /** * Sets the position of the FPS display * * The position is in pixels assuming the size of the client matches the size * last set either on creation or with FPSManager.resize. * * @param {number} x The x position. * @param {number} y The y position. */ o3djs.fps.FPSManager.prototype.setPosition = function(x, y) { this.fpsQuad.transform.identity(); this.fpsQuad.transform.translate(x, y, -1); }; /** * Sets the visiblity of the fps display. * @param {boolean} visible true = visible. */ o3djs.fps.FPSManager.prototype.setVisible = function(visible) { this.viewInfo.root.active = visible; }; /** * Sets the visibility of the performance bar. * @param {boolean} visible true = visible. */ o3djs.fps.FPSManager.prototype.setPerfVisible = function(visible) { this.perfBarRoot_.visible = visible; }; /** * Resizes the area for the FPS display. * Note: you must call this if your client area changes size. * @param {number} clientWidth width of client area. * @param {number} clientHeight height of client area. */ o3djs.fps.FPSManager.prototype.resize = function(clientWidth, clientHeight) { this.viewInfo.drawContext.projection = o3djs.math.matrix4.orthographic( 0 + 0.5, clientWidth + 0.5, clientHeight + 0.5, 0 + 0.5, 0.001, 1000); var barWidth = clientWidth - this.perfBarXOffset_ * 2; this.perfBarBack_.setSize(barWidth, this.perfBarHeight_); for (var ii = 0; ii < this.numPerfBars_; ++ii) { this.perfMarker_[ii].setPosition( this.perfBarXOffset_ + barWidth / (this.numPerfBars_ + 1) * (ii + 1), this.perfBarYOffset_ - 1); } this.perfBarWidth_ = barWidth - 2; }; /** * Updates the fps display. * You must call this every frame to update the FPS display. * *
 * ...
 * client.setRenderCallback(onRender);
 * ...
 * function onRender(renderEvent) {
 *   myFpsManager.update(renderEvent);
 * }
 * 
* * @param {!o3d.RenderEvent} renderEvent The object passed into the render * callback. */ o3djs.fps.FPSManager.prototype.update = function(renderEvent) { var elapsedTime = renderEvent.elapsedTime; var activeTime = renderEvent.activeTime; // Keep the total time and total active time for the last N frames. this.totalTime_ += elapsedTime - this.timeTable_[this.timeTableCursor_]; this.totalActiveTime_ += activeTime - this.activeTimeTable_[this.timeTableCursor_]; // Save off the elapsed time for this frame so we can subtract it later. this.timeTable_[this.timeTableCursor_] = elapsedTime; this.activeTimeTable_[this.timeTableCursor_] = activeTime; // Wrap the place to store the next time sample. ++this.timeTableCursor_; if (this.timeTableCursor_ == o3djs.fps.NUM_FRAMES_TO_AVERAGE) { this.timeTableCursor_ = 0; } // Print the average frame rate for the last N frames as well as the // instantaneous frame rate. var fps = '' + Math.floor((1.0 / (this.totalTime_ / o3djs.fps.NUM_FRAMES_TO_AVERAGE)) + 0.5) + ' : ' + Math.floor(1.0 / elapsedTime + 0.5); var canvas = this.fpsQuad.canvas; canvas.clear([0, 0, 0, 0]); var paint = this.paint_; canvas.saveMatrix(); paint.setOutline(3, [0, 0, 0, 1]); paint.textAlign = o3djs.base.o3d.CanvasPaint.LEFT; paint.textSize = 12; paint.textTypeface = 'Arial'; paint.color = [1, 1, 0, 1]; canvas.drawText(fps, 2, 16, paint); canvas.restoreMatrix(); this.fpsQuad.updateTexture(); var frames = this.totalActiveTime_ / o3djs.fps.NUM_FRAMES_TO_AVERAGE / (1 / 60.0); var colorIndex = Math.min(frames, o3djs.fps.PERF_BAR_COLORS.length - 1); colorIndex = Math.floor(Math.max(colorIndex, 0)); if (!isNaN(colorIndex)) { this.perfBar_.setColor(o3djs.fps.PERF_BAR_COLORS[colorIndex]); this.perfBar_.setSize(frames * this.perfBarWidth_ / this.numPerfBars_, this.perfBarHeight_); } }; /** * A Class the manages a color rect. * @constructor * @param {!o3d.Pack} pack Pack to create things in. * @param {!o3d.Shape} shape Shape to use for rectangle. * @param {!o3d.Transform} parent Transform to parent rect under. * @param {number} x initial x position. * @param {number} y initial y position. * @param {number} z initial z position. * @param {number} width initial width. * @param {number} height initial height. * @param {!o3djs.math.Vector4} color initial color. */ o3djs.fps.ColorRect = function(pack, shape, parent, x, y, z, width, height, color) { this.transform_ = pack.createObject('Transform'); this.colorParam_ = this.transform_.createParam('color', 'ParamFloat4'); this.transform_.addShape(shape); this.transform_.parent = parent; this.width_ = 0; this.height_ = 0; this.x_ = 0; this.y_ = 0; this.z_ = z; this.setPosition(x, y); this.setSize(width, height); this.setColor(color); }; /** * Updates the transform of this ColorRect * @private */ o3djs.fps.ColorRect.prototype.updateTransform_ = function() { this.transform_.identity(); this.transform_.translate(this.x_, this.y_, this.z_); this.transform_.scale(this.width_, this.height_, 1); }; /** * Sets the position of this ColorRect. * @param {number} x x position. * @param {number} y y position. */ o3djs.fps.ColorRect.prototype.setPosition = function(x, y) { this.x_ = x; this.y_ = y; this.updateTransform_(); }; /** * Sets the size of this ColorRect * @param {number} width width. * @param {number} height height. */ o3djs.fps.ColorRect.prototype.setSize = function(width, height) { this.width_ = width; this.height_ = height; this.updateTransform_(); }; /** * Sets the color of this ColorRect. * @param {!o3djs.math.Vector4} color initial color. */ o3djs.fps.ColorRect.prototype.setColor = function(color) { this.colorParam_.value = color; };