aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/GLFunctionTraceViewer.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/GLFunctionTraceViewer.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/GLFunctionTraceViewer.java984
1 files changed, 984 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/GLFunctionTraceViewer.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/GLFunctionTraceViewer.java
new file mode 100644
index 000000000..b809ddddf
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/GLFunctionTraceViewer.java
@@ -0,0 +1,984 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.gltrace.editors;
+
+import com.android.ddmuilib.AbstractBufferFindTarget;
+import com.android.ddmuilib.FindDialog;
+import com.android.ide.eclipse.gltrace.GLProtoBuf.GLMessage.Function;
+import com.android.ide.eclipse.gltrace.GlTracePlugin;
+import com.android.ide.eclipse.gltrace.SwtUtils;
+import com.android.ide.eclipse.gltrace.TraceFileParserTask;
+import com.android.ide.eclipse.gltrace.editors.DurationMinimap.ICallSelectionListener;
+import com.android.ide.eclipse.gltrace.editors.GLCallGroups.GLCallNode;
+import com.android.ide.eclipse.gltrace.model.GLCall;
+import com.android.ide.eclipse.gltrace.model.GLFrame;
+import com.android.ide.eclipse.gltrace.model.GLTrace;
+import com.android.ide.eclipse.gltrace.views.FrameSummaryViewPage;
+import com.android.ide.eclipse.gltrace.views.detail.DetailsPage;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.dialogs.ErrorDialog;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.dialogs.ProgressMonitorDialog;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.viewers.CellLabelProvider;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.ISelectionProvider;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jface.viewers.TreeViewerColumn;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerCell;
+import org.eclipse.jface.viewers.ViewerFilter;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.dnd.Clipboard;
+import org.eclipse.swt.dnd.TextTransfer;
+import org.eclipse.swt.dnd.Transfer;
+import org.eclipse.swt.events.ControlAdapter;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.FileDialog;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Scale;
+import org.eclipse.swt.widgets.ScrollBar;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Spinner;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.ToolBar;
+import org.eclipse.swt.widgets.ToolItem;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeColumn;
+import org.eclipse.swt.widgets.TreeItem;
+import org.eclipse.ui.IActionBars;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorSite;
+import org.eclipse.ui.ISharedImages;
+import org.eclipse.ui.IURIEditorInput;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.actions.ActionFactory;
+import org.eclipse.ui.part.EditorPart;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/** Display OpenGL function trace in a tabular view. */
+public class GLFunctionTraceViewer extends EditorPart implements ISelectionProvider {
+ public static final String ID = "com.android.ide.eclipse.gltrace.GLFunctionTrace"; //$NON-NLS-1$
+
+ private static final String DEFAULT_FILTER_MESSAGE = "Filter list of OpenGL calls. Accepts Java regexes.";
+ private static final String NEWLINE = System.getProperty("line.separator"); //$NON-NLS-1$
+
+ private static Image sExpandAllIcon;
+
+ private static String sLastExportedToFolder;
+
+ private String mFilePath;
+ private Scale mFrameSelectionScale;
+ private Spinner mFrameSelectionSpinner;
+
+ private GLTrace mTrace;
+
+ private TreeViewer mFrameTreeViewer;
+ private List<GLCallNode> mTreeViewerNodes;
+
+ private Text mFilterText;
+ private GLCallFilter mGLCallFilter;
+
+ private Color mGldrawTextColor;
+ private Color mGlCallErrorColor;
+
+ /**
+ * Job to refresh the tree view & frame summary view.
+ *
+ * When the currently displayed frame is changed, either via the {@link #mFrameSelectionScale}
+ * or via {@link #mFrameSelectionSpinner}, we need to update the displayed tree of calls for
+ * that frame, and the frame summary view. Both these operations need to happen on the UI
+ * thread, but are time consuming. This works out ok if the frame selection is not changing
+ * rapidly (i.e., when the spinner or scale is moved to the target frame in a single action).
+ * However, if the spinner is constantly pressed, then the user is scrolling through a sequence
+ * of frames, and rather than refreshing the details for each of the intermediate frames,
+ * we create a job to refresh the details and schedule the job after a short interval
+ * {@link #TREE_REFRESH_INTERVAL}. This allows us to stay responsive to the spinner/scale,
+ * and not do the costly refresh for each of the intermediate frames.
+ */
+ private Job mTreeRefresherJob;
+ private final Object mTreeRefresherLock = new Object();
+ private static final int TREE_REFRESH_INTERVAL_MS = 250;
+
+ private int mCurrentFrame;
+
+ // Currently displayed frame's start and end call indices.
+ private int mCallStartIndex;
+ private int mCallEndIndex;
+
+ private DurationMinimap mDurationMinimap;
+ private ScrollBar mVerticalScrollBar;
+
+ private Combo mContextSwitchCombo;
+ private boolean mShowContextSwitcher;
+ private int mCurrentlyDisplayedContext = -1;
+
+ private StateViewPage mStateViewPage;
+ private FrameSummaryViewPage mFrameSummaryViewPage;
+ private DetailsPage mDetailsPage;
+
+ private ToolItem mExpandAllToolItem;
+ private ToolItem mCollapseAllToolItem;
+ private ToolItem mSaveAsToolItem;
+
+ public GLFunctionTraceViewer() {
+ mGldrawTextColor = Display.getDefault().getSystemColor(SWT.COLOR_BLUE);
+ mGlCallErrorColor = Display.getDefault().getSystemColor(SWT.COLOR_RED);
+ }
+
+ @Override
+ public void doSave(IProgressMonitor monitor) {
+ }
+
+ @Override
+ public void doSaveAs() {
+ }
+
+ @Override
+ public void init(IEditorSite site, IEditorInput input) throws PartInitException {
+ // we use a IURIEditorInput to allow opening files not within the workspace
+ if (!(input instanceof IURIEditorInput)) {
+ throw new PartInitException("GL Function Trace View: unsupported input type.");
+ }
+
+ setSite(site);
+ setInput(input);
+ mFilePath = ((IURIEditorInput) input).getURI().getPath();
+
+ // set the editor part name to be the name of the file.
+ File f = new File(mFilePath);
+ setPartName(f.getName());
+ }
+
+ @Override
+ public boolean isDirty() {
+ return false;
+ }
+
+ @Override
+ public boolean isSaveAsAllowed() {
+ return false;
+ }
+
+ @Override
+ public void createPartControl(Composite parent) {
+ Composite c = new Composite(parent, SWT.NONE);
+ c.setLayout(new GridLayout(1, false));
+ GridData gd = new GridData(GridData.FILL_BOTH);
+ c.setLayoutData(gd);
+
+ setInput(parent.getShell(), mFilePath);
+
+ createFrameSelectionControls(c);
+ createOptionsBar(c);
+ createFrameTraceView(c);
+
+ getSite().setSelectionProvider(mFrameTreeViewer);
+
+ IActionBars actionBars = getEditorSite().getActionBars();
+ actionBars.setGlobalActionHandler(ActionFactory.COPY.getId(),
+ new Action("Copy") {
+ @Override
+ public void run() {
+ copySelectionToClipboard();
+ }
+ });
+
+ actionBars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(),
+ new Action("Select All") {
+ @Override
+ public void run() {
+ selectAll();
+ }
+ });
+
+ actionBars.setGlobalActionHandler(ActionFactory.FIND.getId(),
+ new Action("Find") {
+ @Override
+ public void run() {
+ showFindDialog();
+ }
+ });
+ }
+
+ public void setInput(Shell shell, String tracePath) {
+ ProgressMonitorDialog dlg = new ProgressMonitorDialog(shell);
+ TraceFileParserTask parser = new TraceFileParserTask(mFilePath);
+ try {
+ dlg.run(true, true, parser);
+ } catch (InvocationTargetException e) {
+ // exception while parsing, display error to user
+ MessageDialog.openError(shell,
+ "Error parsing OpenGL Trace File",
+ e.getCause().getMessage());
+ return;
+ } catch (InterruptedException e) {
+ // operation canceled by user, just return
+ return;
+ }
+
+ mTrace = parser.getTrace();
+ mShowContextSwitcher = (mTrace == null) ? false : mTrace.getContexts().size() > 1;
+ if (mStateViewPage != null) {
+ mStateViewPage.setInput(mTrace);
+ }
+ if (mFrameSummaryViewPage != null) {
+ mFrameSummaryViewPage.setInput(mTrace);
+ }
+ if (mDetailsPage != null) {
+ mDetailsPage.setInput(mTrace);
+ }
+ if (mDurationMinimap != null) {
+ mDurationMinimap.setInput(mTrace);
+ }
+
+ Display.getDefault().asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ refreshUI();
+ }
+ });
+ }
+
+ private void refreshUI() {
+ if (mTrace == null || mTrace.getGLCalls().size() == 0) {
+ setFrameCount(0);
+ return;
+ }
+
+ setFrameCount(mTrace.getFrames().size());
+ selectFrame(1);
+ }
+
+ private void createFrameSelectionControls(Composite parent) {
+ Composite c = new Composite(parent, SWT.NONE);
+ c.setLayout(new GridLayout(3, false));
+ GridData gd = new GridData(GridData.FILL_HORIZONTAL);
+ c.setLayoutData(gd);
+
+ Label l = new Label(c, SWT.NONE);
+ l.setText("Select Frame:");
+
+ mFrameSelectionScale = new Scale(c, SWT.HORIZONTAL);
+ mFrameSelectionScale.setMinimum(1);
+ mFrameSelectionScale.setMaximum(1);
+ mFrameSelectionScale.setSelection(0);
+ gd = new GridData(GridData.FILL_HORIZONTAL);
+ mFrameSelectionScale.setLayoutData(gd);
+
+ mFrameSelectionScale.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ int selectedFrame = mFrameSelectionScale.getSelection();
+ mFrameSelectionSpinner.setSelection(selectedFrame);
+ selectFrame(selectedFrame);
+ }
+ });
+
+ mFrameSelectionSpinner = new Spinner(c, SWT.BORDER);
+ gd = new GridData();
+ // width to hold atleast 6 digits
+ gd.widthHint = SwtUtils.getApproximateFontWidth(mFrameSelectionSpinner) * 6;
+ mFrameSelectionSpinner.setLayoutData(gd);
+
+ mFrameSelectionSpinner.setMinimum(1);
+ mFrameSelectionSpinner.setMaximum(1);
+ mFrameSelectionSpinner.setSelection(0);
+ mFrameSelectionSpinner.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ int selectedFrame = mFrameSelectionSpinner.getSelection();
+ mFrameSelectionScale.setSelection(selectedFrame);
+ selectFrame(selectedFrame);
+ }
+ });
+ }
+
+ private void setFrameCount(int nFrames) {
+ boolean en = nFrames > 0;
+ mFrameSelectionScale.setEnabled(en);
+ mFrameSelectionSpinner.setEnabled(en);
+
+ mFrameSelectionScale.setMaximum(nFrames);
+ mFrameSelectionSpinner.setMaximum(nFrames);
+ }
+
+ private void selectFrame(int selectedFrame) {
+ mFrameSelectionScale.setSelection(selectedFrame);
+ mFrameSelectionSpinner.setSelection(selectedFrame);
+
+ synchronized (mTreeRefresherLock) {
+ if (mTrace != null) {
+ GLFrame f = mTrace.getFrame(selectedFrame - 1);
+ mCallStartIndex = f.getStartIndex();
+ mCallEndIndex = f.getEndIndex();
+ } else {
+ mCallStartIndex = mCallEndIndex = 0;
+ }
+
+ mCurrentFrame = selectedFrame - 1;
+
+ scheduleNewRefreshJob();
+ }
+
+ // update minimap view
+ mDurationMinimap.setCallRangeForCurrentFrame(mCallStartIndex, mCallEndIndex);
+ }
+
+ /**
+ * Show only calls from the given context
+ * @param context context id whose calls should be displayed. Illegal values will result in
+ * calls from all contexts being displayed.
+ */
+ private void selectContext(int context) {
+ if (mCurrentlyDisplayedContext == context) {
+ return;
+ }
+
+ synchronized (mTreeRefresherLock) {
+ mCurrentlyDisplayedContext = context;
+ scheduleNewRefreshJob();
+ }
+ }
+
+ private void scheduleNewRefreshJob() {
+ if (mTreeRefresherJob != null) {
+ return;
+ }
+
+ mTreeRefresherJob = new Job("Refresh GL Trace View Tree") {
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ final int start, end, context;
+
+ synchronized (mTreeRefresherLock) {
+ start = mCallStartIndex;
+ end = mCallEndIndex;
+ context = mCurrentlyDisplayedContext;
+
+ mTreeRefresherJob = null;
+ }
+
+ // update tree view in the editor
+ Display.getDefault().syncExec(new Runnable() {
+ @Override
+ public void run() {
+ refreshTree(start, end, context);
+
+ // update the frame summary view
+ if (mFrameSummaryViewPage != null) {
+ mFrameSummaryViewPage.setSelectedFrame(mCurrentFrame);
+ }
+ }
+ });
+ return Status.OK_STATUS;
+ }
+ };
+ mTreeRefresherJob.setPriority(Job.SHORT);
+ mTreeRefresherJob.schedule(TREE_REFRESH_INTERVAL_MS);
+ }
+
+ private void refreshTree(int startCallIndex, int endCallIndex, int contextToDisplay) {
+ mTreeViewerNodes = GLCallGroups.constructCallHierarchy(mTrace,
+ startCallIndex, endCallIndex,
+ contextToDisplay);
+ mFrameTreeViewer.setInput(mTreeViewerNodes);
+ mFrameTreeViewer.refresh();
+ mFrameTreeViewer.expandAll();
+ }
+
+ private void createOptionsBar(Composite parent) {
+ int numColumns = mShowContextSwitcher ? 4 : 3;
+
+ Composite c = new Composite(parent, SWT.NONE);
+ c.setLayout(new GridLayout(numColumns, false));
+ GridData gd = new GridData(GridData.FILL_HORIZONTAL);
+ c.setLayoutData(gd);
+
+ Label l = new Label(c, SWT.NONE);
+ l.setText("Filter:");
+
+ mFilterText = new Text(c, SWT.BORDER | SWT.ICON_SEARCH | SWT.SEARCH | SWT.ICON_CANCEL);
+ mFilterText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ mFilterText.setMessage(DEFAULT_FILTER_MESSAGE);
+ mFilterText.addModifyListener(new ModifyListener() {
+ @Override
+ public void modifyText(ModifyEvent e) {
+ updateAppliedFilters();
+ }
+ });
+
+ if (mShowContextSwitcher) {
+ mContextSwitchCombo = new Combo(c, SWT.BORDER | SWT.READ_ONLY);
+
+ // Setup the combo such that "All Contexts" is the first item,
+ // and then we have an item for each context.
+ mContextSwitchCombo.add("All Contexts");
+ mContextSwitchCombo.select(0);
+ mCurrentlyDisplayedContext = -1; // showing all contexts
+ for (int i = 0; i < mTrace.getContexts().size(); i++) {
+ mContextSwitchCombo.add("Context " + i);
+ }
+
+ mContextSwitchCombo.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ selectContext(mContextSwitchCombo.getSelectionIndex() - 1);
+ }
+ });
+ } else {
+ mCurrentlyDisplayedContext = 0;
+ }
+
+ ToolBar toolBar = new ToolBar(c, SWT.FLAT | SWT.BORDER);
+
+ mExpandAllToolItem = new ToolItem(toolBar, SWT.PUSH);
+ mExpandAllToolItem.setToolTipText("Expand All");
+ if (sExpandAllIcon == null) {
+ ImageDescriptor id = GlTracePlugin.getImageDescriptor("/icons/expandall.png");
+ sExpandAllIcon = id.createImage();
+ }
+ if (sExpandAllIcon != null) {
+ mExpandAllToolItem.setImage(sExpandAllIcon);
+ }
+
+ mCollapseAllToolItem = new ToolItem(toolBar, SWT.PUSH);
+ mCollapseAllToolItem.setToolTipText("Collapse All");
+ mCollapseAllToolItem.setImage(
+ PlatformUI.getWorkbench().getSharedImages().getImage(
+ ISharedImages.IMG_ELCL_COLLAPSEALL));
+
+ mSaveAsToolItem = new ToolItem(toolBar, SWT.PUSH);
+ mSaveAsToolItem.setToolTipText("Export Trace");
+ mSaveAsToolItem.setImage(
+ PlatformUI.getWorkbench().getSharedImages().getImage(
+ ISharedImages.IMG_ETOOL_SAVEAS_EDIT));
+
+ SelectionListener toolbarSelectionListener = new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (e.getSource() == mCollapseAllToolItem) {
+ setTreeItemsExpanded(false);
+ } else if (e.getSource() == mExpandAllToolItem) {
+ setTreeItemsExpanded(true);
+ } else if (e.getSource() == mSaveAsToolItem) {
+ exportTrace();
+ }
+ }
+ };
+ mExpandAllToolItem.addSelectionListener(toolbarSelectionListener);
+ mCollapseAllToolItem.addSelectionListener(toolbarSelectionListener);
+ mSaveAsToolItem.addSelectionListener(toolbarSelectionListener);
+ }
+
+ private void updateAppliedFilters() {
+ mGLCallFilter.setFilters(mFilterText.getText().trim());
+ mFrameTreeViewer.refresh();
+ }
+
+ private void createFrameTraceView(Composite parent) {
+ Composite c = new Composite(parent, SWT.NONE);
+ c.setLayout(new GridLayout(2, false));
+ GridData gd = new GridData(GridData.FILL_BOTH);
+ c.setLayoutData(gd);
+
+ final Tree tree = new Tree(c, SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI);
+ gd = new GridData(GridData.FILL_BOTH);
+ tree.setLayoutData(gd);
+ tree.setLinesVisible(true);
+ tree.setHeaderVisible(true);
+
+ mFrameTreeViewer = new TreeViewer(tree);
+ CellLabelProvider labelProvider = new GLFrameLabelProvider();
+
+ // column showing the GL context id
+ TreeViewerColumn tvc = new TreeViewerColumn(mFrameTreeViewer, SWT.NONE);
+ tvc.setLabelProvider(labelProvider);
+ TreeColumn column = tvc.getColumn();
+ column.setText("Function");
+ column.setWidth(500);
+
+ // column showing the GL function duration (wall clock time)
+ tvc = new TreeViewerColumn(mFrameTreeViewer, SWT.NONE);
+ tvc.setLabelProvider(labelProvider);
+ column = tvc.getColumn();
+ column.setText("Wall Time (ns)");
+ column.setWidth(150);
+ column.setAlignment(SWT.RIGHT);
+
+ // column showing the GL function duration (thread time)
+ tvc = new TreeViewerColumn(mFrameTreeViewer, SWT.NONE);
+ tvc.setLabelProvider(labelProvider);
+ column = tvc.getColumn();
+ column.setText("Thread Time (ns)");
+ column.setWidth(150);
+ column.setAlignment(SWT.RIGHT);
+
+ mFrameTreeViewer.setContentProvider(new GLFrameContentProvider());
+
+ mGLCallFilter = new GLCallFilter();
+ mFrameTreeViewer.addFilter(mGLCallFilter);
+
+ // when the control is resized, give all the additional space
+ // to the function name column.
+ tree.addControlListener(new ControlAdapter() {
+ @Override
+ public void controlResized(ControlEvent e) {
+ int w = mFrameTreeViewer.getTree().getClientArea().width;
+ if (w > 200) {
+ mFrameTreeViewer.getTree().getColumn(2).setWidth(100);
+ mFrameTreeViewer.getTree().getColumn(1).setWidth(100);
+ mFrameTreeViewer.getTree().getColumn(0).setWidth(w - 200);
+ }
+ }
+ });
+
+ mDurationMinimap = new DurationMinimap(c, mTrace);
+ gd = new GridData(GridData.FILL_VERTICAL);
+ gd.widthHint = gd.minimumWidth = mDurationMinimap.getMinimumWidth();
+ mDurationMinimap.setLayoutData(gd);
+ mDurationMinimap.addCallSelectionListener(new ICallSelectionListener() {
+ @Override
+ public void callSelected(int selectedCallIndex) {
+ if (selectedCallIndex > 0 && selectedCallIndex < mTreeViewerNodes.size()) {
+ TreeItem item = tree.getItem(selectedCallIndex);
+ tree.select(item);
+ tree.setTopItem(item);
+ }
+ }
+ });
+
+ mVerticalScrollBar = tree.getVerticalBar();
+ mVerticalScrollBar.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ updateVisibleRange();
+ }
+ });
+ }
+
+ private void updateVisibleRange() {
+ int visibleCallTopIndex = mCallStartIndex;
+ int visibleCallBottomIndex = mCallEndIndex;
+
+ if (mVerticalScrollBar.isEnabled()) {
+ int selection = mVerticalScrollBar.getSelection();
+ int thumb = mVerticalScrollBar.getThumb();
+ int max = mVerticalScrollBar.getMaximum();
+
+ // from the scrollbar values, compute the visible fraction
+ double top = (double) selection / max;
+ double bottom = (double) (selection + thumb) / max;
+
+ // map the fraction to the call indices
+ int range = mCallEndIndex - mCallStartIndex;
+ visibleCallTopIndex = mCallStartIndex + (int) Math.floor(range * top);
+ visibleCallBottomIndex = mCallStartIndex + (int) Math.ceil(range * bottom);
+ }
+
+ mDurationMinimap.setVisibleCallRange(visibleCallTopIndex, visibleCallBottomIndex);
+ }
+
+ @Override
+ public void setFocus() {
+ mFrameTreeViewer.getTree().setFocus();
+ }
+
+ private static class GLFrameContentProvider implements ITreeContentProvider {
+ @Override
+ public void dispose() {
+ }
+
+ @Override
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ }
+
+ @Override
+ public Object[] getElements(Object inputElement) {
+ return getChildren(inputElement);
+ }
+
+ @Override
+ public Object[] getChildren(Object parentElement) {
+ if (parentElement instanceof List<?>) {
+ return ((List<?>) parentElement).toArray();
+ }
+
+ if (!(parentElement instanceof GLCallNode)) {
+ return null;
+ }
+
+ GLCallNode parent = (GLCallNode) parentElement;
+ if (parent.hasChildren()) {
+ return parent.getChildren().toArray();
+ } else {
+ return new Object[0];
+ }
+ }
+
+ @Override
+ public Object getParent(Object element) {
+ if (!(element instanceof GLCallNode)) {
+ return null;
+ }
+
+ return ((GLCallNode) element).getParent();
+ }
+
+ @Override
+ public boolean hasChildren(Object element) {
+ if (!(element instanceof GLCallNode)) {
+ return false;
+ }
+
+ return ((GLCallNode) element).hasChildren();
+ }
+ }
+
+ private class GLFrameLabelProvider extends ColumnLabelProvider {
+ @Override
+ public void update(ViewerCell cell) {
+ Object element = cell.getElement();
+ if (!(element instanceof GLCallNode)) {
+ return;
+ }
+
+ GLCall c = ((GLCallNode) element).getCall();
+
+ if (c.getFunction() == Function.glDrawArrays
+ || c.getFunction() == Function.glDrawElements) {
+ cell.setForeground(mGldrawTextColor);
+ }
+
+ if (c.hasErrors()) {
+ cell.setForeground(mGlCallErrorColor);
+ }
+
+ cell.setText(getColumnText(c, cell.getColumnIndex()));
+ }
+
+ private String getColumnText(GLCall c, int columnIndex) {
+ switch (columnIndex) {
+ case 0:
+ if (c.getFunction() == Function.glPushGroupMarkerEXT) {
+ Object marker = c.getProperty(GLCall.PROPERTY_MARKERNAME);
+ if (marker instanceof String) {
+ return ((String) marker);
+ }
+ }
+ return c.toString();
+ case 1:
+ return formatDuration(c.getWallDuration());
+ case 2:
+ return formatDuration(c.getThreadDuration());
+ default:
+ return Integer.toString(c.getContextId());
+ }
+ }
+
+ private String formatDuration(int time) {
+ // Max duration is in the 10s of milliseconds, so xx,xxx,xxx ns
+ // So we require a format specifier that is 10 characters wide
+ return String.format("%,10d", time); //$NON-NLS-1$
+ }
+ }
+
+ private static class GLCallFilter extends ViewerFilter {
+ private final List<Pattern> mPatterns = new ArrayList<Pattern>();
+
+ public void setFilters(String filter) {
+ mPatterns.clear();
+
+ // split the user input into multiple regexes
+ // we assume that the regexes are OR'ed together i.e., all text that matches
+ // any one of the regexes will be displayed
+ for (String regex : filter.split(" ")) {
+ mPatterns.add(Pattern.compile(regex, Pattern.CASE_INSENSITIVE));
+ }
+ }
+
+ @Override
+ public boolean select(Viewer viewer, Object parentElement, Object element) {
+ if (!(element instanceof GLCallNode)) {
+ return true;
+ }
+
+ String text = getTextUnderNode((GLCallNode) element);
+
+ if (mPatterns.size() == 0) {
+ // match if there are no regex filters
+ return true;
+ }
+
+ for (Pattern p : mPatterns) {
+ Matcher matcher = p.matcher(text);
+ if (matcher.find()) {
+ // match if atleast one of the regexes matches this text
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /** Obtain a string representation of all functions under a given tree node. */
+ private String getTextUnderNode(GLCallNode element) {
+ String func = element.getCall().getFunction().toString();
+ if (!element.hasChildren()) {
+ return func;
+ }
+
+ StringBuilder sb = new StringBuilder(100);
+ sb.append(func);
+
+ for (GLCallNode child : element.getChildren()) {
+ sb.append(getTextUnderNode(child));
+ }
+
+ return sb.toString();
+ }
+ }
+
+ @Override
+ public void addSelectionChangedListener(ISelectionChangedListener listener) {
+ if (mFrameTreeViewer != null) {
+ mFrameTreeViewer.addSelectionChangedListener(listener);
+ }
+ }
+
+ @Override
+ public ISelection getSelection() {
+ if (mFrameTreeViewer != null) {
+ return mFrameTreeViewer.getSelection();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void removeSelectionChangedListener(ISelectionChangedListener listener) {
+ if (mFrameTreeViewer != null) {
+ mFrameTreeViewer.removeSelectionChangedListener(listener);
+ }
+ }
+
+ @Override
+ public void setSelection(ISelection selection) {
+ if (mFrameTreeViewer != null) {
+ mFrameTreeViewer.setSelection(selection);
+ }
+ }
+
+ public GLTrace getTrace() {
+ return mTrace;
+ }
+
+ public StateViewPage getStateViewPage() {
+ if (mStateViewPage == null) {
+ mStateViewPage = new StateViewPage(mTrace);
+ }
+
+ return mStateViewPage;
+ }
+
+ public FrameSummaryViewPage getFrameSummaryViewPage() {
+ if (mFrameSummaryViewPage == null) {
+ mFrameSummaryViewPage = new FrameSummaryViewPage(mTrace);
+ }
+
+ return mFrameSummaryViewPage;
+ }
+
+ public DetailsPage getDetailsPage() {
+ if (mDetailsPage == null) {
+ mDetailsPage = new DetailsPage(mTrace);
+ }
+
+ return mDetailsPage;
+ }
+
+ private void copySelectionToClipboard() {
+ if (mFrameTreeViewer == null || mFrameTreeViewer.getTree().isDisposed()) {
+ return;
+ }
+
+ StringBuilder sb = new StringBuilder();
+
+ for (TreeItem it: mFrameTreeViewer.getTree().getSelection()) {
+ Object data = it.getData();
+ if (data instanceof GLCallNode) {
+ sb.append(((GLCallNode) data).getCall());
+ sb.append(NEWLINE);
+ }
+ }
+
+ if (sb.length() > 0) {
+ Clipboard cb = new Clipboard(Display.getDefault());
+ cb.setContents(
+ new Object[] { sb.toString() },
+ new Transfer[] { TextTransfer.getInstance() });
+ cb.dispose();
+ }
+ }
+
+ private void selectAll() {
+ if (mFrameTreeViewer == null || mFrameTreeViewer.getTree().isDisposed()) {
+ return;
+ }
+
+ mFrameTreeViewer.getTree().selectAll();
+ }
+
+ private void exportTrace() {
+ if (mFrameTreeViewer == null || mFrameTreeViewer.getTree().isDisposed()) {
+ return;
+ }
+
+ if (mCallEndIndex == 0) {
+ return;
+ }
+
+ FileDialog fd = new FileDialog(mFrameTreeViewer.getTree().getShell(), SWT.SAVE);
+ fd.setFilterExtensions(new String[] { "*.txt" });
+ if (sLastExportedToFolder != null) {
+ fd.setFilterPath(sLastExportedToFolder);
+ }
+
+ String path = fd.open();
+ if (path == null) {
+ return;
+ }
+
+ File f = new File(path);
+ sLastExportedToFolder = f.getParent();
+ try {
+ exportFrameTo(f);
+ } catch (IOException e) {
+ ErrorDialog.openError(mFrameTreeViewer.getTree().getShell(),
+ "Export trace file.",
+ "Unexpected error exporting trace file.",
+ new Status(Status.ERROR, GlTracePlugin.PLUGIN_ID, e.toString()));
+ }
+ }
+
+ private void exportFrameTo(File f) throws IOException {
+ String glCalls = serializeGlCalls(mTrace.getGLCalls(), mCallStartIndex, mCallEndIndex);
+ Files.write(glCalls, f, Charsets.UTF_8);
+ }
+
+ private String serializeGlCalls(List<GLCall> glCalls, int start, int end) {
+ StringBuilder sb = new StringBuilder();
+ while (start < end) {
+ sb.append(glCalls.get(start).toString());
+ sb.append("\n"); //$NON-NLS-1$
+ start++;
+ }
+
+ return sb.toString();
+ }
+
+ private void setTreeItemsExpanded(boolean expand) {
+ if (mFrameTreeViewer == null || mFrameTreeViewer.getTree().isDisposed()) {
+ return;
+ }
+
+ if (expand) {
+ mFrameTreeViewer.expandAll();
+ } else {
+ mFrameTreeViewer.collapseAll();
+ }
+ }
+
+ private class TraceViewerFindTarget extends AbstractBufferFindTarget {
+ @Override
+ public int getItemCount() {
+ return mFrameTreeViewer.getTree().getItemCount();
+ }
+
+ @Override
+ public String getItem(int index) {
+ Object data = mFrameTreeViewer.getTree().getItem(index).getData();
+ if (data instanceof GLCallNode) {
+ return ((GLCallNode) data).getCall().toString();
+ }
+ return null;
+ }
+
+ @Override
+ public void selectAndReveal(int index) {
+ Tree t = mFrameTreeViewer.getTree();
+ t.deselectAll();
+ t.select(t.getItem(index));
+ t.showSelection();
+ }
+
+ @Override
+ public int getStartingIndex() {
+ return 0;
+ }
+ };
+
+ private FindDialog mFindDialog;
+ private TraceViewerFindTarget mFindTarget = new TraceViewerFindTarget();
+
+ private void showFindDialog() {
+ if (mFindDialog != null) {
+ // the dialog is already displayed
+ return;
+ }
+
+ mFindDialog = new FindDialog(Display.getDefault().getActiveShell(),
+ mFindTarget,
+ FindDialog.FIND_NEXT_ID);
+ mFindDialog.open(); // blocks until find dialog is closed
+ mFindDialog = null;
+ }
+
+ public String getInputPath() {
+ return mFilePath;
+ }
+}