• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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.SwtUtils;
21 import com.android.ide.eclipse.gltrace.TraceFileParserTask;
22 import com.android.ide.eclipse.gltrace.editors.DurationMinimap.ICallSelectionListener;
23 import com.android.ide.eclipse.gltrace.editors.GLCallGroups.GLCallNode;
24 import com.android.ide.eclipse.gltrace.model.GLCall;
25 import com.android.ide.eclipse.gltrace.model.GLFrame;
26 import com.android.ide.eclipse.gltrace.model.GLTrace;
27 import com.android.ide.eclipse.gltrace.views.FrameSummaryViewPage;
28 import com.android.ide.eclipse.gltrace.views.detail.DetailsPage;
29 
30 import org.eclipse.core.runtime.IProgressMonitor;
31 import org.eclipse.jface.dialogs.MessageDialog;
32 import org.eclipse.jface.dialogs.ProgressMonitorDialog;
33 import org.eclipse.jface.viewers.CellLabelProvider;
34 import org.eclipse.jface.viewers.ColumnLabelProvider;
35 import org.eclipse.jface.viewers.ISelection;
36 import org.eclipse.jface.viewers.ISelectionChangedListener;
37 import org.eclipse.jface.viewers.ISelectionProvider;
38 import org.eclipse.jface.viewers.ITreeContentProvider;
39 import org.eclipse.jface.viewers.TreeViewer;
40 import org.eclipse.jface.viewers.TreeViewerColumn;
41 import org.eclipse.jface.viewers.Viewer;
42 import org.eclipse.jface.viewers.ViewerCell;
43 import org.eclipse.jface.viewers.ViewerFilter;
44 import org.eclipse.swt.SWT;
45 import org.eclipse.swt.events.ControlAdapter;
46 import org.eclipse.swt.events.ControlEvent;
47 import org.eclipse.swt.events.ModifyEvent;
48 import org.eclipse.swt.events.ModifyListener;
49 import org.eclipse.swt.events.SelectionAdapter;
50 import org.eclipse.swt.events.SelectionEvent;
51 import org.eclipse.swt.graphics.Color;
52 import org.eclipse.swt.layout.GridData;
53 import org.eclipse.swt.layout.GridLayout;
54 import org.eclipse.swt.widgets.Combo;
55 import org.eclipse.swt.widgets.Composite;
56 import org.eclipse.swt.widgets.Display;
57 import org.eclipse.swt.widgets.Label;
58 import org.eclipse.swt.widgets.Scale;
59 import org.eclipse.swt.widgets.ScrollBar;
60 import org.eclipse.swt.widgets.Spinner;
61 import org.eclipse.swt.widgets.Text;
62 import org.eclipse.swt.widgets.Tree;
63 import org.eclipse.swt.widgets.TreeColumn;
64 import org.eclipse.swt.widgets.TreeItem;
65 import org.eclipse.ui.IEditorInput;
66 import org.eclipse.ui.IEditorSite;
67 import org.eclipse.ui.IURIEditorInput;
68 import org.eclipse.ui.PartInitException;
69 import org.eclipse.ui.part.EditorPart;
70 
71 import java.io.File;
72 import java.lang.reflect.InvocationTargetException;
73 import java.util.ArrayList;
74 import java.util.List;
75 import java.util.regex.Matcher;
76 import java.util.regex.Pattern;
77 
78 /** Display OpenGL function trace in a tabular view. */
79 public class GLFunctionTraceViewer extends EditorPart implements ISelectionProvider {
80     public static final String ID = "com.android.ide.eclipse.gltrace.GLFunctionTrace"; //$NON-NLS-1$
81 
82     private static final String DEFAULT_FILTER_MESSAGE = "Filter list of OpenGL calls. Accepts Java regexes.";
83 
84     /** Width of thumbnail images of the framebuffer. */
85     private static final int THUMBNAIL_WIDTH = 50;
86 
87     /** Height of thumbnail images of the framebuffer. */
88     private static final int THUMBNAIL_HEIGHT = 50;
89 
90     private String mFilePath;
91     private Scale mFrameSelectionScale;
92     private Spinner mFrameSelectionSpinner;
93 
94     private GLTrace mTrace;
95 
96     private TreeViewer mFrameTreeViewer;
97     private List<GLCallNode> mTreeViewerNodes;
98 
99     private Text mFilterText;
100     private GLCallFilter mGLCallFilter;
101 
102     private Color mGldrawTextColor;
103     private Color mGlCallErrorColor;
104 
105     // Currently displayed frame's start and end call indices.
106     private int mCallStartIndex;
107     private int mCallEndIndex;
108 
109     private DurationMinimap mDurationMinimap;
110     private ScrollBar mVerticalScrollBar;
111 
112     private Combo mContextSwitchCombo;
113     private boolean mShowContextSwitcher;
114     private int mCurrentlyDisplayedContext = -1;
115 
116     private FrameSummaryViewPage mFrameSummaryViewPage;
117 
GLFunctionTraceViewer()118     public GLFunctionTraceViewer() {
119         mGldrawTextColor = Display.getDefault().getSystemColor(SWT.COLOR_BLUE);
120         mGlCallErrorColor = Display.getDefault().getSystemColor(SWT.COLOR_RED);
121     }
122 
123     @Override
doSave(IProgressMonitor monitor)124     public void doSave(IProgressMonitor monitor) {
125     }
126 
127     @Override
doSaveAs()128     public void doSaveAs() {
129     }
130 
131     @Override
init(IEditorSite site, IEditorInput input)132     public void init(IEditorSite site, IEditorInput input) throws PartInitException {
133         // we use a IURIEditorInput to allow opening files not within the workspace
134         if (!(input instanceof IURIEditorInput)) {
135             throw new PartInitException("GL Function Trace View: unsupported input type.");
136         }
137 
138         setSite(site);
139         setInput(input);
140         mFilePath = ((IURIEditorInput) input).getURI().getPath();
141 
142         // set the editor part name to be the name of the file.
143         File f = new File(mFilePath);
144         setPartName(f.getName());
145     }
146 
147     @Override
isDirty()148     public boolean isDirty() {
149         return false;
150     }
151 
152     @Override
isSaveAsAllowed()153     public boolean isSaveAsAllowed() {
154         return false;
155     }
156 
157     @Override
createPartControl(Composite parent)158     public void createPartControl(Composite parent) {
159         Composite c = new Composite(parent, SWT.NONE);
160         c.setLayout(new GridLayout(1, false));
161         GridData gd = new GridData(GridData.FILL_BOTH);
162         c.setLayoutData(gd);
163 
164         ProgressMonitorDialog dlg = new ProgressMonitorDialog(parent.getShell());
165         TraceFileParserTask parser = new TraceFileParserTask(mFilePath, parent.getDisplay(),
166                 THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT);
167         try {
168             dlg.run(true, true, parser);
169         } catch (InvocationTargetException e) {
170             // exception while parsing, display error to user
171             MessageDialog.openError(parent.getShell(),
172                     "Error parsing OpenGL Trace File",
173                     e.getCause().getMessage());
174             return;
175         } catch (InterruptedException e) {
176             // operation canceled by user, just return
177             return;
178         }
179 
180         mTrace = parser.getTrace();
181         if (mTrace == null) {
182             return;
183         }
184 
185         mShowContextSwitcher = mTrace.getContexts().size() > 1;
186 
187         createFrameSelectionControls(c);
188         createFilterBar(c);
189         createFrameTraceView(c);
190 
191         getSite().setSelectionProvider(mFrameTreeViewer);
192 
193         Display.getDefault().asyncExec(new Runnable() {
194             @Override
195             public void run() {
196                 refreshUI();
197             }
198         });
199     }
200 
refreshUI()201     private void refreshUI() {
202         int nFrames = 0;
203 
204         nFrames = mTrace.getFrames().size();
205         setFrameCount(nFrames);
206         selectFrame(1);
207     }
208 
createFrameSelectionControls(Composite parent)209     private void createFrameSelectionControls(Composite parent) {
210         Composite c = new Composite(parent, SWT.NONE);
211         c.setLayout(new GridLayout(3, false));
212         GridData gd = new GridData(GridData.FILL_HORIZONTAL);
213         c.setLayoutData(gd);
214 
215         Label l = new Label(c, SWT.NONE);
216         l.setText("Select Frame:");
217 
218         mFrameSelectionScale = new Scale(c, SWT.HORIZONTAL);
219         mFrameSelectionScale.setMinimum(1);
220         mFrameSelectionScale.setMaximum(1);
221         mFrameSelectionScale.setSelection(0);
222         gd = new GridData(GridData.FILL_HORIZONTAL);
223         mFrameSelectionScale.setLayoutData(gd);
224 
225         mFrameSelectionScale.addSelectionListener(new SelectionAdapter() {
226             @Override
227             public void widgetSelected(SelectionEvent e) {
228                 int selectedFrame = mFrameSelectionScale.getSelection();
229                 mFrameSelectionSpinner.setSelection(selectedFrame);
230                 selectFrame(selectedFrame);
231             }
232         });
233 
234         mFrameSelectionSpinner = new Spinner(c, SWT.BORDER);
235         gd = new GridData();
236         // width to hold atleast 6 digits
237         gd.widthHint = SwtUtils.getFontWidth(mFrameSelectionSpinner) * 6;
238         mFrameSelectionSpinner.setLayoutData(gd);
239 
240         mFrameSelectionSpinner.setMinimum(1);
241         mFrameSelectionSpinner.setMaximum(1);
242         mFrameSelectionSpinner.setSelection(0);
243         mFrameSelectionSpinner.addSelectionListener(new SelectionAdapter() {
244             @Override
245             public void widgetSelected(SelectionEvent e) {
246                 // Disable spinner until all necessary action is complete.
247                 // This seems to be necessary (atleast on Linux) for the spinner to not get
248                 // stuck in a pressed state if it is pressed for more than a few seconds
249                 // continuously.
250                 mFrameSelectionSpinner.setEnabled(false);
251 
252                 int selectedFrame = mFrameSelectionSpinner.getSelection();
253                 mFrameSelectionScale.setSelection(selectedFrame);
254                 selectFrame(selectedFrame);
255 
256                 // re-enable spinner
257                 mFrameSelectionSpinner.setEnabled(true);
258             }
259         });
260     }
261 
setFrameCount(int nFrames)262     private void setFrameCount(int nFrames) {
263         mFrameSelectionScale.setMaximum(nFrames);
264         mFrameSelectionSpinner.setMaximum(nFrames);
265     }
266 
selectFrame(int selectedFrame)267     private void selectFrame(int selectedFrame) {
268         mFrameSelectionScale.setSelection(selectedFrame);
269         mFrameSelectionSpinner.setSelection(selectedFrame);
270 
271         GLFrame f = mTrace.getFrame(selectedFrame - 1);
272         mCallStartIndex = f.getStartIndex();
273         mCallEndIndex = f.getEndIndex();
274 
275         // update tree view in the editor
276         refreshTree(mCallStartIndex, mCallEndIndex, mCurrentlyDisplayedContext);
277 
278         // update minimap view
279         mDurationMinimap.setCallRangeForCurrentFrame(mCallStartIndex, mCallEndIndex);
280 
281         // update the frame summary view
282         if (mFrameSummaryViewPage != null) {
283             mFrameSummaryViewPage.setSelectedFrame(selectedFrame - 1);
284         }
285     }
286 
287     /**
288      * Show only calls from the given context
289      * @param context context id whose calls should be displayed. Illegal values will result in
290      *                calls from all contexts being displayed.
291      */
selectContext(int context)292     private void selectContext(int context) {
293         if (mCurrentlyDisplayedContext == context) {
294             return;
295         }
296 
297         mCurrentlyDisplayedContext = context;
298         refreshTree(mCallStartIndex, mCallEndIndex, mCurrentlyDisplayedContext);
299     }
300 
refreshTree(int startCallIndex, int endCallIndex, int contextToDisplay)301     private void refreshTree(int startCallIndex, int endCallIndex, int contextToDisplay) {
302         mTreeViewerNodes = GLCallGroups.constructCallHierarchy(mTrace,
303                 startCallIndex, endCallIndex,
304                 contextToDisplay);
305         mFrameTreeViewer.setInput(mTreeViewerNodes);
306         mFrameTreeViewer.refresh();
307         mFrameTreeViewer.expandAll();
308     }
309 
createFilterBar(Composite parent)310     private void createFilterBar(Composite parent) {
311         int numColumns = mShowContextSwitcher ? 3 : 2;
312 
313         Composite c = new Composite(parent, SWT.NONE);
314         c.setLayout(new GridLayout(numColumns, false));
315         GridData gd = new GridData(GridData.FILL_HORIZONTAL);
316         c.setLayoutData(gd);
317 
318         Label l = new Label(c, SWT.NONE);
319         l.setText("Filter:");
320 
321         mFilterText = new Text(c, SWT.BORDER | SWT.ICON_SEARCH | SWT.SEARCH | SWT.ICON_CANCEL);
322         mFilterText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
323         mFilterText.setMessage(DEFAULT_FILTER_MESSAGE);
324         mFilterText.addModifyListener(new ModifyListener() {
325             @Override
326             public void modifyText(ModifyEvent e) {
327                 updateAppliedFilters();
328             }
329         });
330 
331         if (mShowContextSwitcher) {
332             mContextSwitchCombo = new Combo(c, SWT.BORDER | SWT.READ_ONLY);
333 
334             // Setup the combo such that "All Contexts" is the first item,
335             // and then we have an item for each context.
336             mContextSwitchCombo.add("All Contexts");
337             mContextSwitchCombo.select(0);
338             mCurrentlyDisplayedContext = -1; // showing all contexts
339             for (int i = 0; i < mTrace.getContexts().size(); i++) {
340                 mContextSwitchCombo.add("Context " + i);
341             }
342 
343             mContextSwitchCombo.addSelectionListener(new SelectionAdapter() {
344                 @Override
345                 public void widgetSelected(SelectionEvent e) {
346                     selectContext(mContextSwitchCombo.getSelectionIndex() - 1);
347                 }
348             });
349         } else {
350             mCurrentlyDisplayedContext = 0;
351         }
352     }
353 
updateAppliedFilters()354     private void updateAppliedFilters() {
355         mGLCallFilter.setFilters(mFilterText.getText().trim());
356         mFrameTreeViewer.refresh();
357     }
358 
createFrameTraceView(Composite parent)359     private void createFrameTraceView(Composite parent) {
360         Composite c = new Composite(parent, SWT.NONE);
361         c.setLayout(new GridLayout(2, false));
362         GridData gd = new GridData(GridData.FILL_BOTH);
363         c.setLayoutData(gd);
364 
365         final Tree tree = new Tree(c, SWT.BORDER | SWT.FULL_SELECTION);
366         gd = new GridData(GridData.FILL_BOTH);
367         tree.setLayoutData(gd);
368         tree.setLinesVisible(true);
369         tree.setHeaderVisible(true);
370 
371         mFrameTreeViewer = new TreeViewer(tree);
372         CellLabelProvider labelProvider = new GLFrameLabelProvider();
373 
374         // column showing the GL context id
375         TreeViewerColumn tvc = new TreeViewerColumn(mFrameTreeViewer, SWT.NONE);
376         tvc.setLabelProvider(labelProvider);
377         TreeColumn column = tvc.getColumn();
378         column.setText("Function");
379         column.setWidth(500);
380 
381         // column showing the GL function duration (wall clock time)
382         tvc = new TreeViewerColumn(mFrameTreeViewer, SWT.NONE);
383         tvc.setLabelProvider(labelProvider);
384         column = tvc.getColumn();
385         column.setText("Wall Time (ns)");
386         column.setWidth(150);
387         column.setAlignment(SWT.RIGHT);
388 
389         // column showing the GL function duration (thread time)
390         tvc = new TreeViewerColumn(mFrameTreeViewer, SWT.NONE);
391         tvc.setLabelProvider(labelProvider);
392         column = tvc.getColumn();
393         column.setText("Thread Time (ns)");
394         column.setWidth(150);
395         column.setAlignment(SWT.RIGHT);
396 
397         mFrameTreeViewer.setContentProvider(new GLFrameContentProvider());
398 
399         mGLCallFilter = new GLCallFilter();
400         mFrameTreeViewer.addFilter(mGLCallFilter);
401 
402         // when the control is resized, give all the additional space
403         // to the function name column.
404         tree.addControlListener(new ControlAdapter() {
405             @Override
406             public void controlResized(ControlEvent e) {
407                 int w = mFrameTreeViewer.getTree().getClientArea().width;
408                 if (w > 200) {
409                     mFrameTreeViewer.getTree().getColumn(2).setWidth(100);
410                     mFrameTreeViewer.getTree().getColumn(1).setWidth(100);
411                     mFrameTreeViewer.getTree().getColumn(0).setWidth(w - 200);
412                 }
413             }
414         });
415 
416         mDurationMinimap = new DurationMinimap(c, mTrace);
417         gd = new GridData(GridData.FILL_VERTICAL);
418         gd.widthHint = gd.minimumWidth = mDurationMinimap.getMinimumWidth();
419         mDurationMinimap.setLayoutData(gd);
420         mDurationMinimap.addCallSelectionListener(new ICallSelectionListener() {
421             @Override
422             public void callSelected(int selectedCallIndex) {
423                 if (selectedCallIndex > 0 && selectedCallIndex < mTreeViewerNodes.size()) {
424                     TreeItem item = tree.getItem(selectedCallIndex);
425                     tree.select(item);
426                     tree.setTopItem(item);
427                 }
428             }
429         });
430 
431         mVerticalScrollBar = tree.getVerticalBar();
432         mVerticalScrollBar.addSelectionListener(new SelectionAdapter() {
433             @Override
434             public void widgetSelected(SelectionEvent e) {
435                 updateVisibleRange();
436             }
437         });
438     }
439 
updateVisibleRange()440     private void updateVisibleRange() {
441         int visibleCallTopIndex = mCallStartIndex;
442         int visibleCallBottomIndex = mCallEndIndex;
443 
444         if (mVerticalScrollBar.isEnabled()) {
445             int selection = mVerticalScrollBar.getSelection();
446             int thumb = mVerticalScrollBar.getThumb();
447             int max = mVerticalScrollBar.getMaximum();
448 
449             // from the scrollbar values, compute the visible fraction
450             double top = (double) selection / max;
451             double bottom = (double) (selection + thumb) / max;
452 
453             // map the fraction to the call indices
454             int range = mCallEndIndex - mCallStartIndex;
455             visibleCallTopIndex = mCallStartIndex + (int) Math.floor(range * top);
456             visibleCallBottomIndex = mCallStartIndex + (int) Math.ceil(range * bottom);
457         }
458 
459         mDurationMinimap.setVisibleCallRange(visibleCallTopIndex, visibleCallBottomIndex);
460     }
461 
462     @Override
setFocus()463     public void setFocus() {
464         mFrameTreeViewer.getTree().setFocus();
465     }
466 
467     private static class GLFrameContentProvider implements ITreeContentProvider {
468         @Override
dispose()469         public void dispose() {
470         }
471 
472         @Override
inputChanged(Viewer viewer, Object oldInput, Object newInput)473         public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
474         }
475 
476         @Override
getElements(Object inputElement)477         public Object[] getElements(Object inputElement) {
478             return getChildren(inputElement);
479         }
480 
481         @Override
getChildren(Object parentElement)482         public Object[] getChildren(Object parentElement) {
483             if (parentElement instanceof List<?>) {
484                 return ((List<?>) parentElement).toArray();
485             }
486 
487             if (!(parentElement instanceof GLCallNode)) {
488                 return null;
489             }
490 
491             GLCallNode parent = (GLCallNode) parentElement;
492             if (parent.hasChildren()) {
493                 return parent.getChildren().toArray();
494             } else {
495                 return new Object[0];
496             }
497         }
498 
499         @Override
getParent(Object element)500         public Object getParent(Object element) {
501             if (!(element instanceof GLCallNode)) {
502                 return null;
503             }
504 
505             return ((GLCallNode) element).getParent();
506         }
507 
508         @Override
hasChildren(Object element)509         public boolean hasChildren(Object element) {
510             if (!(element instanceof GLCallNode)) {
511                 return false;
512             }
513 
514             return ((GLCallNode) element).hasChildren();
515         }
516     }
517 
518     private class GLFrameLabelProvider extends ColumnLabelProvider {
519         @Override
update(ViewerCell cell)520         public void update(ViewerCell cell) {
521             Object element = cell.getElement();
522             if (!(element instanceof GLCallNode)) {
523                 return;
524             }
525 
526             GLCall c = ((GLCallNode) element).getCall();
527 
528             if (c.getFunction() == Function.glDrawArrays
529                     || c.getFunction() == Function.glDrawElements) {
530                 cell.setForeground(mGldrawTextColor);
531             }
532 
533             if (c.hasErrors()) {
534                 cell.setForeground(mGlCallErrorColor);
535             }
536 
537             cell.setText(getColumnText(c, cell.getColumnIndex()));
538         }
539 
getColumnText(GLCall c, int columnIndex)540         private String getColumnText(GLCall c, int columnIndex) {
541             switch (columnIndex) {
542             case 0:
543                 if (c.getFunction() == Function.glPushGroupMarkerEXT) {
544                     Object marker = c.getProperty(GLCall.PROPERTY_MARKERNAME);
545                     if (marker instanceof String) {
546                         return ((String) marker);
547                     }
548                 }
549                 try {
550                     return c.toString();
551                 } catch (Exception e) {
552                     // in case of any formatting errors, just return the function name.
553                     return c.getFunction().toString();
554                 }
555             case 1:
556                 return formatDuration(c.getWallDuration());
557             case 2:
558                 return formatDuration(c.getThreadDuration());
559             default:
560                 return Integer.toString(c.getContextId());
561             }
562         }
563 
formatDuration(int time)564         private String formatDuration(int time) {
565             // Max duration is in the 10s of milliseconds, so xx,xxx,xxx ns
566             // So we require a format specifier that is 10 characters wide
567             return String.format("%,10d", time);            //$NON-NLS-1$
568         }
569     }
570 
571     private static class GLCallFilter extends ViewerFilter {
572         private final List<Pattern> mPatterns = new ArrayList<Pattern>();
573 
setFilters(String filter)574         public void setFilters(String filter) {
575             mPatterns.clear();
576 
577             // split the user input into multiple regexes
578             // we assume that the regexes are OR'ed together i.e., all text that matches
579             // any one of the regexes will be displayed
580             for (String regex : filter.split(" ")) {
581                 mPatterns.add(Pattern.compile(regex, Pattern.CASE_INSENSITIVE));
582             }
583         }
584 
585         @Override
select(Viewer viewer, Object parentElement, Object element)586         public boolean select(Viewer viewer, Object parentElement, Object element) {
587             if (!(element instanceof GLCallNode)) {
588                 return true;
589             }
590 
591             String text = getTextUnderNode((GLCallNode) element);
592 
593             if (mPatterns.size() == 0) {
594                 // match if there are no regex filters
595                 return true;
596             }
597 
598             for (Pattern p : mPatterns) {
599                 Matcher matcher = p.matcher(text);
600                 if (matcher.find()) {
601                     // match if atleast one of the regexes matches this text
602                     return true;
603                 }
604             }
605 
606             return false;
607         }
608 
609         /** Obtain a string representation of all functions under a given tree node. */
getTextUnderNode(GLCallNode element)610         private String getTextUnderNode(GLCallNode element) {
611             String func = element.getCall().getFunction().toString();
612             if (!element.hasChildren()) {
613                 return func;
614             }
615 
616             StringBuilder sb = new StringBuilder(100);
617             sb.append(func);
618 
619             for (GLCallNode child : element.getChildren()) {
620                 sb.append(getTextUnderNode(child));
621             }
622 
623             return sb.toString();
624         }
625     }
626 
627     @Override
addSelectionChangedListener(ISelectionChangedListener listener)628     public void addSelectionChangedListener(ISelectionChangedListener listener) {
629         if (mFrameTreeViewer != null) {
630             mFrameTreeViewer.addSelectionChangedListener(listener);
631         }
632     }
633 
634     @Override
getSelection()635     public ISelection getSelection() {
636         if (mFrameTreeViewer != null) {
637             return mFrameTreeViewer.getSelection();
638         } else {
639             return null;
640         }
641     }
642 
643     @Override
removeSelectionChangedListener(ISelectionChangedListener listener)644     public void removeSelectionChangedListener(ISelectionChangedListener listener) {
645         if (mFrameTreeViewer != null) {
646             mFrameTreeViewer.removeSelectionChangedListener(listener);
647         }
648     }
649 
650     @Override
setSelection(ISelection selection)651     public void setSelection(ISelection selection) {
652         if (mFrameTreeViewer != null) {
653             mFrameTreeViewer.setSelection(selection);
654         }
655     }
656 
getTrace()657     public GLTrace getTrace() {
658         return mTrace;
659     }
660 
getStateViewPage()661     public StateViewPage getStateViewPage() {
662         return new StateViewPage(mTrace);
663     }
664 
getFrameSummaryViewPage()665     public FrameSummaryViewPage getFrameSummaryViewPage() {
666         if (mFrameSummaryViewPage == null) {
667             mFrameSummaryViewPage = new FrameSummaryViewPage(mTrace);
668         }
669 
670         return mFrameSummaryViewPage;
671     }
672 
getDetailsPage()673     public DetailsPage getDetailsPage() {
674         return new DetailsPage(mTrace);
675     }
676 }
677