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

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: rebase 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 threads.sort(gpu.TimelineThread.compare);
212 for (var tI = 0; tI < threads.length; tI++) {
213 var thread = threads[tI];
214 var track = new TimelineThreadTrack();
215 track.thread = thread;
216 track.viewport = this.viewport_;
217 this.tracks_.appendChild(track);
218
219 }
220
221 this.needsViewportReset_ = true;
222 },
223
224 viewportChange_: function() {
225 this.invalidate();
226 },
227
228 invalidate: function() {
229 if (this.invalidatePending_)
230 return;
231 this.invalidatePending_ = true;
232 window.setTimeout(function() {
233 this.invalidatePending_ = false;
234 this.redrawAllTracks_();
235 }.bind(this), 0);
236 },
237
238 onResize: function() {
239 for (var i = 0; i < this.tracks_.children.length; ++i) {
240 var track = this.tracks_.children[i];
241 track.onResize();
242 }
243 },
244
245 redrawAllTracks_: function() {
246 if (this.needsViewportReset_ && this.clientWidth != 0) {
247 this.needsViewportReset_ = false;
248 /* update viewport */
249 var rangeTimestamp = this.model_.maxTimestamp -
250 this.model_.minTimestamp;
251 var w = this.firstCanvas.width;
252 console.log('viewport was reset with w=', w);
253 var scaleX = w / rangeTimestamp;
254 var panX = -this.model_.minTimestamp;
255 this.viewport_.setPanAndScale(panX, scaleX);
256 }
257 for (var i = 0; i < this.tracks_.children.length; ++i) {
258 this.tracks_.children[i].redraw();
259 }
260 },
261
262 updateChildViewports_: function() {
263 for (var cI = 0; cI < this.tracks_.children.length; ++cI) {
264 var child = this.tracks_.children[cI];
265 child.setViewport(this.panX, this.scaleX);
266 }
267 },
268
269 onKeypress_: function(e) {
270 var vp = this.viewport_;
271 if (!this.firstCanvas)
272 return;
273 var viewWidth = this.firstCanvas.clientWidth;
274 var curMouseV, curCenterW;
275 switch (e.keyCode) {
276 case 101: // e
277 var vX = this.lastMouseViewPos_.x;
278 var wX = vp.xViewToWorld(this.lastMouseViewPos_.x);
279 var distFromCenter = vX - (viewWidth / 2);
280 var percFromCenter = distFromCenter / viewWidth;
281 var percFromCenterSq = percFromCenter * percFromCenter;
282 vp.xPanWorldPosToViewPos(wX, 'center', viewWidth);
283 break;
284 case 119: // w
285 this.zoomBy_(1.5);
286 break;
287 case 115: // s
288 this.zoomBy_(1 / 1.5);
289 break;
290 case 103: // g
291 this.onGridToggle_(true);
292 break;
293 case 71: // G
294 this.onGridToggle_(false);
295 break;
296 case 87: // W
297 this.zoomBy_(10);
298 break;
299 case 83: // S
300 this.zoomBy_(1 / 10);
301 break;
302 case 97: // a
303 vp.panX += vp.xViewVectorToWorld(viewWidth * 0.1);
304 break;
305 case 100: // d
306 vp.panX -= vp.xViewVectorToWorld(viewWidth * 0.1);
307 break;
308 case 65: // A
309 vp.panX += vp.xViewVectorToWorld(viewWidth * 0.5);
310 break;
311 case 68: // D
312 vp.panX -= vp.xViewVectorToWorld(viewWidth * 0.5);
313 break;
314 }
315 },
316
317 // Not all keys send a keypress.
318 onKeydown_: function(e) {
319 switch (e.keyCode) {
320 case 37: // left arrow
321 this.selectPrevious_(e);
322 e.preventDefault();
323 break;
324 case 39: // right arrow
325 this.selectNext_(e);
326 e.preventDefault();
327 break;
328 case 9: // TAB
329 if (e.shiftKey)
330 this.selectPrevious_(e);
331 else
332 this.selectNext_(e);
333 e.preventDefault();
334 break;
335 }
336 },
337
338 /**
339 * Zoom in or out on the timeline by the given scale factor.
340 * @param {integer} scale The scale factor to apply. If <1, zooms out.
341 */
342 zoomBy_: function(scale) {
343 if (!this.firstCanvas)
344 return;
345 var vp = this.viewport_;
346 var viewWidth = this.firstCanvas.clientWidth;
347 var curMouseV = this.lastMouseViewPos_.x;
348 var curCenterW = vp.xViewToWorld(curMouseV);
349 vp.scaleX = vp.scaleX * scale;
350 vp.xPanWorldPosToViewPos(curCenterW, curMouseV, viewWidth);
351 },
352
353 /** Select the next slice on the timeline. Applies to each track. */
354 selectNext_: function(e) {
355 this.selectAdjoining_(e, true);
356 },
357
358 /** Select the previous slice on the timeline. Applies to each track. */
359 selectPrevious_: function(e) {
360 this.selectAdjoining_(e, false);
361 },
362
363 /**
364 * Helper for selection previous or next.
365 * @param {Event} The current event.
366 * @param {boolean} forwardp If true, select one forward (next).
367 * Else, select previous.
368 */
369 selectAdjoining_: function(e, forwardp) {
370 var i, track, slice, adjoining;
371 var selection = [];
372 // Clear old selection; try and select next.
373 for (i = 0; i < this.selection_.length; ++i) {
374 adjoining = undefined;
375 this.selection_[i].slice.selected = false;
376 var track = this.selection_[i].track;
377 var slice = this.selection_[i].slice;
378 if (slice) {
379 if (forwardp)
380 adjoining = track.pickNext(slice);
381 else
382 adjoining = track.pickPrevious(slice);
383 }
384 if (adjoining != undefined)
385 selection.push({track: track, slice: adjoining});
386 }
387 // Activate the new selection.
388 this.selection_ = selection;
389 for (i = 0; i < this.selection_.length; ++i)
390 this.selection_[i].slice.selected = true;
391 cr.dispatchSimpleEvent(this, 'selectionChange');
392 this.invalidate(); // Cause tracks to redraw.
393 e.preventDefault();
394 },
395
396 get keyHelp() {
397 return 'Keyboard shortcuts:\n' +
398 ' w/s : Zoom in/out (with shift: go faster)\n' +
399 ' a/d : Pan left/right\n' +
400 ' e : Center on mouse\n' +
401 ' g/G : Shows grid at the start/end of the selected task\n' +
402 ' <-,^TAB : Select previous event on current timeline\n' +
403 ' ->, TAB : Select next event on current timeline\n' +
404 '\n' +
405 'Dbl-click to zoom in; Shift dbl-click to zoom out\n';
406
407
408 },
409
410 get selection() {
411 return this.selection_;
412 },
413
414 get firstCanvas() {
415 return this.tracks_.firstChild ?
416 this.tracks_.firstChild.firstCanvas : undefined;
417 },
418
419 showDragBox_: function() {
420 this.dragBox_.hidden = false;
421 },
422
423 hideDragBox_: function() {
424 this.dragBox_.style.left = '-1000px';
425 this.dragBox_.style.top = '-1000px';
426 this.dragBox_.style.width = 0;
427 this.dragBox_.style.height = 0;
428 this.dragBox_.hidden = true;
429 },
430
431 get dragBoxVisible_() {
432 return this.dragBox_.hidden == false;
433 },
434
435 setDragBoxPosition_: function(eDown, eCur) {
436 var loX = Math.min(eDown.clientX, eCur.clientX);
437 var hiX = Math.max(eDown.clientX, eCur.clientX);
438 var loY = Math.min(eDown.clientY, eCur.clientY);
439 var hiY = Math.max(eDown.clientY, eCur.clientY);
440
441 this.dragBox_.style.left = loX + 'px';
442 this.dragBox_.style.top = loY + 'px';
443 this.dragBox_.style.width = hiX - loX + 'px';
444 this.dragBox_.style.height = hiY - loY + 'px';
445
446 var canv = this.firstCanvas;
447 var loWX = this.viewport_.xViewToWorld(loX - canv.offsetLeft);
448 var hiWX = this.viewport_.xViewToWorld(hiX - canv.offsetLeft);
449
450 var roundedDuration = Math.round((hiWX - loWX) * 100) / 100;
451 this.dragBox_.textContent = roundedDuration + 'ms';
452
453 var e = new cr.Event('selectionChanging');
454 e.loWX = loWX;
455 e.hiWX = hiWX;
456 this.dispatchEvent(e);
457 },
458
459 onGridToggle_: function(left) {
460 var tb;
461 if (left)
462 tb = Math.min.apply(Math, this.selection_.map(
463 function(x) { return x.slice.start; }));
464 else
465 tb = Math.max.apply(Math, this.selection_.map(
466 function(x) { return x.slice.end; }));
467
468 // Shift the timebase left until its just left of minTimestamp.
469 var numInterfvalsSinceStart = Math.ceil((tb - this.model_.minTimestamp) /
470 this.viewport_.gridStep_);
471 this.viewport_.gridTimebase = tb -
472 (numInterfvalsSinceStart + 1) * this.viewport_.gridStep_;
473 this.viewport_.gridEnabled = true;
474 },
475
476 onMouseDown_: function(e) {
477 if (e.clientX < this.offsetLeft ||
478 e.clientX >= this.offsetLeft + this.offsetWidth ||
479 e.clientY < this.offsetTop ||
480 e.clientY >= this.offsetTop + this.offsetHeight)
481 return;
482
483 var canv = this.firstCanvas;
484 var pos = {
485 x: e.clientX - canv.offsetLeft,
486 y: e.clientY - canv.offsetTop
487 };
488
489 var wX = this.viewport_.xViewToWorld(pos.x);
490
491 this.dragBeginEvent_ = e;
492 e.preventDefault();
493 },
494
495 onMouseMove_: function(e) {
496 if (!this.firstCanvas)
497 return;
498 var canv = this.firstCanvas;
499 var pos = {
500 x: e.clientX - canv.offsetLeft,
501 y: e.clientY - canv.offsetTop
502 };
503
504 // Remember position. Used during keyboard zooming.
505 this.lastMouseViewPos_ = pos;
506
507 // Initiate the drag box if needed.
508 if (this.dragBeginEvent_ && !this.dragBoxVisible_) {
509 this.showDragBox_();
510 this.setDragBoxPosition_(e, e);
511 }
512
513 // Update the drag box
514 if (this.dragBeginEvent_) {
515 this.setDragBoxPosition_(this.dragBeginEvent_, e);
516 }
517 },
518
519 onMouseUp_: function(e) {
520 var i;
521 if (this.dragBeginEvent_) {
522 // Stop the dragging.
523 this.hideDragBox_();
524 var eDown = this.dragBeginEvent_;
525 this.dragBeginEvent_ = null;
526
527 // Figure out extents of the drag.
528 var loX = Math.min(eDown.clientX, e.clientX);
529 var hiX = Math.max(eDown.clientX, e.clientX);
530 var loY = Math.min(eDown.clientY, e.clientY);
531 var hiY = Math.max(eDown.clientY, e.clientY);
532
533 // Convert to worldspace.
534 var canv = this.firstCanvas;
535 var loWX = this.viewport_.xViewToWorld(loX - canv.offsetLeft);
536 var hiWX = this.viewport_.xViewToWorld(hiX - canv.offsetLeft);
537
538 // Clear old selection.
539 for (i = 0; i < this.selection_.length; ++i) {
540 this.selection_[i].slice.selected = false;
541 }
542
543 // Figure out what has been hit.
544 var selection = [];
545 function addHit(type, track, slice) {
546 selection.push({track: track, slice: slice});
547 }
548 for (i = 0; i < this.tracks_.children.length; ++i) {
549 var track = this.tracks_.children[i];
550
551 // Only check tracks that insersect the rect.
552 var trackClientRect = track.getBoundingClientRect();
553 var a = Math.max(loY, trackClientRect.top);
554 var b = Math.min(hiY, trackClientRect.bottom);
555 if (a <= b) {
556 track.pickRange(loWX, hiWX, loY, hiY, addHit);
557 }
558 }
559 // Activate the new selection.
560 this.selection_ = selection;
561 cr.dispatchSimpleEvent(this, 'selectionChange');
562 for (i = 0; i < this.selection_.length; ++i) {
563 this.selection_[i].slice.selected = true;
564 }
565 this.invalidate(); // Cause tracks to redraw.
566 }
567 },
568
569 onDblClick_: function(e) {
570 var scale = 4;
571 if (e.shiftKey)
572 scale = 1 / scale;
573 this.zoomBy_(scale);
574 e.preventDefault();
575 },
576 };
577
578 /**
579 * The TimelineModel being viewed by the timeline
580 * @type {TimelineModel}
581 */
582 cr.defineProperty(Timeline, 'model', cr.PropertyKind.JS);
583
584 return {
585 Timeline: Timeline
586 };
587 });
OLDNEW
« no previous file with comments | « chrome/browser/resources/gpu_internals/timeline.css ('k') | chrome/browser/resources/gpu_internals/timeline_model.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698