• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 final List<GLCall> mCalls;
63 
64     /** Number of GL contexts in the trace. */
65     private final 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 
DurationMinimap(Composite parent, GLTrace trace)102     public DurationMinimap(Composite parent, GLTrace trace) {
103         super(parent, SWT.NO_BACKGROUND);
104 
105         mCalls = trace.getGLCalls();
106         mContextCount = trace.getContexts().size();
107 
108         initializeColors();
109         initializeFonts();
110 
111         mPositionHelper = new PositionHelper(
112                 mFontHeight,
113                 mContextCount,
114                 mFontWidth * MAX_DURATION_LENGTH_SCALE, /* max display length for call. */
115                 CALL_DURATION_CLAMP                     /* max duration */);
116 
117         addPaintListener(new PaintListener() {
118             @Override
119             public void paintControl(PaintEvent e) {
120                 draw(e.display, e.gc);
121             }
122         });
123 
124         addListener(SWT.Resize, new Listener() {
125             @Override
126             public void handleEvent(Event event) {
127                 controlResized();
128             }
129         });
130 
131         addMouseMoveListener(new MouseMoveListener() {
132             @Override
133             public void mouseMove(MouseEvent e) {
134                 mouseMoved(e);
135             }
136         });
137 
138         addMouseListener(new MouseAdapter() {
139             @Override
140             public void mouseUp(MouseEvent e) {
141                 mouseClicked(e);
142             }
143         });
144 
145         addMouseTrackListener(new MouseTrackListener() {
146             @Override
147             public void mouseHover(MouseEvent e) {
148             }
149 
150             @Override
151             public void mouseExit(MouseEvent e) {
152                 mMouseInSelf = false;
153                 redraw();
154             }
155 
156             @Override
157             public void mouseEnter(MouseEvent e) {
158                 mMouseInSelf = true;
159                 redraw();
160             }
161         });
162     }
163 
164     @Override
dispose()165     public void dispose() {
166         disposeColors();
167         disposeBackBuffer();
168         super.dispose();
169     }
170 
initializeColors()171     private void initializeColors() {
172         mBackgroundColor = new Color(getDisplay(), 0x33, 0x33, 0x33);
173         mDurationLineColor = new Color(getDisplay(), 0x08, 0x51, 0x9c);
174         mGlDrawColor = new Color(getDisplay(), 0x6b, 0xae, 0xd6);
175         mContextHeaderColor = new Color(getDisplay(), 0xd1, 0xe5, 0xf0);
176         mVisibleCallsHighlightColor = new Color(getDisplay(), 0xcc, 0xcc, 0xcc);
177         mMouseMarkerColor = new Color(getDisplay(), 0xaa, 0xaa, 0xaa);
178 
179         mGlErrorColor = getDisplay().getSystemColor(SWT.COLOR_RED);
180     }
181 
disposeColors()182     private void disposeColors() {
183         mBackgroundColor.dispose();
184         mDurationLineColor.dispose();
185         mGlDrawColor.dispose();
186         mContextHeaderColor.dispose();
187         mVisibleCallsHighlightColor.dispose();
188         mMouseMarkerColor.dispose();
189     }
190 
initializeFonts()191     private void initializeFonts() {
192         mFontRegistry = new FontRegistry(getDisplay());
193         mFontRegistry.put(FONT_KEY,
194                 new FontData[] { new FontData("Arial", 8, SWT.NORMAL) });  //$NON-NLS-1$
195 
196         GC gc = new GC(getDisplay());
197         gc.setFont(mFontRegistry.get(FONT_KEY));
198         mFontWidth = gc.getFontMetrics().getAverageCharWidth();
199         mFontHeight = gc.getFontMetrics().getHeight();
200         gc.dispose();
201     }
202 
initializeBackBuffer()203     private void initializeBackBuffer() {
204         Rectangle clientArea = getClientArea();
205 
206         if (clientArea.width == 0 || clientArea.height == 0) {
207             mBackBufferImage = null;
208             mBackBufferGC = null;
209             return;
210         }
211 
212         mBackBufferImage = new Image(getDisplay(),
213                 clientArea.width,
214                 clientArea.height);
215         mBackBufferGC = new GC(mBackBufferImage);
216     }
217 
disposeBackBuffer()218     private void disposeBackBuffer() {
219         if (mBackBufferImage != null) {
220             mBackBufferImage.dispose();
221             mBackBufferImage = null;
222         }
223 
224         if (mBackBufferGC != null) {
225             mBackBufferGC.dispose();
226             mBackBufferGC = null;
227         }
228     }
229 
mouseMoved(MouseEvent e)230     private void mouseMoved(MouseEvent e) {
231         mMouseY = e.y;
232         redraw();
233     }
234 
mouseClicked(MouseEvent e)235     private void mouseClicked(MouseEvent e) {
236         if (mMouseInSelf) {
237             int selIndex = mPositionHelper.getCallAt(mMouseY);
238             sendCallSelectedEvent(selIndex);
239             redraw();
240         }
241     }
242 
draw(Display display, GC gc)243     private void draw(Display display, GC gc) {
244         if (mBackBufferImage == null) {
245             initializeBackBuffer();
246         }
247 
248         if (mBackBufferImage == null) {
249             return;
250         }
251 
252         // draw contents onto the back buffer
253         drawBackground(mBackBufferGC, mBackBufferImage.getBounds());
254         drawContextHeaders(mBackBufferGC);
255         drawCallDurations(mBackBufferGC);
256         drawVisibleCallHighlights(mBackBufferGC);
257         drawMouseMarkers(mBackBufferGC);
258 
259         // finally copy over the rendered back buffer onto screen
260         int width = getClientArea().width;
261         int height = getClientArea().height;
262         gc.drawImage(mBackBufferImage,
263                 0, 0, width, height,
264                 0, 0, width, height);
265     }
266 
drawBackground(GC gc, Rectangle bounds)267     private void drawBackground(GC gc, Rectangle bounds) {
268         gc.setBackground(mBackgroundColor);
269         gc.fillRectangle(bounds);
270     }
271 
drawContextHeaders(GC gc)272     private void drawContextHeaders(GC gc) {
273         if (mContextCount <= 1) {
274             return;
275         }
276 
277         gc.setForeground(mContextHeaderColor);
278         gc.setFont(mFontRegistry.get(FONT_KEY));
279         for (int i = 0; i < mContextCount; i++) {
280             Point p = mPositionHelper.getHeaderLocation(i);
281             gc.drawText("CTX" + Integer.toString(i), p.x, p.y);
282         }
283     }
284 
285     /** Draw the call durations as a sequence of lines.
286      *
287      * Calls are arranged on the y-axis based on the sequence in which they were originally
288      * called by the application. If the display height is lesser than the number of calls, then
289      * not every call is shown - the calls are underscanned based the height of the display.
290      *
291      * The x-axis shows two pieces of information: the duration of the call, and the context
292      * in which the call was made. The duration controls how long the displayed line is, and
293      * the context controls the starting offset of the line.
294      */
drawCallDurations(GC gc)295     private void drawCallDurations(GC gc) {
296         if (mCalls == null || mCalls.size() < mEndCallIndex) {
297             return;
298         }
299 
300         gc.setBackground(mDurationLineColor);
301 
302         int callUnderScan = mPositionHelper.getCallUnderScanValue();
303         for (int i = mStartCallIndex; i < mEndCallIndex; i += callUnderScan) {
304             boolean resetColor = false;
305             GLCall c = mCalls.get(i);
306 
307             long duration = c.getWallDuration();
308 
309             if (c.hasErrors()) {
310                 gc.setBackground(mGlErrorColor);
311                 resetColor = true;
312 
313                 // If the call has any errors, we want it to be visible in the minimap
314                 // regardless of how long it took.
315                 duration = mPositionHelper.getMaxDuration();
316             } else if (c.getFunction() == Function.glDrawArrays
317                     || c.getFunction() == Function.glDrawElements
318                     || c.getFunction() == Function.eglSwapBuffers) {
319                 gc.setBackground(mGlDrawColor);
320                 resetColor = true;
321 
322                 // render all draw calls & swap buffer at max length
323                 duration = mPositionHelper.getMaxDuration();
324             }
325 
326             Rectangle bounds = mPositionHelper.getDurationBounds(
327                     i - mStartCallIndex,
328                     c.getContextId(),
329                     duration);
330             gc.fillRectangle(bounds);
331 
332             if (resetColor) {
333                 gc.setBackground(mDurationLineColor);
334             }
335         }
336     }
337 
338     /**
339      * Draw a bounding box that highlights the currently visible range of calls in the
340      * {@link GLFunctionTraceViewer} table.
341      */
drawVisibleCallHighlights(GC gc)342     private void drawVisibleCallHighlights(GC gc) {
343         gc.setAlpha(VISIBLE_CALLS_HIGHLIGHT_ALPHA);
344         gc.setBackground(mVisibleCallsHighlightColor);
345         gc.fillRectangle(mPositionHelper.getBoundsFramingCalls(
346                 mVisibleCallTopIndex - mStartCallIndex,
347                 mVisibleCallBottomIndex - mStartCallIndex));
348         gc.setAlpha(DEFAULT_ALPHA);
349     }
350 
drawMouseMarkers(GC gc)351     private void drawMouseMarkers(GC gc) {
352         if (!mMouseInSelf) {
353             return;
354         }
355 
356         if (mPositionHelper.getCallAt(mMouseY) < 0) {
357             return;
358         }
359 
360         gc.setForeground(mMouseMarkerColor);
361         gc.drawLine(0, mMouseY, getClientArea().width, mMouseY);
362     }
363 
controlResized()364     private void controlResized() {
365         // regenerate back buffer on size changes
366         disposeBackBuffer();
367         initializeBackBuffer();
368 
369         redraw();
370     }
371 
getMinimumWidth()372     public int getMinimumWidth() {
373         return mPositionHelper.getMinimumWidth();
374     }
375 
376     /** Set the GL Call start and end indices for currently displayed frame. */
setCallRangeForCurrentFrame(int startCallIndex, int endCallIndex)377     public void setCallRangeForCurrentFrame(int startCallIndex, int endCallIndex) {
378         mStartCallIndex = startCallIndex;
379         mEndCallIndex = endCallIndex;
380         mPositionHelper.updateCallDensity(mEndCallIndex - mStartCallIndex, getClientArea().height);
381         redraw();
382     }
383 
384     /**
385      * Set the call range that is currently visible in the {@link GLFunctionTraceViewer} table.
386      * @param visibleTopIndex index of call currently visible at the top of the table.
387      * @param visibleBottomIndex index of call currently visible at the bottom of the table.
388      */
setVisibleCallRange(int visibleTopIndex, int visibleBottomIndex)389     public void setVisibleCallRange(int visibleTopIndex, int visibleBottomIndex) {
390         mVisibleCallTopIndex = visibleTopIndex;
391         mVisibleCallBottomIndex = visibleBottomIndex;
392         redraw();
393     }
394 
395     public interface ICallSelectionListener {
callSelected(int selectedCallIndex)396         void callSelected(int selectedCallIndex);
397     }
398 
399     private List<ICallSelectionListener> mListeners = new ArrayList<ICallSelectionListener>();
400 
addCallSelectionListener(ICallSelectionListener l)401     public void addCallSelectionListener(ICallSelectionListener l) {
402         mListeners.add(l);
403     }
404 
sendCallSelectedEvent(int selectedCall)405     private void sendCallSelectedEvent(int selectedCall) {
406         for (ICallSelectionListener l : mListeners) {
407             l.callSelected(selectedCall);
408         }
409     }
410 
411     /** Utility class to help with the positioning and sizes of elements in the canvas. */
412     private static class PositionHelper {
413         /** Left Margin after which duration lines are drawn. */
414         private static final int LEFT_MARGIN = 5;
415 
416         /** Top margin after which header is drawn. */
417         private static final int TOP_MARGIN = 5;
418 
419         /** # of pixels of padding between duration markers for different contexts. */
420         private static final int CONTEXT_PADDING = 10;
421 
422         private final int mHeaderMargin;
423         private final int mContextCount;
424         private final int mMaxDurationLength;
425         private final long mMaxDuration;
426         private final double mScale;
427 
428         private int mCallCount;
429         private int mNumCallsPerPixel = 1;
430 
PositionHelper(int fontHeight, int contextCount, int maxDurationLength, long maxDuration)431         public PositionHelper(int fontHeight, int contextCount,
432                 int maxDurationLength, long maxDuration) {
433             mContextCount = contextCount;
434             mMaxDurationLength = maxDurationLength;
435             mMaxDuration = maxDuration;
436             mScale = (double) maxDurationLength / maxDuration;
437 
438             // header region is present only there are multiple contexts
439             if (mContextCount > 1) {
440                 mHeaderMargin = fontHeight * 3;
441             } else {
442                 mHeaderMargin = 0;
443             }
444         }
445 
446         /** Get the minimum width of the canvas. */
getMinimumWidth()447         public int getMinimumWidth() {
448             return LEFT_MARGIN + (mMaxDurationLength + CONTEXT_PADDING) * mContextCount;
449         }
450 
451         /** Get the bounds for a call duration line. */
getDurationBounds(int callIndex, int context, long duration)452         public Rectangle getDurationBounds(int callIndex, int context, long duration) {
453             if (duration <= 0) {
454                 duration = 1;
455             } else if (duration > mMaxDuration) {
456                 duration = mMaxDuration;
457             }
458 
459             int x = LEFT_MARGIN + ((mMaxDurationLength + CONTEXT_PADDING) * context);
460             int y = (callIndex/mNumCallsPerPixel) + TOP_MARGIN + mHeaderMargin;
461             int w = (int) (duration * mScale);
462             int h = 1;
463 
464             return new Rectangle(x, y, w, h);
465         }
466 
getMaxDuration()467         public long getMaxDuration() {
468             return mMaxDuration;
469         }
470 
471         /** Get the bounds for calls spanning given range. */
getBoundsFramingCalls(int startCallIndex, int endCallIndex)472         public Rectangle getBoundsFramingCalls(int startCallIndex, int endCallIndex) {
473             if (startCallIndex >= 0 && endCallIndex >= startCallIndex
474                     && endCallIndex <= mCallCount) {
475                 int x = LEFT_MARGIN;
476                 int y = (startCallIndex/mNumCallsPerPixel) + TOP_MARGIN + mHeaderMargin;
477                 int w = ((mMaxDurationLength + CONTEXT_PADDING) * mContextCount);
478                 int h = (endCallIndex - startCallIndex)/mNumCallsPerPixel;
479             return new Rectangle(x, y, w, h);
480             } else {
481                 return new Rectangle(0, 0, 0, 0);
482             }
483         }
484 
getHeaderLocation(int context)485         public Point getHeaderLocation(int context) {
486             int x = LEFT_MARGIN + ((mMaxDurationLength + CONTEXT_PADDING) * context);
487             return new Point(x, TOP_MARGIN);
488         }
489 
490         /** Update the call density based on the number of calls to be displayed and
491          * the available height to display them in. */
updateCallDensity(int callCount, int displayHeight)492         public void updateCallDensity(int callCount, int displayHeight) {
493             mCallCount = callCount;
494 
495             if (displayHeight <= 0) {
496                 displayHeight = callCount + 1;
497             }
498 
499             mNumCallsPerPixel = (callCount / displayHeight) + 1;
500         }
501 
502         /** Get the underscan value. In cases where there are more calls to be displayed
503          * than there are availble pixels, we only display 1 out of every underscan calls. */
getCallUnderScanValue()504         public int getCallUnderScanValue() {
505             return mNumCallsPerPixel;
506         }
507 
508         /** Get the index of the call at given y offset. */
getCallAt(int y)509         public int getCallAt(int y) {
510             if (!isWithinBounds(y)) {
511                 return -1;
512             }
513 
514             Rectangle displayBounds = getBoundsFramingCalls(0, mCallCount);
515             return (y - displayBounds.y) * mNumCallsPerPixel;
516         }
517 
518         /** Does the provided y offset map to a valid call? */
isWithinBounds(int y)519         private boolean isWithinBounds(int y) {
520             Rectangle displayBounds = getBoundsFramingCalls(0, mCallCount);
521             if (y < displayBounds.y) {
522                 return false;
523             }
524 
525             if (y > (displayBounds.y + displayBounds.height)) {
526                 return false;
527             }
528 
529             return true;
530         }
531     }
532 }
533