blob: b4c47766ccda8a7e213cfe242a681ce4f20e111a [file] [log] [blame]
<!DOCTYPE html>
<!--
Copyright (c) 2014 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<link rel="import" href="/core/tracks/highlighter.html">
<link rel="import" href="/core/timeline_track_view.html">
<link rel="import" href="/core/timeline_viewport.html">
<link rel="import" href="/core/tracks/model_track.html">
<script>
'use strict';
/**
* @fileoverview Provides the VSyncHighlighter class.
*/
tr.exportTo('tr.e.highlighter', function() {
var Highlighter = tr.c.tracks.Highlighter;
/**
* Highlights VSync events on the model track (using "zebra" striping).
* @constructor
*/
function VSyncHighlighter(viewport) {
Highlighter.call(this, viewport);
this.times_ = [];
}
VSyncHighlighter.VSYNC_HIGHLIGHT_COLOR = {r: 0, g: 0, b: 255};
VSyncHighlighter.VSYNC_HIGHLIGHT_ALPHA = 0.1;
VSyncHighlighter.VSYNC_DENSITY_TRANSPARENT = 0.20;
VSyncHighlighter.VSYNC_DENSITY_OPAQUE = 0.10;
VSyncHighlighter.VSYNC_DENSITY_RANGE =
VSyncHighlighter.VSYNC_DENSITY_TRANSPARENT -
VSyncHighlighter.VSYNC_DENSITY_OPAQUE;
VSyncHighlighter.VSYNC_COUNTER_PRECISIONS = {
'android.VSYNC-app': 15,
'android.VSYNC': 15
};
VSyncHighlighter.VSYNC_SLICE_PRECISIONS = {
// Android.
'RenderWidgetHostViewAndroid::OnVSync': 5,
// Android. Very precise. Requires "gfx" systrace category to be enabled.
'VSYNC': 10,
// Linux. Very precise. Requires "gpu" tracing category to be enabled.
'vblank': 10,
// Mac. Derived from a Mac callback (CVDisplayLinkSetOutputCallback).
'DisplayLinkMac::GetVSyncParameters': 5
};
/**
* Find the most precise VSync event times in a model.
*/
VSyncHighlighter.findVSyncTimes = function(model) {
var times = [];
// Only keep the most precise VSync data.
var maxPrecision = Number.NEGATIVE_INFINITY;
var maxTitle = undefined;
var useInstead = function(title, precisions) {
if (title != maxTitle) {
var precision = precisions[title];
if (precision === undefined || precision <= maxPrecision) {
if (precision === maxPrecision) {
console.warn('Encountered two different VSync events (' +
maxTitle + ', ' + title + ') with the same precision, ' +
'ignoring the newer one (' + title + ')');
}
return false;
}
maxPrecision = precision;
maxTitle = title;
times = [];
}
return true;
}
for (var pid in model.processes) {
var process = model.processes[pid];
// Traverse process counters.
for (var cid in process.counters) {
if (useInstead(cid, VSyncHighlighter.VSYNC_COUNTER_PRECISIONS)) {
var counter = process.counters[cid];
for (var i = 0; i < counter.series.length; i++) {
var series = counter.series[i];
Array.prototype.push.apply(times, series.timestamps);
}
}
}
// Traverse thread slices.
for (var tid in process.threads) {
var thread = process.threads[tid];
for (var i = 0; i < thread.sliceGroup.slices.length; i++) {
var slice = thread.sliceGroup.slices[i];
if (useInstead(slice.title,
VSyncHighlighter.VSYNC_SLICE_PRECISIONS)) {
times.push(slice.start);
}
}
}
}
times.sort(function(x, y) { return x - y; });
return times;
};
/**
* Generate a zebra striping from a list of times.
*/
VSyncHighlighter.generateStripes = function(times, minTime, maxTime) {
var stripes = [];
// Find the lowest and highest index within the viewport.
var lowIndex = tr.b.findLowIndexInSortedArray(
times,
function(time) { return time; },
minTime);
if (lowIndex > times.length) {
lowIndex = times.length;
}
var highIndex = lowIndex - 1;
while (times[highIndex + 1] <= maxTime) {
highIndex++;
}
// Must start at an even index and end at an odd index.
for (var i = lowIndex - (lowIndex % 2); i <= highIndex; i += 2) {
var left = i < lowIndex ? minTime : times[i];
var right = i + 1 > highIndex ? maxTime : times[i + 1];
stripes.push([left, right]);
}
return stripes;
}
VSyncHighlighter.prototype = {
__proto__: Highlighter.prototype,
processModel: function(model) {
this.times_ = VSyncHighlighter.findVSyncTimes(model);
},
drawHighlight: function(ctx, dt, viewLWorld, viewRWorld, viewHeight) {
if (!this.viewport_.highlightVSync) {
return;
}
var stripes = VSyncHighlighter.generateStripes(
this.times_, viewLWorld, viewRWorld);
if (stripes.length == 0) {
return;
}
var stripeRange = stripes[stripes.length - 1][1] - stripes[0][0];
var stripeDensity = stripes.length / (dt.scaleX * stripeRange);
var clampedStripeDensity = tr.b.clamp(stripeDensity,
VSyncHighlighter.VSYNC_DENSITY_OPAQUE,
VSyncHighlighter.VSYNC_DENSITY_TRANSPARENT);
var opacity =
(VSyncHighlighter.VSYNC_DENSITY_TRANSPARENT - clampedStripeDensity) /
VSyncHighlighter.VSYNC_DENSITY_RANGE;
if (opacity == 0) {
return;
}
var pixelRatio = window.devicePixelRatio || 1;
var height = viewHeight * pixelRatio;
ctx.fillStyle = tr.b.ui.colorToRGBAString(
VSyncHighlighter.VSYNC_HIGHLIGHT_COLOR,
VSyncHighlighter.VSYNC_HIGHLIGHT_ALPHA * opacity);
for (var i = 0; i < stripes.length; i++) {
var xLeftView = dt.xWorldToView(stripes[i][0]);
var xRightView = dt.xWorldToView(stripes[i][1]);
ctx.fillRect(xLeftView, 0, xRightView - xLeftView, height);
}
}
};
// Register the highlighter.
tr.c.tracks.Highlighter.register(VSyncHighlighter);
return {
VSyncHighlighter: VSyncHighlighter
};
});
</script>