Home | History | Annotate | Download | only in gpu_internals
      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 /**
      7  * @fileoverview Renders an array of slices into the provided div,
      8  * using a child canvas element. Uses a FastRectRenderer to draw only
      9  * the visible slices.
     10  */
     11 cr.define('gpu', function() {
     12 
     13   const palletteBase = [
     14     {r: 0x45, g: 0x85, b: 0xaa},
     15     {r: 0xdc, g: 0x73, b: 0xa8},
     16     {r: 0x77, g: 0xb6, b: 0x94},
     17     {r: 0x23, g: 0xae, b: 0x6e},
     18     {r: 0x76, g: 0x5d, b: 0x9e},
     19     {r: 0x48, g: 0xd8, b: 0xfb},
     20     {r: 0xa9, g: 0xd7, b: 0x93},
     21     {r: 0x7c, g: 0x2d, b: 0x52},
     22     {r: 0x69, g: 0xc2, b: 0x75},
     23     {r: 0x76, g: 0xcf, b: 0xee},
     24     {r: 0x3d, g: 0x85, b: 0xd1},
     25     {r: 0x71, g: 0x0b, b: 0x54}];
     26 
     27   function brighten(c) {
     28     return {r: Math.min(255, c.r + Math.floor(c.r * 0.45)),
     29       g: Math.min(255, c.g + Math.floor(c.g * 0.45)),
     30       b: Math.min(255, c.b + Math.floor(c.b * 0.45))};
     31   }
     32   function colorToString(c) {
     33     return 'rgb(' + c.r + ',' + c.g + ',' + c.b + ')';
     34   }
     35 
     36   const selectedIdBoost = palletteBase.length;
     37 
     38   const pallette = palletteBase.concat(palletteBase.map(brighten)).
     39       map(colorToString);
     40 
     41   var textWidthMap = { };
     42   function quickMeasureText(ctx, text) {
     43     var w = textWidthMap[text];
     44     if (!w) {
     45       w = ctx.measureText(text).width;
     46       textWidthMap[text] = w;
     47     }
     48     return w;
     49   }
     50 
     51   /**
     52    * Generic base class for timeline tracks
     53    */
     54   TimelineThreadTrack = cr.ui.define('div');
     55   TimelineThreadTrack.prototype = {
     56     __proto__: HTMLDivElement.prototype,
     57 
     58     decorate: function() {
     59       this.className = 'timeline-thread-track';
     60     },
     61 
     62     set thread(thread) {
     63       this.thread_ = thread;
     64       this.updateChildTracks_();
     65     },
     66 
     67     set viewport(v) {
     68       this.viewport_ = v;
     69       for (var i = 0; i < this.tracks_.length; i++)
     70         this.tracks_[i].viewport = v;
     71       this.invalidate();
     72     },
     73 
     74     invalidate: function() {
     75       if (this.parentNode)
     76         this.parentNode.invalidate();
     77     },
     78 
     79     onResize: function() {
     80       for (var i = 0; i < this.tracks_.length; i++)
     81         this.tracks_[i].onResize();
     82     },
     83 
     84     get firstCanvas() {
     85       if (this.tracks_.length)
     86         return this.tracks_[0].firstCanvas;
     87       return undefined;
     88     },
     89 
     90     redraw: function() {
     91       for (var i = 0; i < this.tracks_.length; i++)
     92         this.tracks_[i].redraw();
     93     },
     94 
     95     updateChildTracks_: function() {
     96       this.textContent = '';
     97       this.tracks_ = [];
     98       if (this.thread_) {
     99         for (var srI = 0; srI < this.thread_.subRows.length; ++srI) {
    100           var track = new TimelineSliceTrack();
    101 
    102           if (srI == 0)
    103             track.heading = this.thread_.parent.pid + ': ' +
    104                 this.thread_.tid + ': ';
    105           else
    106             track.heading = '';
    107           track.slices = this.thread_.subRows[srI];
    108           track.viewport = this.viewport_;
    109 
    110           this.tracks_.push(track);
    111           this.appendChild(track);
    112         }
    113       }
    114     },
    115 
    116     /**
    117      * Picks a slice, if any, at a given location.
    118      * @param {number} wX X location to search at, in worldspace.
    119      * @param {number} wY Y location to search at, in offset space.
    120      *     offset space.
    121      * @param {function():*} onHitCallback Callback to call with the slice,
    122      *     if one is found.
    123      * @return {boolean} true if a slice was found, otherwise false.
    124      */
    125     pick: function(wX, wY, onHitCallback) {
    126       for (var i = 0; i < this.tracks_.length; i++) {
    127         var track = this.tracks_[i];
    128         if (wY >= track.offsetTop && wY < track.offsetTop + track.offsetHeight)
    129           return track.pick(wX, onHitCallback);
    130       }
    131       return false;
    132     },
    133 
    134     /**
    135      * Finds slices intersecting the given interval.
    136      * @param {number} loWX Lower X bound of the interval to search, in
    137      *     worldspace.
    138      * @param {number} hiWX Upper X bound of the interval to search, in
    139      *     worldspace.
    140      * @param {number} loY Lower Y bound of the interval to search, in
    141      *     offset space.
    142      * @param {number} hiY Upper Y bound of the interval to search, in
    143      *     offset space.
    144      * @param {function():*} onHitCallback Function to call for each slice
    145      *     intersecting the interval.
    146      */
    147     pickRange: function(loWX, hiWX, loY, hiY, onHitCallback) {
    148       for (var i = 0; i < this.tracks_.length; i++) {
    149         var a = Math.max(loY, this.tracks_[i].offsetTop);
    150         var b = Math.min(hiY, this.tracks_[i].offsetTop +
    151                          this.tracks_[i].offsetHeight);
    152         if (a <= b)
    153           this.tracks_[i].pickRange(loWX, hiWX, loY, hiY, onHitCallback);
    154       }
    155     }
    156   };
    157 
    158   /**
    159    * Creates a new timeline track div element
    160    * @constructor
    161    * @extends {HTMLDivElement}
    162    */
    163   TimelineSliceTrack = cr.ui.define('div');
    164 
    165   TimelineSliceTrack.prototype = {
    166     __proto__: HTMLDivElement.prototype,
    167 
    168     decorate: function() {
    169       this.className = 'timeline-slice-track';
    170       this.slices_ = null;
    171 
    172       this.titleDiv_ = document.createElement('div');
    173       this.titleDiv_.className = 'timeline-slice-track-title';
    174       this.appendChild(this.titleDiv_);
    175 
    176       this.canvasContainer_ = document.createElement('div');
    177       this.canvasContainer_.className = 'timeline-slice-track-canvas-container';
    178       this.appendChild(this.canvasContainer_);
    179       this.canvas_ = document.createElement('canvas');
    180       this.canvas_.className = 'timeline-slice-track-canvas';
    181       this.canvasContainer_.appendChild(this.canvas_);
    182 
    183       this.ctx_ = this.canvas_.getContext('2d');
    184     },
    185 
    186     set heading(text) {
    187       this.titleDiv_.textContent = text;
    188     },
    189 
    190     set slices(slices) {
    191       this.slices_ = slices;
    192       this.invalidate();
    193     },
    194 
    195     set viewport(v) {
    196       this.viewport_ = v;
    197       this.invalidate();
    198     },
    199 
    200     invalidate: function() {
    201       if (this.parentNode)
    202         this.parentNode.invalidate();
    203     },
    204 
    205     get firstCanvas() {
    206       return this.canvas_;
    207     },
    208 
    209     onResize: function() {
    210       this.canvas_.width = this.canvasContainer_.clientWidth;
    211       this.canvas_.height = this.canvasContainer_.clientHeight;
    212       this.invalidate();
    213     },
    214 
    215     redraw: function() {
    216       if (!this.viewport_)
    217         return;
    218       var ctx = this.ctx_;
    219       var canvasW = this.canvas_.width;
    220       var canvasH = this.canvas_.height;
    221 
    222       ctx.clearRect(0, 0, canvasW, canvasH);
    223 
    224       // culling...
    225       var vp = this.viewport_;
    226       var pixWidth = vp.xViewVectorToWorld(1);
    227       var viewLWorld = vp.xViewToWorld(0);
    228       var viewRWorld = vp.xViewToWorld(this.width);
    229 
    230       // begin rendering in world space
    231       ctx.save();
    232       vp.applyTransformToCanavs(ctx);
    233 
    234       // tracks
    235       var tr = new gpu.FastRectRenderer(ctx, viewLWorld, 2 * pixWidth,
    236                                         2 * pixWidth, viewRWorld, pallette);
    237       tr.setYandH(0, canvasH);
    238       var slices = this.slices_;
    239       for (var i = 0; i < slices.length; ++i) {
    240         var slice = slices[i];
    241         var x = slice.start;
    242         var w = slice.duration;
    243         var colorId;
    244         colorId = slice.selected ?
    245             slice.colorId + selectedIdBoost :
    246             slice.colorId;
    247 
    248         if (w < pixWidth)
    249           w = pixWidth;
    250         tr.fillRect(x, w, colorId);
    251       }
    252       tr.flush();
    253       ctx.restore();
    254 
    255       // labels
    256       ctx.textAlign = 'center';
    257       ctx.textBaseline = 'top';
    258       ctx.font = '10px sans-serif';
    259       ctx.strokeStyle = 'rgb(0,0,0)';
    260       ctx.fillStyle = 'rgb(0,0,0)';
    261       var quickDiscardThresshold = pixWidth * 20; // dont render until 20px wide
    262       for (var i = 0; i < slices.length; ++i) {
    263         var slice = slices[i];
    264         if (slice.duration > quickDiscardThresshold) {
    265           var labelWidth = quickMeasureText(ctx, slice.title) + 2;
    266           var labelWidthWorld = pixWidth * labelWidth;
    267           if (labelWidthWorld < slice.duration) {
    268             var cX = vp.xWorldToView(slice.start + 0.5 * slice.duration);
    269             ctx.fillText(slice.title, cX, 2.5);
    270           }
    271         }
    272       }
    273     },
    274 
    275     /**
    276      * Picks a slice, if any, at a given location.
    277      * @param {number} wX X location to search at, in worldspace.
    278      * @param {number} wY Y location to search at, in offset space.
    279      *     offset space.
    280      * @param {function():*} onHitCallback Callback to call with the slice,
    281      *     if one is found.
    282      * @return {boolean} true if a slice was found, otherwise false.
    283      */
    284     pick: function(wX, wY, onHitCallback) {
    285       if (wY < this.offsetTop || wY >= this.offsetTop + this.offsetHeight)
    286         return false;
    287       var x = gpu.findLowIndexInSortedIntervals(this.slices_,
    288           function(x) { return x.start; },
    289           function(x) { return x.duration; },
    290           wX);
    291       if (x >= 0 && x < this.slices_.length) {
    292         onHitCallback('slice', this, this.slices_[x]);
    293         return true;
    294       }
    295       return false;
    296     },
    297 
    298     /**
    299      * Finds slices intersecting the given interval.
    300      * @param {number} loWX Lower X bound of the interval to search, in
    301      *     worldspace.
    302      * @param {number} hiWX Upper X bound of the interval to search, in
    303      *     worldspace.
    304      * @param {number} loY Lower Y bound of the interval to search, in
    305      *     offset space.
    306      * @param {number} hiY Upper Y bound of the interval to search, in
    307      *     offset space.
    308      * @param {function():*} onHitCallback Function to call for each slice
    309      *     intersecting the interval.
    310      */
    311     pickRange: function(loWX, hiWX, loY, hiY, onHitCallback) {
    312       var a = Math.max(loY, this.offsetTop);
    313       var b = Math.min(hiY, this.offsetTop + this.offsetHeight);
    314       if (a > b)
    315         return;
    316 
    317       function onPickHit(slice) {
    318         onHitCallback('slice', this, slice);
    319       }
    320       gpu.iterateOverIntersectingIntervals(this.slices_,
    321           function(x) { return x.start; },
    322           function(x) { return x.duration; },
    323           loWX, hiWX,
    324           onPickHit);
    325     }
    326 
    327   };
    328 
    329   return {
    330     TimelineSliceTrack: TimelineSliceTrack,
    331     TimelineThreadTrack: TimelineThreadTrack
    332   };
    333 });
    334