| /* |
| * 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.ide.eclipse.gltrace.GlTracePlugin; |
| import com.android.ide.eclipse.gltrace.editors.GLCallGroups.GLCallNode; |
| import com.android.ide.eclipse.gltrace.model.GLCall; |
| import com.android.ide.eclipse.gltrace.model.GLTrace; |
| import com.android.ide.eclipse.gltrace.state.GLState; |
| import com.android.ide.eclipse.gltrace.state.IGLProperty; |
| import com.android.ide.eclipse.gltrace.state.StatePrettyPrinter; |
| import com.android.ide.eclipse.gltrace.state.transforms.IStateTransform; |
| 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.ILock; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.jface.action.Action; |
| import org.eclipse.jface.action.IToolBarManager; |
| import org.eclipse.jface.dialogs.ErrorDialog; |
| import org.eclipse.jface.layout.GridDataFactory; |
| import org.eclipse.jface.viewers.ISelection; |
| import org.eclipse.jface.viewers.ISelectionChangedListener; |
| import org.eclipse.jface.viewers.ISelectionProvider; |
| import org.eclipse.jface.viewers.TreeSelection; |
| import org.eclipse.jface.viewers.TreeViewer; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.FileDialog; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.swt.widgets.Tree; |
| import org.eclipse.swt.widgets.TreeColumn; |
| import org.eclipse.ui.ISelectionListener; |
| import org.eclipse.ui.ISharedImages; |
| import org.eclipse.ui.IWorkbenchPart; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.part.IPageSite; |
| import org.eclipse.ui.part.Page; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * A tree view of the OpenGL state. It listens to the current GLCall that is selected |
| * in the Function Trace view, and updates its view to reflect the state as of the selected call. |
| */ |
| public class StateViewPage extends Page implements ISelectionListener, ISelectionProvider { |
| public static final String ID = "com.android.ide.eclipse.gltrace.views.GLState"; //$NON-NLS-1$ |
| private static String sLastUsedPath; |
| private static final ILock sGlStateLock = Job.getJobManager().newLock(); |
| |
| private GLTrace mTrace; |
| private List<GLCall> mGLCalls; |
| |
| /** OpenGL State as of call {@link #mCurrentStateIndex}. */ |
| private IGLProperty mState; |
| private int mCurrentStateIndex; |
| |
| private String[] TREE_PROPERTIES = { "Name", "Value" }; |
| private TreeViewer mTreeViewer; |
| private StateLabelProvider mLabelProvider; |
| |
| public StateViewPage(GLTrace trace) { |
| setInput(trace); |
| } |
| |
| public void setInput(GLTrace trace) { |
| mTrace = trace; |
| if (trace != null) { |
| mGLCalls = trace.getGLCalls(); |
| } else { |
| mGLCalls = null; |
| } |
| |
| mState = GLState.createDefaultState(); |
| mCurrentStateIndex = -1; |
| |
| if (mTreeViewer != null) { |
| mTreeViewer.setInput(mState); |
| mTreeViewer.refresh(); |
| } |
| } |
| |
| @Override |
| public void createControl(Composite parent) { |
| final Tree tree = new Tree(parent, SWT.VIRTUAL | SWT.H_SCROLL | SWT.V_SCROLL); |
| GridDataFactory.fillDefaults().grab(true, true).applyTo(tree); |
| |
| tree.setHeaderVisible(true); |
| tree.setLinesVisible(true); |
| tree.setLayoutData(new GridData(GridData.FILL_BOTH)); |
| |
| TreeColumn col1 = new TreeColumn(tree, SWT.LEFT); |
| col1.setText(TREE_PROPERTIES[0]); |
| col1.setWidth(200); |
| |
| TreeColumn col2 = new TreeColumn(tree, SWT.LEFT); |
| col2.setText(TREE_PROPERTIES[1]); |
| col2.setWidth(200); |
| |
| mTreeViewer = new TreeViewer(tree); |
| mTreeViewer.setContentProvider(new StateContentProvider()); |
| mLabelProvider = new StateLabelProvider(); |
| mTreeViewer.setLabelProvider(mLabelProvider); |
| mTreeViewer.setInput(mState); |
| mTreeViewer.refresh(); |
| |
| final IToolBarManager manager = getSite().getActionBars().getToolBarManager(); |
| manager.add(new Action("Save to File", |
| PlatformUI.getWorkbench().getSharedImages().getImageDescriptor( |
| ISharedImages.IMG_ETOOL_SAVEAS_EDIT)) { |
| @Override |
| public void run() { |
| saveCurrentState(); |
| } |
| }); |
| } |
| |
| private void saveCurrentState() { |
| final Shell shell = mTreeViewer.getTree().getShell(); |
| FileDialog fd = new FileDialog(shell, SWT.SAVE); |
| fd.setFilterExtensions(new String[] { "*.txt" }); |
| if (sLastUsedPath != null) { |
| fd.setFilterPath(sLastUsedPath); |
| } |
| |
| String path = fd.open(); |
| if (path == null) { |
| return; |
| } |
| |
| File f = new File(path); |
| sLastUsedPath = f.getParent(); |
| |
| // export state to f |
| StatePrettyPrinter pp = new StatePrettyPrinter(); |
| synchronized (sGlStateLock) { |
| mState.prettyPrint(pp); |
| } |
| |
| try { |
| Files.write(pp.toString(), f, Charsets.UTF_8); |
| } catch (IOException e) { |
| ErrorDialog.openError(shell, |
| "Export GL State", |
| "Unexpected error while writing GL state to file.", |
| new Status(Status.ERROR, GlTracePlugin.PLUGIN_ID, e.toString())); |
| } |
| } |
| |
| @Override |
| public void init(IPageSite pageSite) { |
| super.init(pageSite); |
| pageSite.getPage().addSelectionListener(this); |
| } |
| |
| @Override |
| public void dispose() { |
| getSite().getPage().removeSelectionListener(this); |
| super.dispose(); |
| } |
| |
| @Override |
| public void selectionChanged(IWorkbenchPart part, ISelection selection) { |
| if (!(part instanceof GLFunctionTraceViewer)) { |
| return; |
| } |
| |
| if (((GLFunctionTraceViewer) part).getTrace() != mTrace) { |
| return; |
| } |
| |
| if (!(selection instanceof TreeSelection)) { |
| return; |
| } |
| |
| GLCall selectedCall = null; |
| |
| Object data = ((TreeSelection) selection).getFirstElement(); |
| if (data instanceof GLCallNode) { |
| selectedCall = ((GLCallNode) data).getCall(); |
| } |
| |
| if (selectedCall == null) { |
| return; |
| } |
| |
| final int selectedCallIndex = selectedCall.getIndex(); |
| |
| // Creation of texture images takes a few seconds on the first run. So run |
| // the update task as an Eclipse job. |
| Job job = new Job("Updating GL State") { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| Set<IGLProperty> changedProperties = null; |
| |
| try { |
| sGlStateLock.acquire(); |
| changedProperties = updateState(mCurrentStateIndex, |
| selectedCallIndex); |
| mCurrentStateIndex = selectedCallIndex; |
| } catch (Exception e) { |
| GlTracePlugin.getDefault().logMessage( |
| "Unexpected error while updating GL State."); |
| GlTracePlugin.getDefault().logMessage(e.getMessage()); |
| return new Status(Status.ERROR, |
| GlTracePlugin.PLUGIN_ID, |
| "Unexpected error while updating GL State.", |
| e); |
| } finally { |
| sGlStateLock.release(); |
| } |
| |
| mLabelProvider.setChangedProperties(changedProperties); |
| Display.getDefault().syncExec(new Runnable() { |
| @Override |
| public void run() { |
| if (!mTreeViewer.getTree().isDisposed()) { |
| mTreeViewer.refresh(); |
| } |
| } |
| }); |
| |
| return Status.OK_STATUS; |
| } |
| }; |
| job.setPriority(Job.SHORT); |
| job.schedule(); |
| } |
| |
| @Override |
| public Control getControl() { |
| if (mTreeViewer == null) { |
| return null; |
| } |
| |
| return mTreeViewer.getControl(); |
| } |
| |
| @Override |
| public void setFocus() { |
| } |
| |
| /** |
| * Update GL state from GL call at fromIndex to the call at toIndex. |
| * If fromIndex < toIndex, the GL state will be updated by applying all the transformations |
| * corresponding to calls from (fromIndex + 1) to toIndex (inclusive). |
| * If fromIndex > toIndex, the GL state will be updated by reverting all the calls from |
| * fromIndex (inclusive) to (toIndex + 1). |
| * @return GL state properties that changed as a result of this update. |
| */ |
| private Set<IGLProperty> updateState(int fromIndex, int toIndex) { |
| assert fromIndex >= -1 && fromIndex < mGLCalls.size(); |
| assert toIndex >= 0 && toIndex < mGLCalls.size(); |
| |
| if (fromIndex < toIndex) { |
| return applyTransformations(fromIndex, toIndex); |
| } else if (fromIndex > toIndex) { |
| return revertTransformations(fromIndex, toIndex); |
| } else { |
| return Collections.emptySet(); |
| } |
| } |
| |
| private Set<IGLProperty> applyTransformations(int fromIndex, int toIndex) { |
| int setSizeHint = 3 * (toIndex - fromIndex) + 10; |
| Set<IGLProperty> changedProperties = new HashSet<IGLProperty>(setSizeHint); |
| |
| for (int i = fromIndex + 1; i <= toIndex; i++) { |
| GLCall call = mGLCalls.get(i); |
| for (IStateTransform f : call.getStateTransformations()) { |
| try { |
| f.apply(mState); |
| IGLProperty changedProperty = f.getChangedProperty(mState); |
| if (changedProperty != null) { |
| changedProperties.addAll(getHierarchy(changedProperty)); |
| } |
| } catch (Exception e) { |
| GlTracePlugin.getDefault().logMessage("Error applying transformations for " |
| + call); |
| GlTracePlugin.getDefault().logMessage(e.toString()); |
| } |
| } |
| } |
| |
| return changedProperties; |
| } |
| |
| private Set<IGLProperty> revertTransformations(int fromIndex, int toIndex) { |
| int setSizeHint = 3 * (fromIndex - toIndex) + 10; |
| Set<IGLProperty> changedProperties = new HashSet<IGLProperty>(setSizeHint); |
| |
| for (int i = fromIndex; i > toIndex; i--) { |
| List<IStateTransform> transforms = mGLCalls.get(i).getStateTransformations(); |
| // When reverting transformations, iterate from the last to first so that the reversals |
| // are performed in the correct sequence. |
| for (int j = transforms.size() - 1; j >= 0; j--) { |
| IStateTransform f = transforms.get(j); |
| f.revert(mState); |
| |
| IGLProperty changedProperty = f.getChangedProperty(mState); |
| if (changedProperty != null) { |
| changedProperties.addAll(getHierarchy(changedProperty)); |
| } |
| } |
| } |
| |
| return changedProperties; |
| } |
| |
| /** |
| * Obtain the list of properties starting from the provided property up to |
| * the root of GL state. |
| */ |
| private List<IGLProperty> getHierarchy(IGLProperty changedProperty) { |
| List<IGLProperty> changedProperties = new ArrayList<IGLProperty>(5); |
| changedProperties.add(changedProperty); |
| |
| // add the entire parent chain until we reach the root |
| IGLProperty prop = changedProperty; |
| while ((prop = prop.getParent()) != null) { |
| changedProperties.add(prop); |
| } |
| |
| return changedProperties; |
| } |
| |
| @Override |
| public void addSelectionChangedListener(ISelectionChangedListener listener) { |
| mTreeViewer.addSelectionChangedListener(listener); |
| } |
| |
| @Override |
| public ISelection getSelection() { |
| return mTreeViewer.getSelection(); |
| } |
| |
| @Override |
| public void removeSelectionChangedListener(ISelectionChangedListener listener) { |
| mTreeViewer.removeSelectionChangedListener(listener); |
| } |
| |
| @Override |
| public void setSelection(ISelection selection) { |
| mTreeViewer.setSelection(selection); |
| } |
| } |