Home | History | Annotate | Download | only in editors
      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.GlTracePlugin;
     20 import com.android.ide.eclipse.gltrace.editors.GLCallGroups.GLCallNode;
     21 import com.android.ide.eclipse.gltrace.model.GLCall;
     22 import com.android.ide.eclipse.gltrace.model.GLTrace;
     23 import com.android.ide.eclipse.gltrace.state.GLState;
     24 import com.android.ide.eclipse.gltrace.state.IGLProperty;
     25 import com.android.ide.eclipse.gltrace.state.StatePrettyPrinter;
     26 import com.android.ide.eclipse.gltrace.state.transforms.IStateTransform;
     27 import com.google.common.base.Charsets;
     28 import com.google.common.io.Files;
     29 
     30 import org.eclipse.core.runtime.IProgressMonitor;
     31 import org.eclipse.core.runtime.IStatus;
     32 import org.eclipse.core.runtime.Status;
     33 import org.eclipse.core.runtime.jobs.ILock;
     34 import org.eclipse.core.runtime.jobs.Job;
     35 import org.eclipse.jface.action.Action;
     36 import org.eclipse.jface.action.IToolBarManager;
     37 import org.eclipse.jface.dialogs.ErrorDialog;
     38 import org.eclipse.jface.layout.GridDataFactory;
     39 import org.eclipse.jface.viewers.ISelection;
     40 import org.eclipse.jface.viewers.ISelectionChangedListener;
     41 import org.eclipse.jface.viewers.ISelectionProvider;
     42 import org.eclipse.jface.viewers.TreeSelection;
     43 import org.eclipse.jface.viewers.TreeViewer;
     44 import org.eclipse.swt.SWT;
     45 import org.eclipse.swt.layout.GridData;
     46 import org.eclipse.swt.widgets.Composite;
     47 import org.eclipse.swt.widgets.Control;
     48 import org.eclipse.swt.widgets.Display;
     49 import org.eclipse.swt.widgets.FileDialog;
     50 import org.eclipse.swt.widgets.Shell;
     51 import org.eclipse.swt.widgets.Tree;
     52 import org.eclipse.swt.widgets.TreeColumn;
     53 import org.eclipse.ui.ISelectionListener;
     54 import org.eclipse.ui.ISharedImages;
     55 import org.eclipse.ui.IWorkbenchPart;
     56 import org.eclipse.ui.PlatformUI;
     57 import org.eclipse.ui.part.IPageSite;
     58 import org.eclipse.ui.part.Page;
     59 
     60 import java.io.File;
     61 import java.io.IOException;
     62 import java.util.ArrayList;
     63 import java.util.Collections;
     64 import java.util.HashSet;
     65 import java.util.List;
     66 import java.util.Set;
     67 
     68 /**
     69  * A tree view of the OpenGL state. It listens to the current GLCall that is selected
     70  * in the Function Trace view, and updates its view to reflect the state as of the selected call.
     71  */
     72 public class StateViewPage extends Page implements ISelectionListener, ISelectionProvider {
     73     public static final String ID = "com.android.ide.eclipse.gltrace.views.GLState"; //$NON-NLS-1$
     74     private static String sLastUsedPath;
     75     private static final ILock sGlStateLock = Job.getJobManager().newLock();
     76 
     77     private GLTrace mTrace;
     78     private List<GLCall> mGLCalls;
     79 
     80     /** OpenGL State as of call {@link #mCurrentStateIndex}. */
     81     private IGLProperty mState;
     82     private int mCurrentStateIndex;
     83 
     84     private String[] TREE_PROPERTIES = { "Name", "Value" };
     85     private TreeViewer mTreeViewer;
     86     private StateLabelProvider mLabelProvider;
     87 
     88     public StateViewPage(GLTrace trace) {
     89         setInput(trace);
     90     }
     91 
     92     public void setInput(GLTrace trace) {
     93         mTrace = trace;
     94         if (trace != null) {
     95             mGLCalls = trace.getGLCalls();
     96         } else {
     97             mGLCalls = null;
     98         }
     99 
    100         mState = GLState.createDefaultState();
    101         mCurrentStateIndex = -1;
    102 
    103         if (mTreeViewer != null) {
    104             mTreeViewer.setInput(mState);
    105             mTreeViewer.refresh();
    106         }
    107     }
    108 
    109     @Override
    110     public void createControl(Composite parent) {
    111         final Tree tree = new Tree(parent, SWT.VIRTUAL | SWT.H_SCROLL | SWT.V_SCROLL);
    112         GridDataFactory.fillDefaults().grab(true, true).applyTo(tree);
    113 
    114         tree.setHeaderVisible(true);
    115         tree.setLinesVisible(true);
    116         tree.setLayoutData(new GridData(GridData.FILL_BOTH));
    117 
    118         TreeColumn col1 = new TreeColumn(tree, SWT.LEFT);
    119         col1.setText(TREE_PROPERTIES[0]);
    120         col1.setWidth(200);
    121 
    122         TreeColumn col2 = new TreeColumn(tree, SWT.LEFT);
    123         col2.setText(TREE_PROPERTIES[1]);
    124         col2.setWidth(200);
    125 
    126         mTreeViewer = new TreeViewer(tree);
    127         mTreeViewer.setContentProvider(new StateContentProvider());
    128         mLabelProvider = new StateLabelProvider();
    129         mTreeViewer.setLabelProvider(mLabelProvider);
    130         mTreeViewer.setInput(mState);
    131         mTreeViewer.refresh();
    132 
    133         final IToolBarManager manager = getSite().getActionBars().getToolBarManager();
    134         manager.add(new Action("Save to File",
    135                 PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(
    136                         ISharedImages.IMG_ETOOL_SAVEAS_EDIT)) {
    137             @Override
    138             public void run() {
    139                 saveCurrentState();
    140             }
    141         });
    142     }
    143 
    144     private void saveCurrentState() {
    145         final Shell shell = mTreeViewer.getTree().getShell();
    146         FileDialog fd = new FileDialog(shell, SWT.SAVE);
    147         fd.setFilterExtensions(new String[] { "*.txt" });
    148         if (sLastUsedPath != null) {
    149             fd.setFilterPath(sLastUsedPath);
    150         }
    151 
    152         String path = fd.open();
    153         if (path == null) {
    154             return;
    155         }
    156 
    157         File f = new File(path);
    158         sLastUsedPath = f.getParent();
    159 
    160         // export state to f
    161         StatePrettyPrinter pp = new StatePrettyPrinter();
    162         synchronized (sGlStateLock) {
    163             mState.prettyPrint(pp);
    164         }
    165 
    166         try {
    167             Files.write(pp.toString(), f, Charsets.UTF_8);
    168         } catch (IOException e) {
    169             ErrorDialog.openError(shell,
    170                     "Export GL State",
    171                     "Unexpected error while writing GL state to file.",
    172                     new Status(Status.ERROR, GlTracePlugin.PLUGIN_ID, e.toString()));
    173         }
    174     }
    175 
    176     @Override
    177     public void init(IPageSite pageSite) {
    178         super.init(pageSite);
    179         pageSite.getPage().addSelectionListener(this);
    180     }
    181 
    182     @Override
    183     public void dispose() {
    184         getSite().getPage().removeSelectionListener(this);
    185         super.dispose();
    186     }
    187 
    188     @Override
    189     public void selectionChanged(IWorkbenchPart part, ISelection selection) {
    190         if (!(part instanceof GLFunctionTraceViewer)) {
    191             return;
    192         }
    193 
    194         if (((GLFunctionTraceViewer) part).getTrace() != mTrace) {
    195             return;
    196         }
    197 
    198         if (!(selection instanceof TreeSelection)) {
    199             return;
    200         }
    201 
    202         GLCall selectedCall = null;
    203 
    204         Object data = ((TreeSelection) selection).getFirstElement();
    205         if (data instanceof GLCallNode) {
    206             selectedCall = ((GLCallNode) data).getCall();
    207         }
    208 
    209         if (selectedCall == null) {
    210             return;
    211         }
    212 
    213         final int selectedCallIndex = selectedCall.getIndex();
    214 
    215         // Creation of texture images takes a few seconds on the first run. So run
    216         // the update task as an Eclipse job.
    217         Job job = new Job("Updating GL State") {
    218             @Override
    219             protected IStatus run(IProgressMonitor monitor) {
    220                 Set<IGLProperty> changedProperties = null;
    221 
    222                 try {
    223                     sGlStateLock.acquire();
    224                     changedProperties = updateState(mCurrentStateIndex,
    225                             selectedCallIndex);
    226                     mCurrentStateIndex = selectedCallIndex;
    227                 } catch (Exception e) {
    228                     GlTracePlugin.getDefault().logMessage(
    229                             "Unexpected error while updating GL State.");
    230                     GlTracePlugin.getDefault().logMessage(e.getMessage());
    231                     return new Status(Status.ERROR,
    232                             GlTracePlugin.PLUGIN_ID,
    233                             "Unexpected error while updating GL State.",
    234                             e);
    235                 } finally {
    236                     sGlStateLock.release();
    237                 }
    238 
    239                 mLabelProvider.setChangedProperties(changedProperties);
    240                 Display.getDefault().syncExec(new Runnable() {
    241                     @Override
    242                     public void run() {
    243                         if (!mTreeViewer.getTree().isDisposed()) {
    244                             mTreeViewer.refresh();
    245                         }
    246                     }
    247                 });
    248 
    249                 return Status.OK_STATUS;
    250             }
    251         };
    252         job.setPriority(Job.SHORT);
    253         job.schedule();
    254     }
    255 
    256     @Override
    257     public Control getControl() {
    258         if (mTreeViewer == null) {
    259             return null;
    260         }
    261 
    262         return mTreeViewer.getControl();
    263     }
    264 
    265     @Override
    266     public void setFocus() {
    267     }
    268 
    269     /**
    270      * Update GL state from GL call at fromIndex to the call at toIndex.
    271      * If fromIndex < toIndex, the GL state will be updated by applying all the transformations
    272      * corresponding to calls from (fromIndex + 1) to toIndex (inclusive).
    273      * If fromIndex > toIndex, the GL state will be updated by reverting all the calls from
    274      * fromIndex (inclusive) to (toIndex + 1).
    275      * @return GL state properties that changed as a result of this update.
    276      */
    277     private Set<IGLProperty> updateState(int fromIndex, int toIndex) {
    278         assert fromIndex >= -1 && fromIndex < mGLCalls.size();
    279         assert toIndex >= 0 && toIndex < mGLCalls.size();
    280 
    281         if (fromIndex < toIndex) {
    282             return applyTransformations(fromIndex, toIndex);
    283         } else if (fromIndex > toIndex) {
    284             return revertTransformations(fromIndex, toIndex);
    285         } else {
    286             return Collections.emptySet();
    287         }
    288     }
    289 
    290     private Set<IGLProperty> applyTransformations(int fromIndex, int toIndex) {
    291         int setSizeHint = 3 * (toIndex - fromIndex) + 10;
    292         Set<IGLProperty> changedProperties = new HashSet<IGLProperty>(setSizeHint);
    293 
    294         for (int i = fromIndex + 1; i <= toIndex; i++) {
    295             GLCall call = mGLCalls.get(i);
    296             for (IStateTransform f : call.getStateTransformations()) {
    297                 try {
    298                     f.apply(mState);
    299                     IGLProperty changedProperty = f.getChangedProperty(mState);
    300                     if (changedProperty != null) {
    301                         changedProperties.addAll(getHierarchy(changedProperty));
    302                     }
    303                 } catch (Exception e) {
    304                     GlTracePlugin.getDefault().logMessage("Error applying transformations for "
    305                             + call);
    306                     GlTracePlugin.getDefault().logMessage(e.toString());
    307                 }
    308             }
    309         }
    310 
    311         return changedProperties;
    312     }
    313 
    314     private Set<IGLProperty> revertTransformations(int fromIndex, int toIndex) {
    315         int setSizeHint = 3 * (fromIndex - toIndex) + 10;
    316         Set<IGLProperty> changedProperties = new HashSet<IGLProperty>(setSizeHint);
    317 
    318         for (int i = fromIndex; i > toIndex; i--) {
    319             List<IStateTransform> transforms = mGLCalls.get(i).getStateTransformations();
    320             // When reverting transformations, iterate from the last to first so that the reversals
    321             // are performed in the correct sequence.
    322             for (int j = transforms.size() - 1; j >= 0; j--) {
    323                 IStateTransform f = transforms.get(j);
    324                 f.revert(mState);
    325 
    326                 IGLProperty changedProperty = f.getChangedProperty(mState);
    327                 if (changedProperty != null) {
    328                     changedProperties.addAll(getHierarchy(changedProperty));
    329                 }
    330             }
    331         }
    332 
    333         return changedProperties;
    334     }
    335 
    336     /**
    337      * Obtain the list of properties starting from the provided property up to
    338      * the root of GL state.
    339      */
    340     private List<IGLProperty> getHierarchy(IGLProperty changedProperty) {
    341         List<IGLProperty> changedProperties = new ArrayList<IGLProperty>(5);
    342         changedProperties.add(changedProperty);
    343 
    344         // add the entire parent chain until we reach the root
    345         IGLProperty prop = changedProperty;
    346         while ((prop = prop.getParent()) != null) {
    347             changedProperties.add(prop);
    348         }
    349 
    350         return changedProperties;
    351     }
    352 
    353     @Override
    354     public void addSelectionChangedListener(ISelectionChangedListener listener) {
    355         mTreeViewer.addSelectionChangedListener(listener);
    356     }
    357 
    358     @Override
    359     public ISelection getSelection() {
    360         return mTreeViewer.getSelection();
    361     }
    362 
    363     @Override
    364     public void removeSelectionChangedListener(ISelectionChangedListener listener) {
    365         mTreeViewer.removeSelectionChangedListener(listener);
    366     }
    367 
    368     @Override
    369     public void setSelection(ISelection selection) {
    370         mTreeViewer.setSelection(selection);
    371     }
    372 }
    373