summaryrefslogtreecommitdiffstats
path: root/third_party/webkit/PerformanceTests/MotionMark/tests/resources
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/webkit/PerformanceTests/MotionMark/tests/resources')
-rw-r--r--third_party/webkit/PerformanceTests/MotionMark/tests/resources/main.js934
-rw-r--r--third_party/webkit/PerformanceTests/MotionMark/tests/resources/math.js268
-rw-r--r--third_party/webkit/PerformanceTests/MotionMark/tests/resources/stage.css27
-rw-r--r--third_party/webkit/PerformanceTests/MotionMark/tests/resources/star.svg8
-rw-r--r--third_party/webkit/PerformanceTests/MotionMark/tests/resources/yin-yang.pngbin0 -> 4082 bytes
-rw-r--r--third_party/webkit/PerformanceTests/MotionMark/tests/resources/yin-yang.svg17
6 files changed, 1254 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);
+ }
+});
diff --git a/third_party/webkit/PerformanceTests/MotionMark/tests/resources/math.js b/third_party/webkit/PerformanceTests/MotionMark/tests/resources/math.js
new file mode 100644
index 0000000000..9c2706e2a3
--- /dev/null
+++ b/third_party/webkit/PerformanceTests/MotionMark/tests/resources/math.js
@@ -0,0 +1,268 @@
+SimpleKalmanEstimator = Utilities.createSubclass(Experiment,
+ function(processError, measurementError) {
+ Experiment.call(this, false);
+ var error = .5 * (Math.sqrt(processError * processError + 4 * processError * measurementError) - processError);
+ this._gain = error / (error + measurementError);
+ }, {
+
+ sample: function(newMeasurement)
+ {
+ if (!this._initialized) {
+ this._initialized = true;
+ this.estimate = newMeasurement;
+ return;
+ }
+
+ this.estimate = this.estimate + this._gain * (newMeasurement - this.estimate);
+ },
+
+ reset: function()
+ {
+ Experiment.prototype.reset.call(this);
+ this._initialized = false;
+ this.estimate = 0;
+ }
+});
+
+PIDController = Utilities.createClass(
+ function(ysp)
+ {
+ this._ysp = ysp;
+ this._out = 0;
+
+ this._Kp = 0;
+ this._stage = PIDController.stages.WARMING;
+
+ this._eold = 0;
+ this._I = 0;
+ }, {
+
+ // Determines whether the current y is
+ // before ysp => (below ysp if ysp > y0) || (above ysp if ysp < y0)
+ // after ysp => (above ysp if ysp > y0) || (below ysp if ysp < y0)
+ _yPosition: function(y)
+ {
+ return (y < this._ysp) == (this._y0 < this._ysp)
+ ? PIDController.yPositions.BEFORE_SETPOINT
+ : PIDController.yPositions.AFTER_SETPOINT;
+ },
+
+ // Calculate the ultimate distance from y0 after time t. We want to move very
+ // slowly at the beginning to see how adding few items to the test can affect
+ // its output. The complexity of a single item might be big enough to keep the
+ // proportional gain very small but achieves the desired progress. But if y does
+ // not change significantly after adding few items, that means we need a much
+ // bigger gain. So we need to move over a cubic curve which increases very
+ // slowly with small t values but moves very fast with larger t values.
+ // The basic formula is: y = t^3
+ // Change the formula to reach y=1 after 1000 ms: y = (t/1000)^3
+ // Change the formula to reach y=(ysp - y0) after 1000 ms: y = (ysp - y0) * (t/1000)^3
+ _distanceUltimate: function(t)
+ {
+ return (this._ysp - this._y0) * Math.pow(t / 1000, 3);
+ },
+
+ // Calculates the distance of y relative to y0. It also ensures we do not return
+ // zero by returning a epsilon value in the same direction as ultimate distance.
+ _distance: function(y, du)
+ {
+ const epsilon = 0.0001;
+ var d = y - this._y0;
+ return du < 0 ? Math.min(d, -epsilon) : Math.max(d, epsilon);
+ },
+
+ // Decides how much the proportional gain should be increased during the manual
+ // gain stage. We choose to use the ratio of the ultimate distance to the current
+ // distance as an indication of how much the system is responsive. We want
+ // to keep the increment under control so it does not cause the system instability
+ // So we choose to take the natural logarithm of this ratio.
+ _gainIncrement: function(t, y, e)
+ {
+ var du = this._distanceUltimate(t);
+ var d = this._distance(y, du);
+ return Math.log(du / d) * 0.1;
+ },
+
+ // Update the stage of the controller based on its current stage and the system output
+ _updateStage: function(y)
+ {
+ var yPosition = this._yPosition(y);
+
+ switch (this._stage) {
+ case PIDController.stages.WARMING:
+ if (yPosition == PIDController.yPositions.AFTER_SETPOINT)
+ this._stage = PIDController.stages.OVERSHOOT;
+ break;
+
+ case PIDController.stages.OVERSHOOT:
+ if (yPosition == PIDController.yPositions.BEFORE_SETPOINT)
+ this._stage = PIDController.stages.UNDERSHOOT;
+ break;
+
+ case PIDController.stages.UNDERSHOOT:
+ if (yPosition == PIDController.yPositions.AFTER_SETPOINT)
+ this._stage = PIDController.stages.SATURATE;
+ break;
+ }
+ },
+
+ // Manual tuning is used before calculating the PID controller gains.
+ _tuneP: function(e)
+ {
+ // The output is the proportional term only.
+ return this._Kp * e;
+ },
+
+ // PID tuning function. Kp, Ti and Td were already calculated
+ _tunePID: function(h, y, e)
+ {
+ // Proportional term.
+ var P = this._Kp * e;
+
+ // Integral term is the area under the curve starting from the beginning
+ // till the current time.
+ this._I += (this._Kp / this._Ti) * ((e + this._eold) / 2) * h;
+
+ // Derivative term is the slope of the curve at the current time.
+ var D = (this._Kp * this._Td) * (e - this._eold) / h;
+
+ // The ouput is a PID function.
+ return P + this._I + D;
+ },
+
+ // Apply different strategies for the tuning based on the stage of the controller.
+ _tune: function(t, h, y, e)
+ {
+ switch (this._stage) {
+ case PIDController.stages.WARMING:
+ // This is the first stage of the Zieglerâ€Nichols method. It increments
+ // the proportional gain till the system output passes the set-point value.
+ if (typeof this._y0 == "undefined") {
+ // This is the first time a tuning value is required. We want the test
+ // to add only one item. So we need to return -1 which forces us to
+ // choose the initial value of Kp to be = -1 / e
+ this._y0 = y;
+ this._Kp = -1 / e;
+ } else {
+ // Keep incrementing the Kp as long as we have not reached the
+ // set-point yet
+ this._Kp += this._gainIncrement(t, y, e);
+ }
+
+ return this._tuneP(e);
+
+ case PIDController.stages.OVERSHOOT:
+ // This is the second stage of the Zieglerâ€Nichols method. It measures the
+ // oscillation period.
+ if (typeof this._t0 == "undefined") {
+ // t is the time of the begining of the first overshot
+ this._t0 = t;
+ this._Kp /= 2;
+ }
+
+ return this._tuneP(e);
+
+ case PIDController.stages.UNDERSHOOT:
+ // This is the end of the Zieglerâ€Nichols method. We need to calculate the
+ // integral and derivative periods.
+ if (typeof this._Ti == "undefined") {
+ // t is the time of the end of the first overshot
+ var Tu = t - this._t0;
+
+ // Calculate the system parameters from Kp and Tu assuming
+ // a "some overshoot" control type. See:
+ // https://en.wikipedia.org/wiki/Ziegler%E2%80%93Nichols_method
+ this._Ti = Tu / 2;
+ this._Td = Tu / 3;
+ this._Kp = 0.33 * this._Kp;
+
+ // Calculate the tracking time.
+ this._Tt = Math.sqrt(this._Ti * this._Td);
+ }
+
+ return this._tunePID(h, y, e);
+
+ case PIDController.stages.SATURATE:
+ return this._tunePID(h, y, e);
+ }
+
+ return 0;
+ },
+
+ // Ensures the system does not fluctuates.
+ _saturate: function(v, e)
+ {
+ var u = v;
+
+ switch (this._stage) {
+ case PIDController.stages.OVERSHOOT:
+ case PIDController.stages.UNDERSHOOT:
+ // Calculate the min-max values of the saturation actuator.
+ if (typeof this._min == "undefined")
+ this._min = this._max = this._out;
+ else {
+ this._min = Math.min(this._min, this._out);
+ this._max = Math.max(this._max, this._out);
+ }
+ break;
+
+ case PIDController.stages.SATURATE:
+ const limitPercentage = 0.90;
+ var min = this._min > 0 ? Math.min(this._min, this._max * limitPercentage) : this._min;
+ var max = this._max < 0 ? Math.max(this._max, this._min * limitPercentage) : this._max;
+ var out = this._out + u;
+
+ // Clip the controller output to the min-max values
+ out = Math.max(Math.min(max, out), min);
+ u = out - this._out;
+
+ // Apply the back-calculation and tracking
+ if (u != v)
+ u += (this._Kp * this._Tt / this._Ti) * e;
+ break;
+ }
+
+ this._out += u;
+ return u;
+ },
+
+ // Called from the benchmark to tune its test. It uses Ziegler-Nichols method
+ // to calculate the controller parameters. It then returns a PID tuning value.
+ tune: function(t, h, y)
+ {
+ this._updateStage(y);
+
+ // Current error.
+ var e = this._ysp - y;
+ var v = this._tune(t, h, y, e);
+
+ // Save e for the next call.
+ this._eold = e;
+
+ // Apply back-calculation and tracking to avoid integrator windup
+ return this._saturate(v, e);
+ }
+});
+
+Utilities.extendObject(PIDController, {
+ // This enum will be used to tell whether the system output (or the controller input)
+ // is moving towards the set-point or away from it.
+ yPositions: {
+ BEFORE_SETPOINT: 0,
+ AFTER_SETPOINT: 1
+ },
+
+ // The Ziegler-Nichols method for is used tuning the PID controller. The workflow of
+ // the tuning is split into four stages. The first two stages determine the values
+ // of the PID controller gains. During these two stages we return the proportional
+ // term only. The third stage is used to determine the min-max values of the
+ // saturation actuator. In the last stage back-calculation and tracking are applied
+ // to avoid integrator windup. During the last two stages, we return a PID control
+ // value.
+ stages: {
+ WARMING: 0, // Increase the value of the Kp until the system output reaches ysp.
+ OVERSHOOT: 1, // Measure the oscillation period and the overshoot value
+ UNDERSHOOT: 2, // Return PID value and measure the undershoot value
+ SATURATE: 3 // Return PID value and apply back-calculation and tracking.
+ }
+});
diff --git a/third_party/webkit/PerformanceTests/MotionMark/tests/resources/stage.css b/third_party/webkit/PerformanceTests/MotionMark/tests/resources/stage.css
new file mode 100644
index 0000000000..0b6ffdc4a2
--- /dev/null
+++ b/third_party/webkit/PerformanceTests/MotionMark/tests/resources/stage.css
@@ -0,0 +1,27 @@
+html {
+ height: 100%;
+}
+body {
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ padding: 0;
+ background-color: rgb(241, 241, 241);
+ font-family: "Helvetica Neue", Helvetica, Verdana, sans-serif;
+}
+
+#stage {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ background-color: rgb(241, 241, 241);
+ overflow: hidden;
+}
+
+#center-text {
+ position: absolute;
+ z-index: 3;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+}
diff --git a/third_party/webkit/PerformanceTests/MotionMark/tests/resources/star.svg b/third_party/webkit/PerformanceTests/MotionMark/tests/resources/star.svg
new file mode 100644
index 0000000000..3c46ae0419
--- /dev/null
+++ b/third_party/webkit/PerformanceTests/MotionMark/tests/resources/star.svg
@@ -0,0 +1,8 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1 1">
+ <defs>
+ <mask id="star-mask" maskUnits="objectBoundingBox" maskContentUnits="objectBoundingBox">
+ <path fill="white" d="M.50,0L.38,.38L0,.38L.30,.60L.18,1L.50,.75L.82,1L.70,.60L1,.38L.62,.38z" />
+ </mask>
+ </defs>
+ <path d="M.50,0L.38,.38L0,.38L.30,.60L.18,1L.50,.75L.82,1L.70,.60L1,.38L.62,.38z" fill="white" />
+</svg>
diff --git a/third_party/webkit/PerformanceTests/MotionMark/tests/resources/yin-yang.png b/third_party/webkit/PerformanceTests/MotionMark/tests/resources/yin-yang.png
new file mode 100644
index 0000000000..3162f6ec00
--- /dev/null
+++ b/third_party/webkit/PerformanceTests/MotionMark/tests/resources/yin-yang.png
Binary files differ
diff --git a/third_party/webkit/PerformanceTests/MotionMark/tests/resources/yin-yang.svg b/third_party/webkit/PerformanceTests/MotionMark/tests/resources/yin-yang.svg
new file mode 100644
index 0000000000..4412626774
--- /dev/null
+++ b/third_party/webkit/PerformanceTests/MotionMark/tests/resources/yin-yang.svg
@@ -0,0 +1,17 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 200 200">
+ <defs>
+ <clipPath id="left-half">
+ <rect width="100" height="200"/>
+ </clipPath>
+ <clipPath id="right-half">
+ <rect x="100" width="100" height="200"/>
+ </clipPath>
+ </defs>
+ <circle cx="100" cy="100" r="98" fill="none" stroke="green" stroke-width="2"/>
+ <circle cx="100" cy="100" r="98" fill="white" clip-path="url(#left-half)"/>
+ <circle cx="100" cy="100" r="98" fill="green" clip-path="url(#right-half)"/>
+ <circle cx="100" cy="50" r="49" fill="green"/>
+ <circle cx="100" cy="148" r="49" fill="white"/>
+ <circle cx="100" cy="50" r="10" fill="white"/>
+ <circle cx="100" cy="148" r="10" fill="green"/>
+</svg>