| <!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> |