From 117366a1d4d1d64322d021e444a2efa0e61ca5b9 Mon Sep 17 00:00:00 2001 From: "ericu@google.com" Date: Thu, 2 Jul 2009 00:54:37 +0000 Subject: Review URL: http://codereview.chromium.org/147258 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@19795 0039d316-1c4b-4281-b951-d872f2087c98 --- o3d/plugin/idl/pack.idl | 2 +- o3d/samples/beachdemo/beachdemo.js | 130 +++++++++++++++++++------ o3d/samples/o3djs/js_list.scons | 1 + o3d/samples/o3djs/performance.js | 195 +++++++++++++++++++++++++++++++++++++ 4 files changed, 298 insertions(+), 30 deletions(-) create mode 100644 o3d/samples/o3djs/performance.js diff --git a/o3d/plugin/idl/pack.idl b/o3d/plugin/idl/pack.idl index 99b4863..d52424a 100644 --- a/o3d/plugin/idl/pack.idl +++ b/o3d/plugin/idl/pack.idl @@ -64,7 +64,7 @@ typedef ObjectBase[] ObjectArray; \endcode The only difference is that after all the objects are removed the pack - itself will be released from the client. See documenation on + itself will be released from the client. See documentation on pack.removeObject for why this is important. It's important to note that many objects are only referenced by the pack. diff --git a/o3d/samples/beachdemo/beachdemo.js b/o3d/samples/beachdemo/beachdemo.js index 8bf52be..69bd455 100644 --- a/o3d/samples/beachdemo/beachdemo.js +++ b/o3d/samples/beachdemo/beachdemo.js @@ -49,9 +49,8 @@ o3djs.require('o3djs.canvas'); o3djs.require('o3djs.fps'); o3djs.require('o3djs.debug'); o3djs.require('o3djs.particles'); +o3djs.require('o3djs.performance'); -var RENDER_TARGET_WIDTH = 256; -var RENDER_TARGET_HEIGHT = 256; var PROXY_HEIGHT = 5150; // client.root @@ -128,6 +127,9 @@ var g_skyDomeTransform; var g_waterTransform; var g_reflectionTexture; var g_refractionTexture; +var g_reflectionImage; +var g_refrectionImage; +var g_depthSurface; var g_globalParams; var g_globalClockParam; var g_clipHeightParam; @@ -180,6 +182,9 @@ var g_downloadPercent = -1; var g_showError = false; var g_sceneEffects = []; var g_sceneTexturesByURI; +var g_renderTargetWidth = 256; +var g_renderTargetHeight = 256; +var g_perfMon; var g_camera = { farPlane: 80000, @@ -1541,6 +1546,8 @@ function loadMainScene() { } } } + g_perfMon = o3djs.performance.createPerformanceMonitor( + 25, 35, increaseRenderTargetResolution, decreaseRenderTargetResolution); } try { @@ -1815,6 +1822,10 @@ function onRender(renderEvent) { // Render the FPS display. g_client.renderTree(g_fpsManager.viewInfo.root); + + if (g_perfMon) { + g_perfMon.onRender(renderEvent.elapsedTime); + } } function onAllLoadingFinished() { @@ -1843,6 +1854,82 @@ function init() { o3djs.util.makeClients(initStep2); } +function setupRenderTargets() { + var oldReflectionTexture; + var oldRefractionTexture; + var oldDepthSurface; + + if (g_reflectionTexture) { + g_mainPack.removeObject(g_reflectionSurfaceSet.renderSurface); + g_mainPack.removeObject(g_refractionSurfaceSet.renderSurface); + g_mainPack.removeObject(g_reflectionTexture); + g_mainPack.removeObject(g_refractionTexture); + g_mainPack.removeObject(g_depthSurface); + } else { + // First time only. + g_reflectionSurfaceSet = g_mainPack.createObject('RenderSurfaceSet'); + g_refractionSurfaceSet = g_mainPack.createObject('RenderSurfaceSet'); + } + + // Create Render Targets for the reflection and refraction. + g_reflectionTexture = g_mainPack.createTexture2D(g_renderTargetWidth, + g_renderTargetHeight, + g_o3d.Texture.ARGB8, 1, + true); + var reflectionSurface = g_reflectionTexture.getRenderSurface(0, g_mainPack); + g_refractionTexture = g_mainPack.createTexture2D(g_renderTargetWidth, + g_renderTargetHeight, + g_o3d.Texture.XRGB8, 1, + true); + var refractionSurface = g_refractionTexture.getRenderSurface(0, g_mainPack); + g_depthSurface = g_mainPack.createDepthStencilSurface(g_renderTargetWidth, + g_renderTargetHeight); + + // Set up the render graph to generate them. + g_reflectionSurfaceSet.renderSurface = reflectionSurface; + g_reflectionSurfaceSet.renderDepthStencilSurface = g_depthSurface; + + g_refractionSurfaceSet.renderSurface = refractionSurface; + g_refractionSurfaceSet.renderDepthStencilSurface = g_depthSurface; + + g_updateRenderTargets = true; + + if (g_waterMaterial) { // Every time after the first. + var sampler = g_waterMaterial.getParam('reflectionSampler').value; + sampler.texture = g_reflectionTexture; + sampler = g_waterMaterial.getParam('refractionSampler').value; + sampler.texture = g_refractionTexture; + g_reflectionImage.sampler.texture = g_reflectionTexture; + g_refractionImage.sampler.texture = g_refractionTexture; + } +} + +function increaseRenderTargetResolution() { + var changed; + if (g_renderTargetWidth < 2048) { + g_renderTargetWidth <<= 1; + changed = true; + } + if (g_renderTargetHeight < 2048) { + g_renderTargetHeight <<= 1; + changed = true; + } + setupRenderTargets(); +} + +function decreaseRenderTargetResolution() { + var changed; + if (g_renderTargetWidth > 256) { + g_renderTargetWidth >>= 1; + changed = true; + } + if (g_renderTargetHeight > 256) { + g_renderTargetHeight >>= 1; + changed = true; + } + setupRenderTargets(); +} + /** * Initializes O3D and loads the scene into the transform graph. * @param {Array} clientElements Array of o3d object elements. @@ -1865,20 +1952,6 @@ function initStep2(clientElements) { g_mainPack = g_client.createPack(); g_scenePack = g_client.createPack(); - // Create Render Targets for the reflection and refraction. - g_reflectionTexture = g_mainPack.createTexture2D(RENDER_TARGET_WIDTH, - RENDER_TARGET_HEIGHT, - g_o3d.Texture.ARGB8, 1, - true); - var reflectionSurface = g_reflectionTexture.getRenderSurface(0, g_mainPack); - g_refractionTexture = g_mainPack.createTexture2D(RENDER_TARGET_WIDTH, - RENDER_TARGET_HEIGHT, - g_o3d.Texture.XRGB8, 1, - true); - var refractionSurface = g_refractionTexture.getRenderSurface(0, g_mainPack); - var depthSurface = g_mainPack.createDepthStencilSurface(RENDER_TARGET_WIDTH, - RENDER_TARGET_HEIGHT); - g_mainRoot = g_mainPack.createObject('Transform'); g_baseRoot = g_scenePack.createObject('Transform'); g_baseRoot.parent = g_mainRoot; @@ -1887,14 +1960,7 @@ function initStep2(clientElements) { g_mainRoot.parent = g_client.root; g_sceneRoot.translate(0, 0, -g_waterLevel); - // Setup the render graph to generate them. - g_reflectionSurfaceSet = g_mainPack.createObject('RenderSurfaceSet'); - g_reflectionSurfaceSet.renderSurface = reflectionSurface; - g_reflectionSurfaceSet.renderDepthStencilSurface = depthSurface; - - g_refractionSurfaceSet = g_mainPack.createObject('RenderSurfaceSet'); - g_refractionSurfaceSet.renderSurface = refractionSurface; - g_refractionSurfaceSet.renderDepthStencilSurface = depthSurface; + setupRenderTargets(); // Create states to set clipping. g_reflectionClipState = g_mainPack.createObject('State'); @@ -2540,22 +2606,28 @@ function setupHud() { // Make images to show the render targets. for (var ii = 0; ii < 2; ++ii) { + var textureDisplaySquareSize = 256; var renderTargetTexture = (ii == 0) ? g_reflectionTexture : g_refractionTexture; - var scale = 1; var x = 10; - var y = 10 + ii * (g_reflectionTexture.height * scale + 10); + var y = 10 + ii * (textureDisplaySquareSize + 10); var borderSize = 2; var image; // make a back image to create a border around render target. image = new Image(g_renderTargetDisplayRoot, backTexture, true); image.transform.translate(x - borderSize, y - borderSize, -3); - image.transform.scale(renderTargetTexture.width * scale + borderSize * 2, - renderTargetTexture.height * scale + borderSize * 2, + image.transform.scale(textureDisplaySquareSize + borderSize * 2, + textureDisplaySquareSize + borderSize * 2, 1); image = new Image(g_renderTargetDisplayRoot, renderTargetTexture, true); image.transform.translate(x, y, -2); - image.transform.scale(scale, scale, 1); + image.transform.scale(textureDisplaySquareSize / g_renderTargetWidth, + textureDisplaySquareSize / g_renderTargetHeight, 1); + if (ii == 0) { + g_reflectionImage = image; + } else { + g_refractionImage = image; + } } // Make a fader plane. diff --git a/o3d/samples/o3djs/js_list.scons b/o3d/samples/o3djs/js_list.scons index 5a0da33..f590330 100644 --- a/o3d/samples/o3djs/js_list.scons +++ b/o3d/samples/o3djs/js_list.scons @@ -45,6 +45,7 @@ O3D_JS_SOURCES = [ 'math.js', 'pack.js', 'particles.js', + 'performance.js', 'picking.js', 'primitives.js', 'quaternions.js', diff --git a/o3d/samples/o3djs/performance.js b/o3d/samples/o3djs/performance.js new file mode 100644 index 0000000..055bf2e --- /dev/null +++ b/o3d/samples/o3djs/performance.js @@ -0,0 +1,195 @@ +/* + * 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 utility that helps adjust rendering + * quality [or any other setting, really] based on rendering performance. + * + */ + +o3djs.provide('o3djs.performance'); + +/** + * Creates a utility that monitors performance [in terms of FPS] and helps to + * adjust the rendered scene accordingly. + * @param {number} targetFPSMin the minimum acceptable frame rate; if we're + * under this, try to decrease quality to improve performance. + * @param {number} targetFPSMax if we're over this, try to increase quality. + * @param {!function(): void} increaseQuality a function to increase + * quality because we're rendering at high-enough FPS to afford it. + * @param {!function(): void} decreaseQuality a function to decrease + * quality to try to raise our rendering speed. + * @param {!o3djs.performance.PerformanceMonitor.Options} opt_options Options. + * @return {!o3djs.performance.PerformanceMonitor} The created + * PerformanceMonitor. + */ +o3djs.performance.createPerformanceMonitor = function( + targetFPSMin, targetFPSMax, increaseQuality, decreaseQuality, opt_options) { + return new o3djs.performance.PerformanceMonitor(targetFPSMin, targetFPSMax, + increaseQuality, decreaseQuality, opt_options); +} + +/** + * A class that monitors performance [in terms of FPS] and helps to adjust the + * rendered scene accordingly. + * @constructor + * @param {number} targetFPSMin the minimum acceptable frame rate; if we're + * under this, try to decrease quality to improve performance. + * @param {number} targetFPSMax if we're over this, try to increase quality. + * @param {function(): void} increaseQuality a function to increase + * quality/lower FPS. + * @param {function(): void} decreaseQuality a function to decrease + * quality/raise FPS. + * @param {!o3djs.performance.PerformanceMonitor.Options} opt_options Options. + */ +o3djs.performance.PerformanceMonitor = function( + targetFPSMin, targetFPSMax, increaseQuality, decreaseQuality, opt_options) { + opt_options = opt_options || {}; + + /** + * A function to increase quality/lower FPS. + * @type {function(): void} + */ + this.increaseQuality = increaseQuality; + + /** + * A function to decrease quality/raise FPS. + * @type {function(): void} + */ + this.decreaseQuality = decreaseQuality; + + /** + * The mean time taken per frame so far, in seconds. This is only valid once + * we've collected at least minSamples samples. + * @type {number} + */ + this.meanFrameTime = 0; + + /** + * The number of samples we've collected so far, when that number is less than + * or equal to this.damping. After that point, we no longer update + * this.sampleCount, so it will clip at this.damping. + * + * @type {number} + */ + this.sampleCount = 0; + + /** + * The minimum number of samples to collect before trying to adjust quality. + * + * @type {number} + */ + this.minSamples = opt_options.opt_minSamples || 60; + + /** + * A number that controls the rate at which the effects of any given sample + * fade away. Higher is slower, but also means that each individual sample + * counts for less at its most-influential. Damping defaults to 120; anywhere + * between 60 and 600 are probably reasonable values, depending on your needs, + * but the number must be no less than minSamples. + * + * @type {number} + */ + this.damping = opt_options.opt_damping || 120; + + /** + * The minimum number of samples to take in between adjustments, to cut down + * on overshoot. It defaults to 2 * minSamples. + * + * @type {number} + */ + this.delayCycles = opt_options.opt_delayCycles || 2 * this.minSamples; + + this.targetFrameTimeMax_ = 1 / targetFPSMin; + this.targetFrameTimeMin_ = 1 / targetFPSMax; + this.scaleInput_ = 1 / this.minSamples; + this.scaleMean_ = 1; + this.delayCyclesLeft_ = 0; + if (this.damping < this.minSamples) { + throw Error('Damping must be at least minSamples.'); + } +} + +/** + * Options for the PerformanceMonitor. + * + * opt_minSamples is the minimum number of samples to take before making any + * performance adjustments. + * opt_damping is a number that controls the rate at which the effects of any + * given sample fade away. Higher is slower, but also means that each + * individual sample counts for less at its most-influential. Damping + * defaults to 120; anywhere between 60 and 600 are probably reasonable values, + * depending on your needs, but the number must be no less than minSamples. + * opt_delayCycles is the minimum number of samples to take in between + * adjustments, to cut down on overshoot. It defaults to 2 * opt_minSamples. + * + * @type {{ + * opt_minSamples: number, + * opt_damping: number, + * opt_delayCycles, number + * }} + */ +o3djs.performance.PerformanceMonitor.Options = goog.typedef; + +/** + * Call this once per frame with the elapsed time since the last call, and it + * will attempt to adjust your rendering quality as needed. + * + * @param {number} seconds the elapsed time since the last frame was rendered, + * in seconds. + */ +o3djs.performance.PerformanceMonitor.prototype.onRender = function(seconds) { + var test = true; + if (this.sampleCount < this.damping) { + if (this.sampleCount >= this.minSamples) { + this.scaleInput_ = 1 / (this.sampleCount + 1); + this.scaleMean_ = this.sampleCount * this.scaleInput_; + } else { + test = false; + } + this.sampleCount += 1; + } + this.meanFrameTime = this.meanFrameTime * this.scaleMean_ + + seconds * this.scaleInput_; + if (this.delayCyclesLeft_ > 0) { + this.delayCyclesLeft_ -= 1; + } else if (test) { + if (this.meanFrameTime < this.targetFrameTimeMin_) { + this.increaseQuality(); + this.delayCyclesLeft_ = this.delayCycles; + } else if (this.meanFrameTime > this.targetFrameTimeMax_) { + this.decreaseQuality(); + this.delayCyclesLeft_ = this.delayCycles; + } + } +} + -- cgit v1.1