Home | History | Annotate | Download | only in editors
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.eclipse.gltrace.editors;
     18 
     19 import com.android.ide.eclipse.gltrace.GLProtoBuf.GLMessage.Function;
     20 import com.android.ide.eclipse.gltrace.model.GLCall;
     21 import com.android.ide.eclipse.gltrace.model.GLTrace;
     22 
     23 import org.eclipse.jface.resource.FontRegistry;
     24 import org.eclipse.swt.SWT;
     25 import org.eclipse.swt.events.MouseAdapter;
     26 import org.eclipse.swt.events.MouseEvent;
     27 import org.eclipse.swt.events.MouseMoveListener;
     28 import org.eclipse.swt.events.MouseTrackListener;
     29 import org.eclipse.swt.events.PaintEvent;
     30 import org.eclipse.swt.events.PaintListener;
     31 import org.eclipse.swt.graphics.Color;
     32 import org.eclipse.swt.graphics.FontData;
     33 import org.eclipse.swt.graphics.GC;
     34 import org.eclipse.swt.graphics.Image;
     35 import org.eclipse.swt.graphics.Point;
     36 import org.eclipse.swt.graphics.Rectangle;
     37 import org.eclipse.swt.widgets.Canvas;
     38 import org.eclipse.swt.widgets.Composite;
     39 import org.eclipse.swt.widgets.Display;
     40 import org.eclipse.swt.widgets.Event;
     41 import org.eclipse.swt.widgets.Listener;
     42 
     43 import java.util.ArrayList;
     44 import java.util.List;
     45 
     46 public class DurationMinimap extends Canvas {
     47     /** Default alpha value. */
     48     private static final int DEFAULT_ALPHA = 255;
     49 
     50     /** Alpha value for highlighting visible calls. */
     51     private static final int VISIBLE_CALLS_HIGHLIGHT_ALPHA = 50;
     52 
     53     /** Clamp call durations at this value. */
     54     private static final long CALL_DURATION_CLAMP = 20000;
     55 
     56     private static final String FONT_KEY = "default.font";      //$NON-NLS-1$
     57 
     58     /** Scale font size by this amount to get the max display length of call duration. */
     59     private static final int MAX_DURATION_LENGTH_SCALE = 6;
     60 
     61     /** List of GL Calls in the trace. */
     62     private List<GLCall> mCalls;
     63 
     64     /** Number of GL contexts in the trace. */
     65     private int mContextCount;
     66 
     67     /** Starting call index of currently displayed frame. */
     68     private int mStartCallIndex;
     69 
     70     /** Ending call index of currently displayed frame. */
     71     private int mEndCallIndex;
     72 
     73     /** The top index that is currently visible in the table. */
     74     private int mVisibleCallTopIndex;
     75 
     76     /** The bottom index that is currently visible in the table. */
     77     private int mVisibleCallBottomIndex;
     78 
     79     private Color mBackgroundColor;
     80     private Color mDurationLineColor;
     81     private Color mGlDrawColor;
     82     private Color mGlErrorColor;
     83     private Color mContextHeaderColor;
     84     private Color mVisibleCallsHighlightColor;
     85     private Color mMouseMarkerColor;
     86 
     87     private FontRegistry mFontRegistry;
     88     private int mFontWidth;
     89     private int mFontHeight;
     90 
     91     // back buffers used for double buffering
     92     private Image mBackBufferImage;
     93     private GC mBackBufferGC;
     94 
     95     // mouse state
     96     private boolean mMouseInSelf;
     97     private int mMouseY;
     98 
     99     // helper object used to position various items on screen
    100     private final PositionHelper mPositionHelper;
    101 
    102     public DurationMinimap(Composite parent, GLTrace trace) {
    103         super(parent, SWT.NO_BACKGROUND);
    104 
    105         setInput(trace);
    106 
    107         initializeColors();
    108         initializeFonts();
    109 
    110         mPositionHelper = new PositionHelper(
    111                 mFontHeight,
    112                 mContextCount,
    113                 mFontWidth * MAX_DURATION_LENGTH_SCALE, /* max display length for call. */
    114                 CALL_DURATION_CLAMP                     /* max duration */);
    115 
    116         addPaintListener(new PaintListener() {
    117             @Override
    118             public void paintControl(PaintEvent e) {
    119                 draw(e.display, e.gc);
    120             }
    121         });
    122 
    123         addListener(SWT.Resize, new Listener() {
    124             @Override
    125             public void handleEvent(Event event) {
    126                 controlResized();
    127             }
    128         });
    129 
    130         addMouseMoveListener(new MouseMoveListener() {
    131             @Override
    132             public void mouseMove(MouseEvent e) {
    133                 mouseMoved(e);
    134             }
    135         });
    136 
    137         addMouseListener(new MouseAdapter() {
    138             @Override
    139             public void mouseUp(MouseEvent e) {
    140                 mouseClicked(e);
    141             }
    142         });
    143 
    144         addMouseTrackListener(new MouseTrackListener() {
    145             @Override
    146             public void mouseHover(MouseEvent e) {
    147             }
    148 
    149             @Override
    150             public void mouseExit(MouseEvent e) {
    151                 mMouseInSelf = false;
    152                 redraw();
    153             }
    154 
    155             @Override
    156             public void mouseEnter(MouseEvent e) {
    157                 mMouseInSelf = true;
    158                 redraw();
    159             }
    160         });
    161     }
    162 
    163     public void setInput(GLTrace trace) {
    164         if (trace != null) {
    165             mCalls = trace.getGLCalls();
    166             mContextCount = trace.getContexts().size();
    167         } else {
    168             mCalls = null;
    169             mContextCount = 1;
    170         }
    171     }
    172 
    173     @Override
    174     public void dispose() {
    175         disposeColors();
    176         disposeBackBuffer();
    177         super.dispose();
    178     }
    179 
    180     private void initializeColors() {
    181         mBackgroundColor = new Color(getDisplay(), 0x33, 0x33, 0x33);
    182         mDurationLineColor = new Color(getDisplay(), 0x08, 0x51, 0x9c);
    183         mGlDrawColor = new Color(getDisplay(), 0x6b, 0xae, 0xd6);
    184         mContextHeaderColor = new Color(getDisplay(), 0xd1, 0xe5, 0xf0);
    185         mVisibleCallsHighlightColor = new Color(getDisplay(), 0xcc, 0xcc, 0xcc);
    186         mMouseMarkerColor = new Color(getDisplay(), 0xaa, 0xaa, 0xaa);
    187 
    188         mGlErrorColor = getDisplay().getSystemColor(SWT.COLOR_RED);
    189     }
    190 
    191     private void disposeColors() {
    192         mBackgroundColor.dispose();
    193         mDurationLineColor.dispose();
    194         mGlDrawColor.dispose();
    195         mContextHeaderColor.dispose();
    196         mVisibleCallsHighlightColor.dispose();
    197         mMouseMarkerColor.dispose();
    198     }
    199 
    200     private void initializeFonts() {
    201         mFontRegistry = new FontRegistry(getDisplay());
    202         mFontRegistry.put(FONT_KEY,
    203                 new FontData[] { new FontData("Arial", 8, SWT.NORMAL) });  //$NON-NLS-1$
    204 
    205         GC gc = new GC(getDisplay());
    206         gc.setFont(mFontRegistry.get(FONT_KEY));
    207         mFontWidth = gc.getFontMetrics().getAverageCharWidth();
    208         mFontHeight = gc.getFontMetrics().getHeight();
    209         gc.dispose();
    210     }
    211 
    212     private void initializeBackBuffer() {
    213         Rectangle clientArea = getClientArea();
    214 
    215         if (clientArea.width == 0 || clientArea.height == 0) {
    216             mBackBufferImage = null;
    217             mBackBufferGC = null;
    218             return;
    219         }
    220 
    221         mBackBufferImage = new Image(getDisplay(),
    222                 clientArea.width,
    223                 clientArea.height);
    224         mBackBufferGC = new GC(mBackBufferImage);
    225     }
    226 
    227     private void disposeBackBuffer() {
    228         if (mBackBufferImage != null) {
    229             mBackBufferImage.dispose();
    230             mBackBufferImage = null;
    231         }
    232 
    233         if (mBackBufferGC != null) {
    234             mBackBufferGC.dispose();
    235             mBackBufferGC = null;
    236         }
    237     }
    238 
    239     private void mouseMoved(MouseEvent e) {
    240         mMouseY = e.y;
    241         redraw();
    242     }
    243 
    244     private void mouseClicked(MouseEvent e) {
    245         if (mMouseInSelf) {
    246             int selIndex = mPositionHelper.getCallAt(mMouseY);
    247             sendCallSelectedEvent(selIndex);
    248             redraw();
    249         }
    250     }
    251 
    252     private void draw(Display display, GC gc) {
    253         if (mBackBufferImage == null) {
    254             initializeBackBuffer();
    255         }
    256 
    257         if (mBackBufferImage == null) {
    258             return;
    259         }
    260 
    261         // draw contents onto the back buffer
    262         drawBackground(mBackBufferGC, mBackBufferImage.getBounds());
    263         drawContextHeaders(mBackBufferGC);
    264         drawCallDurations(mBackBufferGC);
    265         drawVisibleCallHighlights(mBackBufferGC);
    266         drawMouseMarkers(mBackBufferGC);
    267 
    268         // finally copy over the rendered back buffer onto screen
    269         int width = getClientArea().width;
    270         int height = getClientArea().height;
    271         gc.drawImage(mBackBufferImage,
    272                 0, 0, width, height,
    273                 0, 0, width, height);
    274     }
    275 
    276     private void drawBackground(GC gc, Rectangle bounds) {
    277         gc.setBackground(mBackgroundColor);
    278         gc.fillRectangle(bounds);
    279     }
    280 
    281     private void drawContextHeaders(GC gc) {
    282         if (mContextCount <= 1) {
    283             return;
    284         }
    285 
    286         gc.setForeground(mContextHeaderColor);
    287         gc.setFont(mFontRegistry.get(FONT_KEY));
    288         for (int i = 0; i < mContextCount; i++) {
    289             Point p = mPositionHelper.getHeaderLocation(i);
    290             gc.drawText("CTX" + Integer.toString(i), p.x, p.y);
    291         }
    292     }
    293 
    294     /** Draw the call durations as a sequence of lines.
    295      *
    296      * Calls are arranged on the y-axis based on the sequence in which they were originally
    297      * called by the application. If the display height is lesser than the number of calls, then
    298      * not every call is shown - the calls are underscanned based the height of the display.
    299      *
    300      * The x-axis shows two pieces of information: the duration of the call, and the context
    301      * in which the call was made. The duration controls how long the displayed line is, and
    302      * the context controls the starting offset of the line.
    303      */
    304     private void drawCallDurations(GC gc) {
    305         if (mCalls == null || mCalls.size() < mEndCallIndex) {
    306             return;
    307         }
    308 
    309         gc.setBackground(mDurationLineColor);
    310 
    311         int callUnderScan = mPositionHelper.getCallUnderScanValue();
    312         for (int i = mStartCallIndex; i < mEndCallIndex; i += callUnderScan) {
    313             boolean resetColor = false;
    314             GLCall c = mCalls.get(i);
    315 
    316             long duration = c.getWallDuration();
    317 
    318             if (c.hasErrors()) {
    319                 gc.setBackground(mGlErrorColor);
    320                 resetColor = true;
    321 
    322                 // If the call has any errors, we want it to be visible in the minimap
    323                 // regardless of how long it took.
    324                 duration = mPositionHelper.getMaxDuration();
    325             } else if (c.getFunction() == Function.glDrawArrays
    326                     || c.getFunction() == Function.glDrawElements
    327                     || c.getFunction() == Function.eglSwapBuffers) {
    328                 gc.setBackground(mGlDrawColor);
    329                 resetColor = true;
    330 
    331                 // render all draw calls & swap buffer at max length
    332                 duration = mPositionHelper.getMaxDuration();
    333             }
    334 
    335             Rectangle bounds = mPositionHelper.getDurationBounds(
    336                     i - mStartCallIndex,
    337                     c.getContextId(),
    338                     duration);
    339             gc.fillRectangle(bounds);
    340 
    341             if (resetColor) {
    342                 gc.setBackground(mDurationLineColor);
    343             }
    344         }
    345     }
    346 
    347     /**
    348      * Draw a bounding box that highlights the currently visible range of calls in the
    349      * {@link GLFunctionTraceViewer} table.
    350      */
    351     private void drawVisibleCallHighlights(GC gc) {
    352         gc.setAlpha(VISIBLE_CALLS_HIGHLIGHT_ALPHA);
    353         gc.setBackground(mVisibleCallsHighlightColor);
    354         gc.fillRectangle(mPositionHelper.getBoundsFramingCalls(
    355                 mVisibleCallTopIndex - mStartCallIndex,
    356                 mVisibleCallBottomIndex - mStartCallIndex));
    357         gc.setAlpha(DEFAULT_ALPHA);
    358     }
    359 
    360     private void drawMouseMarkers(GC gc) {
    361         if (!mMouseInSelf) {
    362             return;
    363         }
    364 
    365         if (mPositionHelper.getCallAt(mMouseY) < 0) {
    366             return;
    367         }
    368 
    369         gc.setForeground(mMouseMarkerColor);
    370         gc.drawLine(0, mMouseY, getClientArea().width, mMouseY);
    371     }
    372 
    373     private void controlResized() {
    374         // regenerate back buffer on size changes
    375         disposeBackBuffer();
    376         initializeBackBuffer();
    377 
    378         redraw();
    379     }
    380 
    381     public int getMinimumWidth() {
    382         return mPositionHelper.getMinimumWidth();
    383     }
    384 
    385     /** Set the GL Call start and end indices for currently displayed frame. */
    386     public void setCallRangeForCurrentFrame(int startCallIndex, int endCallIndex) {
    387         mStartCallIndex = startCallIndex;
    388         mEndCallIndex = endCallIndex;
    389         mPositionHelper.updateCallDensity(mEndCallIndex - mStartCallIndex, getClientArea().height);
    390         redraw();
    391     }
    392 
    393     /**
    394      * Set the call range that is currently visible in the {@link GLFunctionTraceViewer} table.
    395      * @param visibleTopIndex index of call currently visible at the top of the table.
    396      * @param visibleBottomIndex index of call currently visible at the bottom of the table.
    397      */
    398     public void setVisibleCallRange(int visibleTopIndex, int visibleBottomIndex) {
    399         mVisibleCallTopIndex = visibleTopIndex;
    400         mVisibleCallBottomIndex = visibleBottomIndex;
    401         redraw();
    402     }
    403 
    404     public interface ICallSelectionListener {
    405         void callSelected(int selectedCallIndex);
    406     }
    407 
    408     private List<ICallSelectionListener> mListeners = new ArrayList<ICallSelectionListener>();
    409 
    410     public void addCallSelectionListener(ICallSelectionListener l) {
    411         mListeners.add(l);
    412     }
    413 
    414     private void sendCallSelectedEvent(int selectedCall) {
    415         for (ICallSelectionListener l : mListeners) {
    416             l.callSelected(selectedCall);
    417         }
    418     }
    419 
    420     /** Utility class to help with the positioning and sizes of elements in the canvas. */
    421     private static class PositionHelper {
    422         /** Left Margin after which duration lines are drawn. */
    423         private static final int LEFT_MARGIN = 5;
    424 
    425         /** Top margin after which header is drawn. */
    426         private static final int TOP_MARGIN = 5;
    427 
    428         /** # of pixels of padding between duration markers for different contexts. */
    429         private static final int CONTEXT_PADDING = 10;
    430 
    431         private final int mHeaderMargin;
    432         private final int mContextCount;
    433         private final int mMaxDurationLength;
    434         private final long mMaxDuration;
    435         private final double mScale;
    436 
    437         private int mCallCount;
    438         private int mNumCallsPerPixel = 1;
    439 
    440         public PositionHelper(int fontHeight, int contextCount,
    441                 int maxDurationLength, long maxDuration) {
    442             mContextCount = contextCount;
    443             mMaxDurationLength = maxDurationLength;
    444             mMaxDuration = maxDuration;
    445             mScale = (double) maxDurationLength / maxDuration;
    446 
    447             // header region is present only there are multiple contexts
    448             if (mContextCount > 1) {
    449                 mHeaderMargin = fontHeight * 3;
    450             } else {
    451                 mHeaderMargin = 0;
    452             }
    453         }
    454 
    455         /** Get the minimum width of the canvas. */
    456         public int getMinimumWidth() {
    457             return LEFT_MARGIN + (mMaxDurationLength + CONTEXT_PADDING) * mContextCount;
    458         }
    459 
    460         /** Get the bounds for a call duration line. */
    461         public Rectangle getDurationBounds(int callIndex, int context, long duration) {
    462             if (duration <= 0) {
    463                 duration = 1;
    464             } else if (duration > mMaxDuration) {
    465                 duration = mMaxDuration;
    466             }
    467 
    468             int x = LEFT_MARGIN + ((mMaxDurationLength + CONTEXT_PADDING) * context);
    469             int y = (callIndex/mNumCallsPerPixel) + TOP_MARGIN + mHeaderMargin;
    470             int w = (int) (duration * mScale);
    471             int h = 1;
    472 
    473             return new Rectangle(x, y, w, h);
    474         }
    475 
    476         public long getMaxDuration() {
    477             return mMaxDuration;
    478         }
    479 
    480         /** Get the bounds for calls spanning given range. */
    481         public Rectangle getBoundsFramingCalls(int startCallIndex, int endCallIndex) {
    482             if (startCallIndex >= 0 && endCallIndex >= startCallIndex
    483                     && endCallIndex <= mCallCount) {
    484                 int x = LEFT_MARGIN;
    485                 int y = (startCallIndex/mNumCallsPerPixel) + TOP_MARGIN + mHeaderMargin;
    486                 int w = ((mMaxDurationLength + CONTEXT_PADDING) * mContextCount);
    487                 int h = (endCallIndex - startCallIndex)/mNumCallsPerPixel;
    488             return new Rectangle(x, y, w, h);
    489             } else {
    490                 return new Rectangle(0, 0, 0, 0);
    491             }
    492         }
    493 
    494         public Point getHeaderLocation(int context) {
    495             int x = LEFT_MARGIN + ((mMaxDurationLength + CONTEXT_PADDING) * context);
    496             return new Point(x, TOP_MARGIN);
    497         }
    498 
    499         /** Update the call density based on the number of calls to be displayed and
    500          * the available height to display them in. */
    501         public void updateCallDensity(int callCount, int displayHeight) {
    502             mCallCount = callCount;
    503 
    504             if (displayHeight <= 0) {
    505                 displayHeight = callCount + 1;
    506             }
    507 
    508             mNumCallsPerPixel = (callCount / displayHeight) + 1;
    509         }
    510 
    511         /** Get the underscan value. In cases where there are more calls to be displayed
    512          * than there are availble pixels, we only display 1 out of every underscan calls. */
    513         public int getCallUnderScanValue() {
    514             return mNumCallsPerPixel;
    515         }
    516 
    517         /** Get the index of the call at given y offset. */
    518         public int getCallAt(int y) {
    519             if (!isWithinBounds(y)) {
    520                 return -1;
    521             }
    522 
    523             Rectangle displayBounds = getBoundsFramingCalls(0, mCallCount);
    524             return (y - displayBounds.y) * mNumCallsPerPixel;
    525         }
    526 
    527         /** Does the provided y offset map to a valid call? */
    528         private boolean isWithinBounds(int y) {
    529             Rectangle displayBounds = getBoundsFramingCalls(0, mCallCount);
    530             if (y < displayBounds.y) {
    531                 return false;
    532             }
    533 
    534             if (y > (displayBounds.y + displayBounds.height)) {
    535                 return false;
    536             }
    537 
    538             return true;
    539         }
    540     }
    541 }
    542