| /* |
| * 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; |
| } |
| } |