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