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