summaryrefslogtreecommitdiffstats
path: root/third_party/webkit/PerformanceTests/MotionMark/tests/resources/main.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--third_party/webkit/PerformanceTests/MotionMark/tests/resources/main.js934
1 files changed, 934 insertions, 0 deletions
diff --git a/third_party/webkit/PerformanceTests/MotionMark/tests/resources/main.js b/third_party/webkit/PerformanceTests/MotionMark/tests/resources/main.js
new file mode 100644
index 0000000000..b9776d571e
--- /dev/null
+++ b/third_party/webkit/PerformanceTests/MotionMark/tests/resources/main.js
@@ -0,0 +1,934 @@
+Sampler = Utilities.createClass(
+ function(seriesCount, expectedSampleCount, processor)
+ {
+ this._processor = processor;
+
+ this.samples = [];
+ for (var i = 0; i < seriesCount; ++i) {
+ var array = new Array(expectedSampleCount);
+ array.fill(0);
+ this.samples[i] = array;
+ }
+ this.sampleCount = 0;
+ }, {
+
+ record: function() {
+ // Assume that arguments.length == this.samples.length
+ for (var i = 0; i < arguments.length; i++) {
+ this.samples[i][this.sampleCount] = arguments[i];
+ }
+ ++this.sampleCount;
+ },
+
+ processSamples: function()
+ {
+ var results = {};
+
+ // Remove unused capacity
+ this.samples = this.samples.map(function(array) {
+ return array.slice(0, this.sampleCount);
+ }, this);
+
+ this._processor.processSamples(results);
+
+ return results;
+ }
+});
+
+Controller = Utilities.createClass(
+ function(benchmark, options)
+ {
+ // Initialize timestamps relative to the start of the benchmark
+ // In start() the timestamps are offset by the start timestamp
+ this._startTimestamp = 0;
+ this._endTimestamp = options["test-interval"];
+ // Default data series: timestamp, complexity, estimatedFrameLength
+ var sampleSize = options["sample-capacity"] || (60 * options["test-interval"] / 1000);
+ this._sampler = new Sampler(options["series-count"] || 3, sampleSize, this);
+ this._marks = {};
+
+ this._frameLengthEstimator = new SimpleKalmanEstimator(options["kalman-process-error"], options["kalman-measurement-error"]);
+ this._isFrameLengthEstimatorEnabled = true;
+
+ // Length of subsequent intervals; a value of 0 means use no intervals
+ this.intervalSamplingLength = 100;
+
+ this.initialComplexity = 1;
+ }, {
+
+ set isFrameLengthEstimatorEnabled(enabled) {
+ this._isFrameLengthEstimatorEnabled = enabled;
+ },
+
+ start: function(startTimestamp, stage)
+ {
+ this._startTimestamp = startTimestamp;
+ this._endTimestamp += startTimestamp;
+ this._previousTimestamp = startTimestamp;
+ this._measureAndResetInterval(startTimestamp);
+ this.recordFirstSample(startTimestamp, stage);
+ },
+
+ recordFirstSample: function(startTimestamp, stage)
+ {
+ this._sampler.record(startTimestamp, stage.complexity(), -1);
+ this.mark(Strings.json.samplingStartTimeOffset, startTimestamp);
+ },
+
+ mark: function(comment, timestamp, data) {
+ data = data || {};
+ data.time = timestamp;
+ data.index = this._sampler.sampleCount;
+ this._marks[comment] = data;
+ },
+
+ containsMark: function(comment) {
+ return comment in this._marks;
+ },
+
+ _measureAndResetInterval: function(currentTimestamp)
+ {
+ var sampleCount = this._sampler.sampleCount;
+ var averageFrameLength = 0;
+
+ if (this._intervalEndTimestamp) {
+ var intervalStartTimestamp = this._sampler.samples[0][this._intervalStartIndex];
+ averageFrameLength = (currentTimestamp - intervalStartTimestamp) / (sampleCount - this._intervalStartIndex);
+ }
+
+ this._intervalStartIndex = sampleCount;
+ this._intervalEndTimestamp = currentTimestamp + this.intervalSamplingLength;
+
+ return averageFrameLength;
+ },
+
+ update: function(timestamp, stage)
+ {
+ var lastFrameLength = timestamp - this._previousTimestamp;
+ this._previousTimestamp = timestamp;
+
+ var frameLengthEstimate = -1, intervalAverageFrameLength = -1;
+ var didFinishInterval = false;
+ if (!this.intervalSamplingLength) {
+ if (this._isFrameLengthEstimatorEnabled) {
+ this._frameLengthEstimator.sample(lastFrameLength);
+ frameLengthEstimate = this._frameLengthEstimator.estimate;
+ }
+ } else if (timestamp >= this._intervalEndTimestamp) {
+ var intervalStartTimestamp = this._sampler.samples[0][this._intervalStartIndex];
+ intervalAverageFrameLength = this._measureAndResetInterval(timestamp);
+ if (this._isFrameLengthEstimatorEnabled) {
+ this._frameLengthEstimator.sample(intervalAverageFrameLength);
+ frameLengthEstimate = this._frameLengthEstimator.estimate;
+ }
+ didFinishInterval = true;
+ this.didFinishInterval(timestamp, stage, intervalAverageFrameLength);
+ }
+
+ this._sampler.record(timestamp, stage.complexity(), frameLengthEstimate);
+ this.tune(timestamp, stage, lastFrameLength, didFinishInterval, intervalAverageFrameLength);
+ },
+
+ didFinishInterval: function(timestamp, stage, intervalAverageFrameLength)
+ {
+ },
+
+ tune: function(timestamp, stage, lastFrameLength, didFinishInterval, intervalAverageFrameLength)
+ {
+ },
+
+ shouldStop: function(timestamp)
+ {
+ return timestamp > this._endTimestamp;
+ },
+
+ results: function()
+ {
+ return this._sampler.processSamples();
+ },
+
+ _processComplexitySamples: function(complexitySamples, complexityAverageSamples)
+ {
+ complexityAverageSamples.addField(Strings.json.complexity, 0);
+ complexityAverageSamples.addField(Strings.json.frameLength, 1);
+ complexityAverageSamples.addField(Strings.json.measurements.stdev, 2);
+
+ complexitySamples.sort(function(a, b) {
+ return complexitySamples.getFieldInDatum(a, Strings.json.complexity) - complexitySamples.getFieldInDatum(b, Strings.json.complexity);
+ });
+
+ // Samples averaged based on complexity
+ var currentComplexity = -1;
+ var experimentAtComplexity;
+ function addSample() {
+ var mean = experimentAtComplexity.mean();
+ var stdev = experimentAtComplexity.standardDeviation();
+
+ var averageSample = complexityAverageSamples.createDatum();
+ complexityAverageSamples.push(averageSample);
+ complexityAverageSamples.setFieldInDatum(averageSample, Strings.json.complexity, currentComplexity);
+ complexityAverageSamples.setFieldInDatum(averageSample, Strings.json.frameLength, mean);
+ complexityAverageSamples.setFieldInDatum(averageSample, Strings.json.measurements.stdev, stdev);
+ }
+ complexitySamples.forEach(function(sample) {
+ var sampleComplexity = complexitySamples.getFieldInDatum(sample, Strings.json.complexity);
+ if (sampleComplexity != currentComplexity) {
+ if (currentComplexity > -1)
+ addSample();
+
+ currentComplexity = sampleComplexity;
+ experimentAtComplexity = new Experiment;
+ }
+ experimentAtComplexity.sample(complexitySamples.getFieldInDatum(sample, Strings.json.frameLength));
+ });
+ // Finish off the last one
+ addSample();
+ },
+
+ processSamples: function(results)
+ {
+ var complexityExperiment = new Experiment;
+ var smoothedFrameLengthExperiment = new Experiment;
+
+ var samples = this._sampler.samples;
+
+ for (var markName in this._marks)
+ this._marks[markName].time -= this._startTimestamp;
+ results[Strings.json.marks] = this._marks;
+
+ results[Strings.json.samples] = {};
+
+ var controllerSamples = new SampleData;
+ results[Strings.json.samples][Strings.json.controller] = controllerSamples;
+
+ controllerSamples.addField(Strings.json.time, 0);
+ controllerSamples.addField(Strings.json.complexity, 1);
+ controllerSamples.addField(Strings.json.frameLength, 2);
+ controllerSamples.addField(Strings.json.smoothedFrameLength, 3);
+
+ var complexitySamples = new SampleData(controllerSamples.fieldMap);
+ results[Strings.json.samples][Strings.json.complexity] = complexitySamples;
+
+ samples[0].forEach(function(timestamp, i) {
+ var sample = controllerSamples.createDatum();
+ controllerSamples.push(sample);
+ complexitySamples.push(sample);
+
+ // Represent time in milliseconds
+ controllerSamples.setFieldInDatum(sample, Strings.json.time, timestamp - this._startTimestamp);
+ controllerSamples.setFieldInDatum(sample, Strings.json.complexity, samples[1][i]);
+
+ if (i == 0)
+ controllerSamples.setFieldInDatum(sample, Strings.json.frameLength, 1000/60);
+ else
+ controllerSamples.setFieldInDatum(sample, Strings.json.frameLength, timestamp - samples[0][i - 1]);
+
+ if (samples[2][i] != -1)
+ controllerSamples.setFieldInDatum(sample, Strings.json.smoothedFrameLength, samples[2][i]);
+ }, this);
+
+ var complexityAverageSamples = new SampleData;
+ results[Strings.json.samples][Strings.json.complexityAverage] = complexityAverageSamples;
+ this._processComplexitySamples(complexitySamples, complexityAverageSamples);
+ }
+});
+
+FixedController = Utilities.createSubclass(Controller,
+ function(benchmark, options)
+ {
+ Controller.call(this, benchmark, options);
+ this.initialComplexity = options["complexity"];
+ this.intervalSamplingLength = 0;
+ }
+);
+
+StepController = Utilities.createSubclass(Controller,
+ function(benchmark, options)
+ {
+ Controller.call(this, benchmark, options);
+ this.initialComplexity = options["complexity"];
+ this.intervalSamplingLength = 0;
+ this._stepped = false;
+ this._stepTime = options["test-interval"] / 2;
+ }, {
+
+ start: function(startTimestamp, stage)
+ {
+ Controller.prototype.start.call(this, startTimestamp, stage);
+ this._stepTime += startTimestamp;
+ },
+
+ tune: function(timestamp, stage)
+ {
+ if (this._stepped || timestamp < this._stepTime)
+ return;
+
+ this.mark(Strings.json.samplingEndTimeOffset, timestamp);
+ this._stepped = true;
+ stage.tune(stage.complexity() * 3);
+ }
+});
+
+AdaptiveController = Utilities.createSubclass(Controller,
+ function(benchmark, options)
+ {
+ // Data series: timestamp, complexity, estimatedIntervalFrameLength
+ Controller.call(this, benchmark, options);
+
+ // All tests start at 0, so we expect to see 60 fps quickly.
+ this._samplingTimestamp = options["test-interval"] / 2;
+ this._startedSampling = false;
+ this._targetFrameRate = options["frame-rate"];
+ this._pid = new PIDController(this._targetFrameRate);
+
+ this._intervalFrameCount = 0;
+ this._numberOfFramesToMeasurePerInterval = 4;
+ }, {
+
+ start: function(startTimestamp, stage)
+ {
+ Controller.prototype.start.call(this, startTimestamp, stage);
+
+ this._samplingTimestamp += startTimestamp;
+ this._intervalTimestamp = startTimestamp;
+ },
+
+ recordFirstSample: function(startTimestamp, stage)
+ {
+ this._sampler.record(startTimestamp, stage.complexity(), -1);
+ },
+
+ update: function(timestamp, stage)
+ {
+ if (!this._startedSampling && timestamp >= this._samplingTimestamp) {
+ this._startedSampling = true;
+ this.mark(Strings.json.samplingStartTimeOffset, this._samplingTimestamp);
+ }
+
+ // Start the work for the next frame.
+ ++this._intervalFrameCount;
+
+ if (this._intervalFrameCount < this._numberOfFramesToMeasurePerInterval) {
+ this._sampler.record(timestamp, stage.complexity(), -1);
+ return;
+ }
+
+ // Adjust the test to reach the desired FPS.
+ var intervalLength = timestamp - this._intervalTimestamp;
+ this._frameLengthEstimator.sample(intervalLength / this._numberOfFramesToMeasurePerInterval);
+ var intervalEstimatedFrameRate = 1000 / this._frameLengthEstimator.estimate;
+ var tuneValue = -this._pid.tune(timestamp - this._startTimestamp, intervalLength, intervalEstimatedFrameRate);
+ tuneValue = tuneValue > 0 ? Math.floor(tuneValue) : Math.ceil(tuneValue);
+ stage.tune(tuneValue);
+
+ this._sampler.record(timestamp, stage.complexity(), this._frameLengthEstimator.estimate);
+
+ // Start the next interval.
+ this._intervalFrameCount = 0;
+ this._intervalTimestamp = timestamp;
+ }
+});
+
+RampController = Utilities.createSubclass(Controller,
+ function(benchmark, options)
+ {
+ // The tier warmup takes at most 5 seconds
+ options["sample-capacity"] = (options["test-interval"] / 1000 + 5) * 60;
+ Controller.call(this, benchmark, options);
+
+ // Initially start with a tier test to find the bounds
+ // The number of objects in a tier test is 10^|_tier|
+ this._tier = -.5;
+ // The timestamp is first set after the first interval completes
+ this._tierStartTimestamp = 0;
+ this._minimumComplexity = 1;
+ this._maximumComplexity = 1;
+
+ // After the tier range is determined, figure out the number of ramp iterations
+ var minimumRampLength = 3000;
+ var totalRampIterations = Math.max(1, Math.floor(this._endTimestamp / minimumRampLength));
+ // Give a little extra room to run since the ramps won't be exactly this length
+ this._rampLength = Math.floor((this._endTimestamp - totalRampIterations * this.intervalSamplingLength) / totalRampIterations);
+ this._rampDidWarmup = false;
+ this._rampRegressions = [];
+
+ this._finishedTierSampling = false;
+ this._changePointEstimator = new Experiment;
+ this._minimumComplexityEstimator = new Experiment;
+ // Estimates all frames within an interval
+ this._intervalFrameLengthEstimator = new Experiment;
+ }, {
+
+ // If the engine can handle the tier's complexity at the desired frame rate, test for a short
+ // period, then move on to the next tier
+ tierFastTestLength: 250,
+ // If the engine is under stress, let the test run a little longer to let the measurement settle
+ tierSlowTestLength: 750,
+
+ rampWarmupLength: 200,
+
+ // Used for regression calculations in the ramps
+ frameLengthDesired: 1000/60,
+ // Add some tolerance; frame lengths shorter than this are considered to be @ the desired frame length
+ frameLengthDesiredThreshold: 1000/58,
+ // During tier sampling get at least this slow to find the right complexity range
+ frameLengthTierThreshold: 1000/30,
+ // Try to make each ramp get this slow so that we can cross the break point
+ frameLengthRampLowerThreshold: 1000/45,
+ // Do not let the regression calculation at the maximum complexity of a ramp get slower than this threshold
+ frameLengthRampUpperThreshold: 1000/20,
+
+ start: function(startTimestamp, stage)
+ {
+ Controller.prototype.start.call(this, startTimestamp, stage);
+ this._rampStartTimestamp = 0;
+ this.intervalSamplingLength = 100;
+ },
+
+ didFinishInterval: function(timestamp, stage, intervalAverageFrameLength)
+ {
+ if (!this._finishedTierSampling) {
+ if (this._tierStartTimestamp > 0 && timestamp < this._tierStartTimestamp + this.tierFastTestLength)
+ return;
+
+ var currentComplexity = stage.complexity();
+ var currentFrameLength = this._frameLengthEstimator.estimate;
+ if (currentFrameLength < this.frameLengthTierThreshold) {
+ var isAnimatingAt60FPS = currentFrameLength < this.frameLengthDesiredThreshold;
+ var hasFinishedSlowTierTest = timestamp > this._tierStartTimestamp + this.tierSlowTestLength;
+
+ if (!isAnimatingAt60FPS && !hasFinishedSlowTierTest)
+ return;
+
+ // We're measuring at 60 fps, so quickly move on to the next tier, or
+ // we've slower than 60 fps, but we've let this tier run long enough to
+ // get an estimate
+ this._lastTierComplexity = currentComplexity;
+ this._lastTierFrameLength = currentFrameLength;
+
+ this._tier += .5;
+ var nextTierComplexity = Math.round(Math.pow(10, this._tier));
+ stage.tune(nextTierComplexity - currentComplexity);
+
+ // Some tests may be unable to go beyond a certain capacity. If so, don't keep moving up tiers
+ if (stage.complexity() - currentComplexity > 0 || nextTierComplexity == 1) {
+ this._tierStartTimestamp = timestamp;
+ this.mark("Complexity: " + nextTierComplexity, timestamp);
+ return;
+ }
+ } else if (timestamp < this._tierStartTimestamp + this.tierSlowTestLength)
+ return;
+
+ this._finishedTierSampling = true;
+ this.isFrameLengthEstimatorEnabled = false;
+ this.intervalSamplingLength = 120;
+
+ // Extend the test length so that the full test length is made of the ramps
+ this._endTimestamp += timestamp;
+ this.mark(Strings.json.samplingStartTimeOffset, timestamp);
+
+ this._minimumComplexity = 1;
+ this._possibleMinimumComplexity = this._minimumComplexity;
+ this._minimumComplexityEstimator.sample(this._minimumComplexity);
+
+ // Sometimes this last tier will drop the frame length well below the threshold.
+ // Avoid going down that far since it means fewer measurements are taken in the 60 fps area.
+ // Interpolate a maximum complexity that gets us around the lowest threshold.
+ // Avoid doing this calculation if we never get out of the first tier (where this._lastTierComplexity is undefined).
+ if (this._lastTierComplexity && this._lastTierComplexity != currentComplexity)
+ this._maximumComplexity = Math.floor(Utilities.lerp(Utilities.progressValue(this.frameLengthTierThreshold, this._lastTierFrameLength, currentFrameLength), this._lastTierComplexity, currentComplexity));
+ else {
+ // If the browser is capable of handling the most complex version of the test, use that
+ this._maximumComplexity = currentComplexity;
+ }
+ this._possibleMaximumComplexity = this._maximumComplexity;
+
+ // If we get ourselves onto a ramp where the maximum complexity does not yield slow enough FPS,
+ // We'll use this as a boundary to find a higher maximum complexity for the next ramp
+ this._lastTierComplexity = currentComplexity;
+ this._lastTierFrameLength = currentFrameLength;
+
+ // First ramp
+ stage.tune(this._maximumComplexity - currentComplexity);
+ this._rampDidWarmup = false;
+ // Start timestamp represents start of ramp iteration and warm up
+ this._rampStartTimestamp = timestamp;
+ return;
+ }
+
+ if ((timestamp - this._rampStartTimestamp) < this.rampWarmupLength)
+ return;
+
+ if (this._rampDidWarmup)
+ return;
+
+ this._rampDidWarmup = true;
+ this._currentRampLength = this._rampStartTimestamp + this._rampLength - timestamp;
+ // Start timestamp represents start of ramp down, after warm up
+ this._rampStartTimestamp = timestamp;
+ this._rampStartIndex = this._sampler.sampleCount;
+ },
+
+ tune: function(timestamp, stage, lastFrameLength, didFinishInterval, intervalAverageFrameLength)
+ {
+ if (!this._rampDidWarmup)
+ return;
+
+ this._intervalFrameLengthEstimator.sample(lastFrameLength);
+ if (!didFinishInterval)
+ return;
+
+ var currentComplexity = stage.complexity();
+ var intervalFrameLengthMean = this._intervalFrameLengthEstimator.mean();
+ var intervalFrameLengthStandardDeviation = this._intervalFrameLengthEstimator.standardDeviation();
+
+ if (intervalFrameLengthMean < this.frameLengthDesiredThreshold && this._intervalFrameLengthEstimator.cdf(this.frameLengthDesiredThreshold) > .9) {
+ this._possibleMinimumComplexity = Math.max(this._possibleMinimumComplexity, currentComplexity);
+ } else if (intervalFrameLengthStandardDeviation > 2) {
+ // In the case where we might have found a previous interval where 60fps was reached. We hit a significant blip,
+ // so we should resample this area in the next ramp.
+ this._possibleMinimumComplexity = 1;
+ }
+ if (intervalFrameLengthMean - intervalFrameLengthStandardDeviation > this.frameLengthRampLowerThreshold)
+ this._possibleMaximumComplexity = Math.min(this._possibleMaximumComplexity, currentComplexity);
+ this._intervalFrameLengthEstimator.reset();
+
+ var progress = (timestamp - this._rampStartTimestamp) / this._currentRampLength;
+
+ if (progress < 1) {
+ // Reframe progress percentage so that the last interval of the ramp can sample at minimum complexity
+ progress = (timestamp - this._rampStartTimestamp) / (this._currentRampLength - this.intervalSamplingLength);
+ stage.tune(Math.max(this._minimumComplexity, Math.floor(Utilities.lerp(progress, this._maximumComplexity, this._minimumComplexity))) - currentComplexity);
+ return;
+ }
+
+ var regression = new Regression(this._sampler.samples, this._getComplexity, this._getFrameLength,
+ this._sampler.sampleCount - 1, this._rampStartIndex, { desiredFrameLength: this.frameLengthDesired });
+ this._rampRegressions.push(regression);
+
+ var frameLengthAtMaxComplexity = regression.valueAt(this._maximumComplexity);
+ if (frameLengthAtMaxComplexity < this.frameLengthRampLowerThreshold)
+ this._possibleMaximumComplexity = Math.floor(Utilities.lerp(Utilities.progressValue(this.frameLengthRampLowerThreshold, frameLengthAtMaxComplexity, this._lastTierFrameLength), this._maximumComplexity, this._lastTierComplexity));
+ // If the regression doesn't fit the first segment at all, keep the minimum bound at 1
+ if ((timestamp - this._sampler.samples[0][this._sampler.sampleCount - regression.n1]) / this._currentRampLength < .25)
+ this._possibleMinimumComplexity = 1;
+
+ this._minimumComplexityEstimator.sample(this._possibleMinimumComplexity);
+ this._minimumComplexity = Math.round(this._minimumComplexityEstimator.mean());
+
+ if (frameLengthAtMaxComplexity < this.frameLengthRampUpperThreshold) {
+ this._changePointEstimator.sample(regression.complexity);
+ // Ideally we'll target the change point in the middle of the ramp. If the range of the ramp is too small, there isn't enough
+ // range along the complexity (x) axis for a good regression calculation to be made, so force at least a range of 5
+ // particles. Make it possible to increase the maximum complexity in case unexpected noise caps the regression too low.
+ this._maximumComplexity = Math.round(this._minimumComplexity +
+ Math.max(5,
+ this._possibleMaximumComplexity - this._minimumComplexity,
+ (this._changePointEstimator.mean() - this._minimumComplexity) * 2));
+ } else {
+ // The slowest samples weighed the regression too heavily
+ this._maximumComplexity = Math.max(Math.round(.8 * this._maximumComplexity), this._minimumComplexity + 5);
+ }
+
+ // Next ramp
+ stage.tune(this._maximumComplexity - stage.complexity());
+ this._rampDidWarmup = false;
+ // Start timestamp represents start of ramp iteration and warm up
+ this._rampStartTimestamp = timestamp;
+ this._possibleMinimumComplexity = 1;
+ this._possibleMaximumComplexity = this._maximumComplexity;
+ },
+
+ _getComplexity: function(samples, i) {
+ return samples[1][i];
+ },
+
+ _getFrameLength: function(samples, i) {
+ return samples[0][i] - samples[0][i - 1];
+ },
+
+ processSamples: function(results)
+ {
+ Controller.prototype.processSamples.call(this, results);
+
+ // Have samplingTimeOffset represent time 0
+ var startTimestamp = this._marks[Strings.json.samplingStartTimeOffset].time;
+
+ for (var markName in results[Strings.json.marks]) {
+ results[Strings.json.marks][markName].time -= startTimestamp;
+ }
+
+ var controllerSamples = results[Strings.json.samples][Strings.json.controller];
+ controllerSamples.forEach(function(timeSample) {
+ controllerSamples.setFieldInDatum(timeSample, Strings.json.time, controllerSamples.getFieldInDatum(timeSample, Strings.json.time) - startTimestamp);
+ });
+
+ // Aggregate all of the ramps into one big complexity-frameLength dataset
+ var complexitySamples = new SampleData(controllerSamples.fieldMap);
+ results[Strings.json.samples][Strings.json.complexity] = complexitySamples;
+
+ results[Strings.json.controller] = [];
+ this._rampRegressions.forEach(function(ramp) {
+ var startIndex = ramp.startIndex, endIndex = ramp.endIndex;
+ var startTime = controllerSamples.getFieldInDatum(startIndex, Strings.json.time);
+ var endTime = controllerSamples.getFieldInDatum(endIndex, Strings.json.time);
+ var startComplexity = controllerSamples.getFieldInDatum(startIndex, Strings.json.complexity);
+ var endComplexity = controllerSamples.getFieldInDatum(endIndex, Strings.json.complexity);
+
+ var regression = {};
+ results[Strings.json.controller].push(regression);
+
+ var percentage = (ramp.complexity - startComplexity) / (endComplexity - startComplexity);
+ var inflectionTime = startTime + percentage * (endTime - startTime);
+
+ regression[Strings.json.regressions.segment1] = [
+ [startTime, ramp.s2 + ramp.t2 * startComplexity],
+ [inflectionTime, ramp.s2 + ramp.t2 * ramp.complexity]
+ ];
+ regression[Strings.json.regressions.segment2] = [
+ [inflectionTime, ramp.s1 + ramp.t1 * ramp.complexity],
+ [endTime, ramp.s1 + ramp.t1 * endComplexity]
+ ];
+ regression[Strings.json.complexity] = ramp.complexity;
+ regression[Strings.json.regressions.startIndex] = startIndex;
+ regression[Strings.json.regressions.endIndex] = endIndex;
+ regression[Strings.json.regressions.profile] = ramp.profile;
+
+ for (var j = startIndex; j <= endIndex; ++j)
+ complexitySamples.push(controllerSamples.at(j));
+ });
+
+ var complexityAverageSamples = new SampleData;
+ results[Strings.json.samples][Strings.json.complexityAverage] = complexityAverageSamples;
+ this._processComplexitySamples(complexitySamples, complexityAverageSamples);
+ }
+});
+
+Ramp30Controller = Utilities.createSubclass(RampController,
+ function(benchmark, options)
+ {
+ RampController.call(this, benchmark, options);
+ }, {
+
+ frameLengthDesired: 1000/30,
+ frameLengthDesiredThreshold: 1000/29,
+ frameLengthTierThreshold: 1000/20,
+ frameLengthRampLowerThreshold: 1000/20,
+ frameLengthRampUpperThreshold: 1000/12
+});
+
+Stage = Utilities.createClass(
+ function()
+ {
+ }, {
+
+ initialize: function(benchmark)
+ {
+ this._benchmark = benchmark;
+ this._element = document.getElementById("stage");
+ this._element.setAttribute("width", document.body.offsetWidth);
+ this._element.setAttribute("height", document.body.offsetHeight);
+ this._size = Point.elementClientSize(this._element).subtract(Insets.elementPadding(this._element).size);
+ },
+
+ get element()
+ {
+ return this._element;
+ },
+
+ get size()
+ {
+ return this._size;
+ },
+
+ complexity: function()
+ {
+ return 0;
+ },
+
+ tune: function()
+ {
+ throw "Not implemented";
+ },
+
+ animate: function()
+ {
+ throw "Not implemented";
+ },
+
+ clear: function()
+ {
+ return this.tune(-this.tune(0));
+ }
+});
+
+Utilities.extendObject(Stage, {
+ random: function(min, max)
+ {
+ return (Pseudo.random() * (max - min)) + min;
+ },
+
+ randomBool: function()
+ {
+ return !!Math.round(Pseudo.random());
+ },
+
+ randomSign: function()
+ {
+ return Pseudo.random() >= .5 ? 1 : -1;
+ },
+
+ randomInt: function(min, max)
+ {
+ return Math.floor(this.random(min, max + 1));
+ },
+
+ randomPosition: function(maxPosition)
+ {
+ return new Point(this.randomInt(0, maxPosition.x), this.randomInt(0, maxPosition.y));
+ },
+
+ randomSquareSize: function(min, max)
+ {
+ var side = this.random(min, max);
+ return new Point(side, side);
+ },
+
+ randomVelocity: function(maxVelocity)
+ {
+ return this.random(maxVelocity / 8, maxVelocity);
+ },
+
+ randomAngle: function()
+ {
+ return this.random(0, Math.PI * 2);
+ },
+
+ randomColor: function()
+ {
+ var min = 32;
+ var max = 256 - 32;
+ return "#"
+ + this.randomInt(min, max).toString(16)
+ + this.randomInt(min, max).toString(16)
+ + this.randomInt(min, max).toString(16);
+ },
+
+ randomStyleMixBlendMode: function()
+ {
+ var mixBlendModeList = [
+ 'normal',
+ 'multiply',
+ 'screen',
+ 'overlay',
+ 'darken',
+ 'lighten',
+ 'color-dodge',
+ 'color-burn',
+ 'hard-light',
+ 'soft-light',
+ 'difference',
+ 'exclusion',
+ 'hue',
+ 'saturation',
+ 'color',
+ 'luminosity'
+ ];
+
+ return mixBlendModeList[this.randomInt(0, mixBlendModeList.length)];
+ },
+
+ randomStyleFilter: function()
+ {
+ var filterList = [
+ 'grayscale(50%)',
+ 'sepia(50%)',
+ 'saturate(50%)',
+ 'hue-rotate(180)',
+ 'invert(50%)',
+ 'opacity(50%)',
+ 'brightness(50%)',
+ 'contrast(50%)',
+ 'blur(10px)',
+ 'drop-shadow(10px 10px 10px gray)'
+ ];
+
+ return filterList[this.randomInt(0, filterList.length)];
+ },
+
+ randomElementInArray: function(array)
+ {
+ return array[Stage.randomInt(0, array.length - 1)];
+ },
+
+ rotatingColor: function(cycleLengthMs, saturation, lightness)
+ {
+ return "hsl("
+ + Stage.dateFractionalValue(cycleLengthMs) * 360 + ", "
+ + ((saturation || .8) * 100).toFixed(0) + "%, "
+ + ((lightness || .35) * 100).toFixed(0) + "%)";
+ },
+
+ // Returns a fractional value that wraps around within [0,1]
+ dateFractionalValue: function(cycleLengthMs)
+ {
+ return (Date.now() / (cycleLengthMs || 2000)) % 1;
+ },
+
+ // Returns an increasing value slowed down by factor
+ dateCounterValue: function(factor)
+ {
+ return Date.now() / factor;
+ },
+
+ randomRotater: function()
+ {
+ return new Rotater(this.random(1000, 10000));
+ }
+});
+
+Rotater = Utilities.createClass(
+ function(rotateInterval)
+ {
+ this._timeDelta = 0;
+ this._rotateInterval = rotateInterval;
+ this._isSampling = false;
+ }, {
+
+ get interval()
+ {
+ return this._rotateInterval;
+ },
+
+ next: function(timeDelta)
+ {
+ this._timeDelta = (this._timeDelta + timeDelta) % this._rotateInterval;
+ },
+
+ degree: function()
+ {
+ return (360 * this._timeDelta) / this._rotateInterval;
+ },
+
+ rotateZ: function()
+ {
+ return "rotateZ(" + Math.floor(this.degree()) + "deg)";
+ },
+
+ rotate: function(center)
+ {
+ return "rotate(" + Math.floor(this.degree()) + ", " + center.x + "," + center.y + ")";
+ }
+});
+
+Benchmark = Utilities.createClass(
+ function(stage, options)
+ {
+ this._animateLoop = this._animateLoop.bind(this);
+
+ this._stage = stage;
+ this._stage.initialize(this, options);
+
+ switch (options["time-measurement"])
+ {
+ case "performance":
+ if (window.performance && window.performance.now)
+ this._getTimestamp = performance.now.bind(performance);
+ else
+ this._getTimestamp = null;
+ break;
+ case "raf":
+ this._getTimestamp = null;
+ break;
+ case "date":
+ this._getTimestamp = Date.now;
+ break;
+ }
+
+ options["test-interval"] *= 1000;
+ switch (options["controller"])
+ {
+ case "fixed":
+ this._controller = new FixedController(this, options);
+ break;
+ case "step":
+ this._controller = new StepController(this, options);
+ break;
+ case "adaptive":
+ this._controller = new AdaptiveController(this, options);
+ break;
+ case "ramp":
+ this._controller = new RampController(this, options);
+ break;
+ case "ramp30":
+ this._controller = new Ramp30Controller(this, options);
+ }
+ }, {
+
+ get stage()
+ {
+ return this._stage;
+ },
+
+ get timestamp()
+ {
+ return this._currentTimestamp - this._startTimestamp;
+ },
+
+ backgroundColor: function()
+ {
+ var stage = window.getComputedStyle(document.getElementById("stage"));
+ return stage["background-color"];
+ },
+
+ run: function()
+ {
+ return this.waitUntilReady().then(function() {
+ this._finishPromise = new SimplePromise;
+ this._previousTimestamp = undefined;
+ this._didWarmUp = false;
+ this._stage.tune(this._controller.initialComplexity - this._stage.complexity());
+ this._animateLoop();
+ return this._finishPromise;
+ }.bind(this));
+ },
+
+ // Subclasses should override this if they have setup to do prior to commencing.
+ waitUntilReady: function()
+ {
+ var promise = new SimplePromise;
+ promise.resolve();
+ return promise;
+ },
+
+ _animateLoop: function(timestamp)
+ {
+ timestamp = (this._getTimestamp && this._getTimestamp()) || timestamp;
+ this._currentTimestamp = timestamp;
+
+ if (this._controller.shouldStop(timestamp)) {
+ this._finishPromise.resolve(this._controller.results());
+ return;
+ }
+
+ if (!this._didWarmUp) {
+ if (!this._previousTimestamp)
+ this._previousTimestamp = timestamp;
+ else if (timestamp - this._previousTimestamp >= 100) {
+ this._didWarmUp = true;
+ this._startTimestamp = timestamp;
+ this._controller.start(timestamp, this._stage);
+ this._previousTimestamp = timestamp;
+ }
+
+ this._stage.animate(0);
+ requestAnimationFrame(this._animateLoop);
+ return;
+ }
+
+ this._controller.update(timestamp, this._stage);
+ this._stage.animate(timestamp - this._previousTimestamp);
+ this._previousTimestamp = timestamp;
+ requestAnimationFrame(this._animateLoop);
+ }
+});