• 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 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 
DurationMinimap(Composite parent, GLTrace trace)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 
setInput(GLTrace trace)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
dispose()174     public void dispose() {
175         disposeColors();
176         disposeBackBuffer();
177         super.dispose();
178     }
179 
initializeColors()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 
disposeColors()191     private void disposeColors() {
192         mBackgroundColor.dispose();
193         mDurationLineColor.dispose();
194         mGlDrawColor.dispose();
195         mContextHeaderColor.dispose();
196         mVisibleCallsHighlightColor.dispose();
197         mMouseMarkerColor.dispose();
198     }
199 
initializeFonts()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 
initializeBackBuffer()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 
disposeBackBuffer()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 
mouseMoved(MouseEvent e)239     private void mouseMoved(MouseEvent e) {
240         mMouseY = e.y;
241         redraw();
242     }
243 
mouseClicked(MouseEvent e)244     private void mouseClicked(MouseEvent e) {
245         if (mMouseInSelf) {
246             int selIndex = mPositionHelper.getCallAt(mMouseY);
247             sendCallSelectedEvent(selIndex);
248             redraw();
249         }
250     }
251 
draw(Display display, GC gc)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 
drawBackground(GC gc, Rectangle bounds)276     private void drawBackground(GC gc, Rectangle bounds) {
277         gc.setBackground(mBackgroundColor);
278         gc.fillRectangle(bounds);
279     }
280 
drawContextHeaders(GC gc)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      */
drawCallDurations(GC gc)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      */
drawVisibleCallHighlights(GC gc)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 
drawMouseMarkers(GC gc)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 
controlResized()373     private void controlResized() {
374         // regenerate back buffer on size changes
375         disposeBackBuffer();
376         initializeBackBuffer();
377 
378         redraw();
379     }
380 
getMinimumWidth()381     public int getMinimumWidth() {
382         return mPositionHelper.getMinimumWidth();
383     }
384 
385     /** Set the GL Call start and end indices for currently displayed frame. */
setCallRangeForCurrentFrame(int startCallIndex, int endCallIndex)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      */
setVisibleCallRange(int visibleTopIndex, int visibleBottomIndex)398     public void setVisibleCallRange(int visibleTopIndex, int visibleBottomIndex) {
399         mVisibleCallTopIndex = visibleTopIndex;
400         mVisibleCallBottomIndex = visibleBottomIndex;
401         redraw();
402     }
403 
404     public interface ICallSelectionListener {
callSelected(int selectedCallIndex)405         void callSelected(int selectedCallIndex);
406     }
407 
408     private List<ICallSelectionListener> mListeners = new ArrayList<ICallSelectionListener>();
409 
addCallSelectionListener(ICallSelectionListener l)410     public void addCallSelectionListener(ICallSelectionListener l) {
411         mListeners.add(l);
412     }
413 
sendCallSelectedEvent(int selectedCall)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 
PositionHelper(int fontHeight, int contextCount, int maxDurationLength, long maxDuration)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. */
getMinimumWidth()456         public int getMinimumWidth() {
457             return LEFT_MARGIN + (mMaxDurationLength + CONTEXT_PADDING) * mContextCount;
458         }
459 
460         /** Get the bounds for a call duration line. */
getDurationBounds(int callIndex, int context, long duration)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 
getMaxDuration()476         public long getMaxDuration() {
477             return mMaxDuration;
478         }
479 
480         /** Get the bounds for calls spanning given range. */
getBoundsFramingCalls(int startCallIndex, int endCallIndex)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 
getHeaderLocation(int context)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. */
updateCallDensity(int callCount, int displayHeight)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. */
getCallUnderScanValue()513         public int getCallUnderScanValue() {
514             return mNumCallsPerPixel;
515         }
516 
517         /** Get the index of the call at given y offset. */
getCallAt(int y)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? */
isWithinBounds(int y)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