Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(93)

Side by Side Diff: chrome/browser/resources/gpu_internals/timeline.js

Issue 7555005: Moving the contents of chrome://gpu Profiling to chrome://tracing. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Remove tabs Created 9 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 /**
6 * @fileoverview Interactive visualizaiton of TimelineModel objects
7 * based loosely on gantt charts. Each thread in the TimelineModel is given a
8 * set of TimelineTracks, one per subrow in the thread. The Timeline class
9 * acts as a controller, creating the individual tracks, while TimelineTracks
10 * do actual drawing.
11 *
12 * Visually, the Timeline produces (prettier) visualizations like the following:
13 * Thread1: AAAAAAAAAA AAAAA
14 * BBBB BB
15 * Thread2: CCCCCC CCCCC
16 *
17 */
18 cr.define('gpu', function() {
19
20 /**
21 * The TimelineViewport manages the transform used for navigating
22 * within the timeline. It is a simple transform:
23 * x' = (x+pan) * scale
24 *
25 * The timeline code tries to avoid directly accessing this transform,
26 * instead using this class to do conversion between world and view space,
27 * as well as the math for centering the viewport in various interesting
28 * ways.
29 *
30 * @constructor
31 * @extends {cr.EventTarget}
32 */
33 function TimelineViewport() {
34 this.scaleX_ = 1;
35 this.panX_ = 0;
36 this.gridTimebase_ = 0;
37 this.gridStep_ = 1000 / 60;
38 this.gridEnabled_ = false;
39 }
40
41 TimelineViewport.prototype = {
42 __proto__: cr.EventTarget.prototype,
43
44 get scaleX() {
45 return this.scaleX_;
46 },
47 set scaleX(s) {
48 var changed = this.scaleX_ != s;
49 if (changed) {
50 this.scaleX_ = s;
51 cr.dispatchSimpleEvent(this, 'change');
52 }
53 },
54
55 get panX() {
56 return this.panX_;
57 },
58 set panX(p) {
59 var changed = this.panX_ != p;
60 if (changed) {
61 this.panX_ = p;
62 cr.dispatchSimpleEvent(this, 'change');
63 }
64 },
65
66 setPanAndScale: function(p, s) {
67 var changed = this.scaleX_ != s || this.panX_ != p;
68 if (changed) {
69 this.scaleX_ = s;
70 this.panX_ = p;
71 cr.dispatchSimpleEvent(this, 'change');
72 }
73 },
74
75 xWorldToView: function(x) {
76 return (x + this.panX_) * this.scaleX_;
77 },
78
79 xWorldVectorToView: function(x) {
80 return x * this.scaleX_;
81 },
82
83 xViewToWorld: function(x) {
84 return (x / this.scaleX_) - this.panX_;
85 },
86
87 xViewVectorToWorld: function(x) {
88 return x / this.scaleX_;
89 },
90
91 xPanWorldPosToViewPos: function(worldX, viewX, viewWidth) {
92 if (typeof viewX == 'string') {
93 if (viewX == 'left') {
94 viewX = 0;
95 } else if (viewX == 'center') {
96 viewX = viewWidth / 2;
97 } else if (viewX == 'right') {
98 viewX = viewWidth - 1;
99 } else {
100 throw Error('unrecognized string for viewPos. left|center|right');
101 }
102 }
103 this.panX = (viewX / this.scaleX_) - worldX;
104 },
105
106 get gridEnabled() {
107 return this.gridEnabled_;
108 },
109
110 set gridEnabled(enabled) {
111 if (this.gridEnabled_ == enabled)
112 return;
113 this.gridEnabled_ = enabled && true;
114 cr.dispatchSimpleEvent(this, 'change');
115 },
116
117 get gridTimebase() {
118 return this.gridTimebase_;
119 },
120
121 set gridTimebase(timebase) {
122 if (this.gridTimebase_ == timebase)
123 return;
124 this.gridTimebase_ = timebase;
125 cr.dispatchSimpleEvent(this, 'change');
126 },
127
128 get gridStep() {
129 return this.gridStep_;
130 },
131
132 applyTransformToCanavs: function(ctx) {
133 ctx.transform(this.scaleX_, 0, 0, 1, this.panX_ * this.scaleX_, 0);
134 }
135 };
136
137 /**
138 * Renders a TimelineModel into a div element, making one
139 * TimelineTrack for each subrow in each thread of the model, managing
140 * overall track layout, and handling user interaction with the
141 * viewport.
142 *
143 * @constructor
144 * @extends {HTMLDivElement}
145 */
146 Timeline = cr.ui.define('div');
147
148 Timeline.prototype = {
149 __proto__: HTMLDivElement.prototype,
150
151 model_: null,
152
153 decorate: function() {
154 this.classList.add('timeline');
155 this.needsViewportReset_ = false;
156
157 this.viewport_ = new TimelineViewport();
158 this.viewport_.addEventListener('change',
159 this.viewportChange_.bind(this));
160
161 this.invalidatePending_ = false;
162
163 this.tracks_ = this.ownerDocument.createElement('div');
164 this.tracks_.invalidate = this.invalidate.bind(this);
165 this.appendChild(this.tracks_);
166
167 this.dragBox_ = this.ownerDocument.createElement('div');
168 this.dragBox_.className = 'timeline-drag-box';
169 this.appendChild(this.dragBox_);
170
171 // The following code uses a setInterval to monitor the timeline control
172 // for size changes. This is so that we can keep the canvas' bitmap size
173 // correctly synchronized with its presentation size.
174 // TODO(nduca): detect this in a more efficient way, e.g. iframe hack.
175 this.lastSize_ = this.clientWidth + 'x' + this.clientHeight;
176 this.ownerDocument.defaultView.setInterval(function() {
177 var curSize = this.clientWidth + 'x' + this.clientHeight;
178 if (this.clientWidth && curSize != this.lastSize_) {
179 this.lastSize_ = curSize;
180 this.onResize();
181 }
182 }.bind(this), 250);
183
184 document.addEventListener('keypress', this.onKeypress_.bind(this));
185 document.addEventListener('keydown', this.onKeydown_.bind(this));
186 document.addEventListener('mousedown', this.onMouseDown_.bind(this));
187 document.addEventListener('mousemove', this.onMouseMove_.bind(this));
188 document.addEventListener('mouseup', this.onMouseUp_.bind(this));
189 document.addEventListener('dblclick', this.onDblClick_.bind(this));
190
191 this.lastMouseViewPos_ = {x: 0, y: 0};
192
193 this.selection_ = [];
194 },
195
196 get model() {
197 return this.model_;
198 },
199
200 set model(model) {
201 if (!model)
202 throw Error('Model cannot be null');
203 if (this.model) {
204 throw Error('Cannot set model twice.');
205 }
206 this.model_ = model;
207
208 // Create tracks.
209 this.tracks_.textContent = '';
210 var threads = model.getAllThreads();
211 for (var tI = 0; tI < threads.length; tI++) {
212 var thread = threads[tI];
213 var track = new TimelineThreadTrack();
214 track.thread = thread;
215 track.viewport = this.viewport_;
216 this.tracks_.appendChild(track);
217
218 }
219
220 this.needsViewportReset_ = true;
221 },
222
223 viewportChange_: function() {
224 this.invalidate();
225 },
226
227 invalidate: function() {
228 if (this.invalidatePending_)
229 return;
230 this.invalidatePending_ = true;
231 window.setTimeout(function() {
232 this.invalidatePending_ = false;
233 this.redrawAllTracks_();
234 }.bind(this), 0);
235 },
236
237 onResize: function() {
238 for (var i = 0; i < this.tracks_.children.length; ++i) {
239 var track = this.tracks_.children[i];
240 track.onResize();
241 }
242 },
243
244 redrawAllTracks_: function() {
245 if (this.needsViewportReset_ && this.clientWidth != 0) {
246 this.needsViewportReset_ = false;
247 /* update viewport */
248 var rangeTimestamp = this.model_.maxTimestamp -
249 this.model_.minTimestamp;
250 var w = this.firstCanvas.width;
251 console.log('viewport was reset with w=', w);
252 var scaleX = w / rangeTimestamp;
253 var panX = -this.model_.minTimestamp;
254 this.viewport_.setPanAndScale(panX, scaleX);
255 }
256 for (var i = 0; i < this.tracks_.children.length; ++i) {
257 this.tracks_.children[i].redraw();
258 }
259 },
260
261 updateChildViewports_: function() {
262 for (var cI = 0; cI < this.tracks_.children.length; ++cI) {
263 var child = this.tracks_.children[cI];
264 child.setViewport(this.panX, this.scaleX);
265 }
266 },
267
268 onKeypress_: function(e) {
269 var vp = this.viewport_;
270 if (!this.firstCanvas)
271 return;
272 var viewWidth = this.firstCanvas.clientWidth;
273 var curMouseV, curCenterW;
274 switch (e.keyCode) {
275 case 101: // e
276 var vX = this.lastMouseViewPos_.x;
277 var wX = vp.xViewToWorld(this.lastMouseViewPos_.x);
278 var distFromCenter = vX - (viewWidth / 2);
279 var percFromCenter = distFromCenter / viewWidth;
280 var percFromCenterSq = percFromCenter * percFromCenter;
281 vp.xPanWorldPosToViewPos(wX, 'center', viewWidth);
282 break;
283 case 119: // w
284 this.zoomBy_(1.5);
285 break;
286 case 115: // s
287 this.zoomBy_(1 / 1.5);
288 break;
289 case 103: // g
290 this.onGridToggle_(true);
291 break;
292 case 71: // G
293 this.onGridToggle_(false);
294 break;
295 case 87: // W
296 this.zoomBy_(10);
297 break;
298 case 83: // S
299 this.zoomBy_(1 / 10);
300 break;
301 case 97: // a
302 vp.panX += vp.xViewVectorToWorld(viewWidth * 0.1);
303 break;
304 case 100: // d
305 vp.panX -= vp.xViewVectorToWorld(viewWidth * 0.1);
306 break;
307 case 65: // A
308 vp.panX += vp.xViewVectorToWorld(viewWidth * 0.5);
309 break;
310 case 68: // D
311 vp.panX -= vp.xViewVectorToWorld(viewWidth * 0.5);
312 break;
313 }
314 },
315
316 // Not all keys send a keypress.
317 onKeydown_: function(e) {
318 switch (e.keyCode) {
319 case 37: // left arrow
320 this.selectPrevious_(e);
321 e.preventDefault();
322 break;
323 case 39: // right arrow
324 this.selectNext_(e);
325 e.preventDefault();
326 break;
327 case 9: // TAB
328 if (e.shiftKey)
329 this.selectPrevious_(e);
330 else
331 this.selectNext_(e);
332 e.preventDefault();
333 break;
334 }
335 },
336
337 /**
338 * Zoom in or out on the timeline by the given scale factor.
339 * @param {integer} scale The scale factor to apply. If <1, zooms out.
340 */
341 zoomBy_: function(scale) {
342 if (!this.firstCanvas)
343 return;
344 var vp = this.viewport_;
345 var viewWidth = this.firstCanvas.clientWidth;
346 var curMouseV = this.lastMouseViewPos_.x;
347 var curCenterW = vp.xViewToWorld(curMouseV);
348 vp.scaleX = vp.scaleX * scale;
349 vp.xPanWorldPosToViewPos(curCenterW, curMouseV, viewWidth);
350 },
351
352 /** Select the next slice on the timeline. Applies to each track. */
353 selectNext_: function(e) {
354 this.selectAdjoining_(e, true);
355 },
356
357 /** Select the previous slice on the timeline. Applies to each track. */
358 selectPrevious_: function(e) {
359 this.selectAdjoining_(e, false);
360 },
361
362 /**
363 * Helper for selection previous or next.
364 * @param {Event} The current event.
365 * @param {boolean} forwardp If true, select one forward (next).
366 * Else, select previous.
367 */
368 selectAdjoining_: function(e, forwardp) {
369 var i, track, slice, adjoining;
370 var selection = [];
371 // Clear old selection; try and select next.
372 for (i = 0; i < this.selection_.length; ++i) {
373 adjoining = undefined;
374 this.selection_[i].slice.selected = false;
375 var track = this.selection_[i].track;
376 var slice = this.selection_[i].slice;
377 if (slice) {
378 if (forwardp)
379 adjoining = track.pickNext(slice);
380 else
381 adjoining = track.pickPrevious(slice);
382 }
383 if (adjoining != undefined)
384 selection.push({track: track, slice: adjoining});
385 }
386 // Activate the new selection.
387 this.selection_ = selection;
388 for (i = 0; i < this.selection_.length; ++i)
389 this.selection_[i].slice.selected = true;
390 cr.dispatchSimpleEvent(this, 'selectionChange');
391 this.invalidate(); // Cause tracks to redraw.
392 e.preventDefault();
393 },
394
395 get keyHelp() {
396 return 'Keyboard shortcuts:\n' +
397 ' w/s : Zoom in/out (with shift: go faster)\n' +
398 ' a/d : Pan left/right\n' +
399 ' e : Center on mouse\n' +
400 ' g/G : Shows grid at the start/end of the selected task\n' +
401 ' <-,^TAB : Select previous event on current timeline\n' +
402 ' ->, TAB : Select next event on current timeline\n' +
403 '\n' +
404 'Dbl-click to zoom in; Shift dbl-click to zoom out\n';
405
406
407 },
408
409 get selection() {
410 return this.selection_;
411 },
412
413 get firstCanvas() {
414 return this.tracks_.firstChild ?
415 this.tracks_.firstChild.firstCanvas : undefined;
416 },
417
418 showDragBox_: function() {
419 this.dragBox_.hidden = false;
420 },
421
422 hideDragBox_: function() {
423 this.dragBox_.style.left = '-1000px';
424 this.dragBox_.style.top = '-1000px';
425 this.dragBox_.style.width = 0;
426 this.dragBox_.style.height = 0;
427 this.dragBox_.hidden = true;
428 },
429
430 get dragBoxVisible_() {
431 return this.dragBox_.hidden == false;
432 },
433
434 setDragBoxPosition_: function(eDown, eCur) {
435 var loX = Math.min(eDown.clientX, eCur.clientX);
436 var hiX = Math.max(eDown.clientX, eCur.clientX);
437 var loY = Math.min(eDown.clientY, eCur.clientY);
438 var hiY = Math.max(eDown.clientY, eCur.clientY);
439
440 this.dragBox_.style.left = loX + 'px';
441 this.dragBox_.style.top = loY + 'px';
442 this.dragBox_.style.width = hiX - loX + 'px';
443 this.dragBox_.style.height = hiY - loY + 'px';
444
445 var canv = this.firstCanvas;
446 var loWX = this.viewport_.xViewToWorld(loX - canv.offsetLeft);
447 var hiWX = this.viewport_.xViewToWorld(hiX - canv.offsetLeft);
448
449 var roundedDuration = Math.round((hiWX - loWX) * 100) / 100;
450 this.dragBox_.textContent = roundedDuration + 'ms';
451
452 var e = new cr.Event('selectionChanging');
453 e.loWX = loWX;
454 e.hiWX = hiWX;
455 this.dispatchEvent(e);
456 },
457
458 onGridToggle_: function(left) {
459 var tb;
460 if (left)
461 tb = Math.min.apply(Math, this.selection_.map(
462 function(x) { return x.slice.start; }));
463 else
464 tb = Math.max.apply(Math, this.selection_.map(
465 function(x) { return x.slice.end; }));
466
467 // Shift the timebase left until its just left of minTimestamp.
468 var numInterfvalsSinceStart = Math.ceil((tb - this.model_.minTimestamp) /
469 this.viewport_.gridStep_);
470 this.viewport_.gridTimebase = tb -
471 (numInterfvalsSinceStart + 1) * this.viewport_.gridStep_;
472 this.viewport_.gridEnabled = true;
473 },
474
475 onMouseDown_: function(e) {
476 if (e.clientX < this.offsetLeft ||
477 e.clientX >= this.offsetLeft + this.offsetWidth ||
478 e.clientY < this.offsetTop ||
479 e.clientY >= this.offsetTop + this.offsetHeight)
480 return;
481
482 var canv = this.firstCanvas;
483 var pos = {
484 x: e.clientX - canv.offsetLeft,
485 y: e.clientY - canv.offsetTop
486 };
487
488 var wX = this.viewport_.xViewToWorld(pos.x);
489
490 this.dragBeginEvent_ = e;
491 e.preventDefault();
492 },
493
494 onMouseMove_: function(e) {
495 if (!this.firstCanvas)
496 return;
497 var canv = this.firstCanvas;
498 var pos = {
499 x: e.clientX - canv.offsetLeft,
500 y: e.clientY - canv.offsetTop
501 };
502
503 // Remember position. Used during keyboard zooming.
504 this.lastMouseViewPos_ = pos;
505
506 // Initiate the drag box if needed.
507 if (this.dragBeginEvent_ && !this.dragBoxVisible_) {
508 this.showDragBox_();
509 this.setDragBoxPosition_(e, e);
510 }
511
512 // Update the drag box
513 if (this.dragBeginEvent_) {
514 this.setDragBoxPosition_(this.dragBeginEvent_, e);
515 }
516 },
517
518 onMouseUp_: function(e) {
519 var i;
520 if (this.dragBeginEvent_) {
521 // Stop the dragging.
522 this.hideDragBox_();
523 var eDown = this.dragBeginEvent_;
524 this.dragBeginEvent_ = null;
525
526 // Figure out extents of the drag.
527 var loX = Math.min(eDown.clientX, e.clientX);
528 var hiX = Math.max(eDown.clientX, e.clientX);
529 var loY = Math.min(eDown.clientY, e.clientY);
530 var hiY = Math.max(eDown.clientY, e.clientY);
531
532 // Convert to worldspace.
533 var canv = this.firstCanvas;
534 var loWX = this.viewport_.xViewToWorld(loX - canv.offsetLeft);
535 var hiWX = this.viewport_.xViewToWorld(hiX - canv.offsetLeft);
536
537 // Clear old selection.
538 for (i = 0; i < this.selection_.length; ++i) {
539 this.selection_[i].slice.selected = false;
540 }
541
542 // Figure out what has been hit.
543 var selection = [];
544 function addHit(type, track, slice) {
545 selection.push({track: track, slice: slice});
546 }
547 for (i = 0; i < this.tracks_.children.length; ++i) {
548 var track = this.tracks_.children[i];
549
550 // Only check tracks that insersect the rect.
551 var trackClientRect = track.getBoundingClientRect();
552 var a = Math.max(loY, trackClientRect.top);
553 var b = Math.min(hiY, trackClientRect.bottom);
554 if (a <= b) {
555 track.pickRange(loWX, hiWX, loY, hiY, addHit);
556 }
557 }
558 // Activate the new selection.
559 this.selection_ = selection;
560 cr.dispatchSimpleEvent(this, 'selectionChange');
561 for (i = 0; i < this.selection_.length; ++i) {
562 this.selection_[i].slice.selected = true;
563 }
564 this.invalidate(); // Cause tracks to redraw.
565 }
566 },
567
568 onDblClick_: function(e) {
569 var scale = 4;
570 if (e.shiftKey)
571 scale = 1 / scale;
572 this.zoomBy_(scale);
573 e.preventDefault();
574 },
575 };
576
577 /**
578 * The TimelineModel being viewed by the timeline
579 * @type {TimelineModel}
580 */
581 cr.defineProperty(Timeline, 'model', cr.PropertyKind.JS);
582
583 return {
584 Timeline: Timeline
585 };
586 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698