Home | History | Annotate | Download | only in base
      1 <!DOCTYPE html>
      2 <!--
      3 Copyright (c) 2013 The Chromium Authors. All rights reserved.
      4 Use of this source code is governed by a BSD-style license that can be
      5 found in the LICENSE file.
      6 -->
      7 
      8 <link rel="import" href="/tracing/ui/base/event_presenter.html">
      9 <link rel="import" href="/tracing/base/sorted_array_utils.html">
     10 <link rel="import" href="/tracing/ui/base/elided_cache.html">
     11 
     12 <script>
     13 'use strict';
     14 
     15 /**
     16  * @fileoverview Provides various helper methods for drawing to a provided
     17  * canvas.
     18  */
     19 tr.exportTo('tr.ui.b', function() {
     20   var elidedTitleCache = new tr.ui.b.ElidedTitleCache();
     21   var ColorScheme = tr.b.ColorScheme;
     22   var colorsAsStrings = ColorScheme.colorsAsStrings;
     23 
     24   var EventPresenter = tr.ui.b.EventPresenter;
     25   var blackColorId = ColorScheme.getColorIdForReservedName('black');
     26 
     27   /**
     28    * This value is used to allow for consistent style UI elements.
     29    * Thread time visualisation uses a smaller rectangle that has this height.
     30    * @const
     31    */
     32   var THIN_SLICE_HEIGHT = 4;
     33 
     34   /**
     35    * This value is used to for performance considerations when drawing large
     36    * zoomed out traces that feature cpu time in the slices. If the waiting
     37    * width is less than the threshold, we only draw the rectangle as a solid.
     38    * @const
     39    */
     40   var SLICE_WAITING_WIDTH_DRAW_THRESHOLD = 3;
     41 
     42   /**
     43    * If the slice has mostly been waiting to be scheduled on the cpu, the
     44    * wall clock will be far greater than the cpu clock. Draw the slice
     45    * only as an idle slice, if the active width is not thicker than the
     46    * threshold.
     47    * @const
     48    */
     49   var SLICE_ACTIVE_WIDTH_DRAW_THRESHOLD = 1;
     50 
     51   /**
     52    * Should we elide text on trace labels?
     53    * Without eliding, text that is too wide isn't drawn at all.
     54    * Disable if you feel this causes a performance problem.
     55    * This is a default value that can be overridden in tracks for testing.
     56    * @const
     57    */
     58   var SHOULD_ELIDE_TEXT = true;
     59 
     60   /**
     61    * Draw the define line into |ctx|.
     62    *
     63    * @param {Context} ctx The context to draw into.
     64    * @param {float} x1 The start x position of the line.
     65    * @param {float} y1 The start y position of the line.
     66    * @param {float} x2 The end x position of the line.
     67    * @param {float} y2 The end y position of the line.
     68    */
     69   function drawLine(ctx, x1, y1, x2, y2) {
     70     ctx.moveTo(x1, y1);
     71     ctx.lineTo(x2, y2);
     72   }
     73 
     74   /**
     75    * Draw the defined triangle into |ctx|.
     76    *
     77    * @param {Context} ctx The context to draw into.
     78    * @param {float} x1 The first corner x.
     79    * @param {float} y1 The first corner y.
     80    * @param {float} x2 The second corner x.
     81    * @param {float} y2 The second corner y.
     82    * @param {float} x3 The third corner x.
     83    * @param {float} y3 The third corner y.
     84    */
     85   function drawTriangle(ctx, x1, y1, x2, y2, x3, y3) {
     86     ctx.beginPath();
     87     ctx.moveTo(x1, y1);
     88     ctx.lineTo(x2, y2);
     89     ctx.lineTo(x3, y3);
     90     ctx.closePath();
     91   }
     92 
     93   /**
     94    * Draw an arrow into |ctx|.
     95    *
     96    * @param {Context} ctx The context to draw into.
     97    * @param {float} x1 The shaft x.
     98    * @param {float} y1 The shaft y.
     99    * @param {float} x2 The head x.
    100    * @param {float} y2 The head y.
    101    * @param {float} arrowLength The length of the head.
    102    * @param {float} arrowWidth The width of the head.
    103    */
    104   function drawArrow(ctx, x1, y1, x2, y2, arrowLength, arrowWidth) {
    105     var dx = x2 - x1;
    106     var dy = y2 - y1;
    107     var len = Math.sqrt(dx * dx + dy * dy);
    108     var perc = (len - arrowLength) / len;
    109     var bx = x1 + perc * dx;
    110     var by = y1 + perc * dy;
    111     var ux = dx / len;
    112     var uy = dy / len;
    113     var ax = uy * arrowWidth;
    114     var ay = -ux * arrowWidth;
    115 
    116     ctx.beginPath();
    117     drawLine(ctx, x1, y1, x2, y2);
    118     ctx.stroke();
    119 
    120     drawTriangle(ctx,
    121         bx + ax, by + ay,
    122         x2, y2,
    123         bx - ax, by - ay);
    124     ctx.fill();
    125   }
    126 
    127   /**
    128    * Draw the provided slices to the screen.
    129    *
    130    * Each of the elements in |slices| must provide the follow methods:
    131    *   * start
    132    *   * duration
    133    *   * colorId
    134    *   * selected
    135    *
    136    * @param {Context} ctx The canvas context.
    137    * @param {TimelineDrawTransform} dt The draw transform.
    138    * @param {float} viewLWorld The left most point of the world viewport.
    139    * @param {float} viewRWorld The right most point of the world viewport.
    140    * @param {float} viewHeight The height of the viewport.
    141    * @param {Array} slices The slices to draw.
    142    * @param {bool} async Whether the slices are drawn with async style.
    143    */
    144   function drawSlices(ctx, dt, viewLWorld, viewRWorld, viewHeight, slices,
    145                       async) {
    146     var pixelRatio = window.devicePixelRatio || 1;
    147     var pixWidth = dt.xViewVectorToWorld(1);
    148     var height = viewHeight * pixelRatio;
    149 
    150     var darkRectHeight = THIN_SLICE_HEIGHT * pixelRatio;
    151 
    152     // Not enough space for both colors, use light color only.
    153     if (height < darkRectHeight)
    154       darkRectHeight = 0;
    155 
    156     var lightRectHeight = height - darkRectHeight;
    157 
    158     // Begin rendering in world space.
    159     ctx.save();
    160     dt.applyTransformToCanvas(ctx);
    161 
    162     var rect = new tr.ui.b.FastRectRenderer(
    163         ctx, 2 * pixWidth, 2 * pixWidth, colorsAsStrings);
    164     rect.setYandH(0, height);
    165 
    166     var lowSlice = tr.b.findLowIndexInSortedArray(
    167         slices,
    168         function(slice) { return slice.start + slice.duration; },
    169         viewLWorld);
    170 
    171     var hadTopLevel = false;
    172 
    173     for (var i = lowSlice; i < slices.length; ++i) {
    174       var slice = slices[i];
    175       var x = slice.start;
    176       if (x > viewRWorld)
    177         break;
    178 
    179       var w = pixWidth;
    180       if (slice.duration > 0) {
    181         w = Math.max(slice.duration, 0.000001);
    182         if (w < pixWidth)
    183           w = pixWidth;
    184       }
    185 
    186       var colorId = EventPresenter.getSliceColorId(slice);
    187       var alpha = EventPresenter.getSliceAlpha(slice, async);
    188       var lightAlpha = alpha * 0.70;
    189 
    190       if (async && slice.isTopLevel) {
    191         rect.setYandH(3, height - 3);
    192         hadTopLevel = true;
    193       } else {
    194         rect.setYandH(0, height);
    195       }
    196 
    197       // If cpuDuration is available, draw rectangles proportional to the
    198       // amount of cpu time taken.
    199       if (!slice.cpuDuration) {
    200         // No cpuDuration available, draw using only one alpha.
    201         rect.fillRect(x, w, colorId, alpha);
    202         continue;
    203       }
    204 
    205       var activeWidth = w * (slice.cpuDuration / slice.duration);
    206       var waitingWidth = w - activeWidth;
    207 
    208       // Check if we have enough screen space to draw the whole slice, with
    209       // both color tones.
    210       //
    211       // Truncate the activeWidth to 0 if it is less than 'threshold' pixels.
    212       if (activeWidth < SLICE_ACTIVE_WIDTH_DRAW_THRESHOLD * pixWidth) {
    213         activeWidth = 0;
    214         waitingWidth = w;
    215       }
    216 
    217       // Truncate the waitingWidth to 0 if it is less than 'threshold' pixels.
    218       if (waitingWidth < SLICE_WAITING_WIDTH_DRAW_THRESHOLD * pixWidth) {
    219         activeWidth = w;
    220         waitingWidth = 0;
    221       }
    222 
    223       // We now draw the two rectangles making up the event slice.
    224       // NOTE: The if statements are necessary for performance considerations.
    225       // We do not want to force draws, if the width of the rectangle is 0.
    226       //
    227       // First draw the solid color, representing the 'active' part.
    228       if (activeWidth > 0) {
    229         rect.fillRect(x, activeWidth, colorId, alpha);
    230       }
    231 
    232       // Next draw the two toned 'idle' part.
    233       // NOTE: Substracting pixWidth and drawing one extra pixel is done to
    234       // prevent drawing artifacts. Without it, the two parts of the slice,
    235       // ('active' and 'idle') may appear split apart.
    236       if (waitingWidth > 0) {
    237         // First draw the light toned top part.
    238         rect.setYandH(0, lightRectHeight);
    239         rect.fillRect(x + activeWidth - pixWidth,
    240             waitingWidth + pixWidth, colorId, lightAlpha);
    241         // Then the solid bottom half.
    242         rect.setYandH(lightRectHeight, darkRectHeight);
    243         rect.fillRect(x + activeWidth - pixWidth,
    244             waitingWidth + pixWidth, colorId, alpha);
    245         // Reset for the next slice.
    246         rect.setYandH(0, height);
    247       }
    248     }
    249     rect.flush();
    250 
    251     if (async && hadTopLevel) {
    252       // Draw a top border over async slices in order to visually separate
    253       // them from events above it.
    254       // See https://github.com/google/trace-viewer/issues/725.
    255       rect.setYandH(2, 1);
    256       for (var i = lowSlice; i < slices.length; ++i) {
    257         var slice = slices[i];
    258         var x = slice.start;
    259         if (x > viewRWorld)
    260           break;
    261 
    262         if (!slice.isTopLevel)
    263           continue;
    264 
    265         var w = pixWidth;
    266         if (slice.duration > 0) {
    267           w = Math.max(slice.duration, 0.000001);
    268           if (w < pixWidth)
    269             w = pixWidth;
    270         }
    271 
    272         rect.fillRect(x, w, blackColorId, 0.7);
    273       }
    274       rect.flush();
    275     }
    276 
    277     ctx.restore();
    278   }
    279 
    280   /**
    281    * Draw the provided instant slices as lines to the screen.
    282    *
    283    * Each of the elements in |slices| must provide the follow methods:
    284    *   * start
    285    *   * duration with value of 0.
    286    *   * colorId
    287    *   * selected
    288    *
    289    * @param {Context} ctx The canvas context.
    290    * @param {TimelineDrawTransform} dt The draw transform.
    291    * @param {float} viewLWorld The left most point of the world viewport.
    292    * @param {float} viewRWorld The right most point of the world viewport.
    293    * @param {float} viewHeight The height of the viewport.
    294    * @param {Array} slices The slices to draw.
    295    * @param {Numer} lineWidthInPixels The width of the lines.
    296    */
    297   function drawInstantSlicesAsLines(
    298       ctx, dt, viewLWorld, viewRWorld, viewHeight, slices, lineWidthInPixels) {
    299     var pixelRatio = window.devicePixelRatio || 1;
    300     var height = viewHeight * pixelRatio;
    301 
    302     var pixWidth = dt.xViewVectorToWorld(1);
    303 
    304     // Begin rendering in world space.
    305     ctx.save();
    306     ctx.lineWidth = pixWidth * lineWidthInPixels * pixelRatio;
    307     dt.applyTransformToCanvas(ctx);
    308     ctx.beginPath();
    309 
    310     var lowSlice = tr.b.findLowIndexInSortedArray(
    311         slices,
    312         function(slice) { return slice.start; },
    313         viewLWorld);
    314 
    315     for (var i = lowSlice; i < slices.length; ++i) {
    316       var slice = slices[i];
    317       var x = slice.start;
    318       if (x > viewRWorld)
    319         break;
    320 
    321       ctx.strokeStyle = EventPresenter.getInstantSliceColor(slice);
    322 
    323       ctx.beginPath();
    324       ctx.moveTo(x, 0);
    325       ctx.lineTo(x, height);
    326       ctx.stroke();
    327     }
    328     ctx.restore();
    329   }
    330 
    331   /**
    332    * Draws the labels for the given slices.
    333    *
    334    * The |slices| array must contain objects with the following API:
    335    *   * start
    336    *   * duration
    337    *   * title
    338    *   * didNotFinish (optional)
    339    *
    340    * @param {Context} ctx The graphics context.
    341    * @param {TimelineDrawTransform} dt The draw transform.
    342    * @param {float} viewLWorld The left most point of the world viewport.
    343    * @param {float} viewRWorld The right most point of the world viewport.
    344    * @param {Array} slices The slices to label.
    345    * @param {bool} async Whether the slice labels are drawn with async style.
    346    * @param {float} fontSize The font size.
    347    * @param {float} yOffset The font offset.
    348    */
    349   function drawLabels(ctx, dt, viewLWorld, viewRWorld, slices, async,
    350                       fontSize, yOffset) {
    351     var pixelRatio = window.devicePixelRatio || 1;
    352     var pixWidth = dt.xViewVectorToWorld(1);
    353 
    354     ctx.save();
    355 
    356     ctx.textAlign = 'center';
    357     ctx.textBaseline = 'top';
    358     ctx.font = (fontSize * pixelRatio) + 'px sans-serif';
    359 
    360     if (async)
    361       ctx.font = 'italic ' + ctx.font;
    362 
    363     var cY = yOffset * pixelRatio;
    364 
    365     var lowSlice = tr.b.findLowIndexInSortedArray(
    366         slices,
    367         function(slice) { return slice.start + slice.duration; },
    368         viewLWorld);
    369 
    370     // Don't render text until until it is 20px wide
    371     var quickDiscardThresshold = pixWidth * 20;
    372     for (var i = lowSlice; i < slices.length; ++i) {
    373       var slice = slices[i];
    374       if (slice.start > viewRWorld)
    375         break;
    376 
    377       if (slice.duration <= quickDiscardThresshold)
    378         continue;
    379 
    380       var title = slice.title +
    381           (slice.didNotFinish ? ' (Did Not Finish)' : '');
    382 
    383       var drawnTitle = title;
    384       var drawnWidth = elidedTitleCache.labelWidth(ctx, drawnTitle);
    385       var fullLabelWidth = elidedTitleCache.labelWidthWorld(
    386           ctx, drawnTitle, pixWidth);
    387       if (SHOULD_ELIDE_TEXT && fullLabelWidth > slice.duration) {
    388         var elidedValues = elidedTitleCache.get(
    389             ctx, pixWidth,
    390             drawnTitle, drawnWidth,
    391             slice.duration);
    392         drawnTitle = elidedValues.string;
    393         drawnWidth = elidedValues.width;
    394       }
    395 
    396       if (drawnWidth * pixWidth < slice.duration) {
    397         ctx.fillStyle = EventPresenter.getTextColor(slice);
    398         var cX = dt.xWorldToView(slice.start + 0.5 * slice.duration);
    399         ctx.fillText(drawnTitle, cX, cY, drawnWidth);
    400       }
    401     }
    402     ctx.restore();
    403   }
    404 
    405   return {
    406     drawSlices: drawSlices,
    407     drawInstantSlicesAsLines: drawInstantSlicesAsLines,
    408     drawLabels: drawLabels,
    409 
    410     drawLine: drawLine,
    411     drawTriangle: drawTriangle,
    412     drawArrow: drawArrow,
    413 
    414     elidedTitleCache_: elidedTitleCache,
    415 
    416     THIN_SLICE_HEIGHT: THIN_SLICE_HEIGHT
    417   };
    418 });
    419 </script>
    420