1 /* 2 ** Copyright 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.gldebugger; 18 19 import com.android.ide.eclipse.gldebugger.DebuggerMessage.Message; 20 import com.android.ide.eclipse.gldebugger.DebuggerMessage.Message.DataType; 21 import com.android.ide.eclipse.gldebugger.DebuggerMessage.Message.Function; 22 import com.android.ide.eclipse.gldebugger.DebuggerMessage.Message.Prop; 23 import com.android.sdklib.util.SparseArray; 24 import com.android.sdklib.util.SparseIntArray; 25 import com.google.protobuf.ByteString; 26 27 import org.eclipse.jface.viewers.ISelectionChangedListener; 28 import org.eclipse.jface.viewers.ITreeContentProvider; 29 import org.eclipse.jface.viewers.LabelProvider; 30 import org.eclipse.jface.viewers.SelectionChangedEvent; 31 import org.eclipse.jface.viewers.StructuredSelection; 32 import org.eclipse.jface.viewers.Viewer; 33 import org.eclipse.swt.graphics.Image; 34 import org.eclipse.swt.widgets.Display; 35 36 import java.io.FileNotFoundException; 37 import java.io.IOException; 38 import java.io.RandomAccessFile; 39 import java.lang.reflect.Array; 40 import java.lang.reflect.Field; 41 import java.nio.ByteBuffer; 42 import java.nio.ByteOrder; 43 import java.util.ArrayList; 44 import java.util.Collection; 45 import java.util.Map; 46 import java.util.Set; 47 48 class Frame { 49 public final long filePosition; 50 private int callsCount; 51 52 final Context startContext; 53 private ArrayList<MessageData> calls = new ArrayList<MessageData>(); 54 55 Frame(final Context context, final long filePosition) { 56 this.startContext = context.clone(); 57 this.filePosition = filePosition; 58 } 59 60 void add(final MessageData msgData) { 61 calls.add(msgData); 62 } 63 64 void increaseCallsCount() { 65 callsCount++; 66 } 67 68 Context computeContext(final MessageData call) { 69 Context ctx = startContext.clone(); 70 for (int i = 0; i < calls.size(); i++) 71 if (call == calls.get(i)) 72 return ctx; 73 else 74 ctx.processMessage(calls.get(i).msg); 75 assert false; 76 return ctx; 77 } 78 79 int size() { 80 return callsCount; 81 } 82 83 MessageData get(final int i) { 84 return calls.get(i); 85 } 86 87 ArrayList<MessageData> get() { 88 return calls; 89 } 90 91 void unload() { 92 if (calls == null) 93 return; 94 calls.clear(); 95 calls = null; 96 } 97 98 void load(final RandomAccessFile file) { 99 if (calls != null && calls.size() == callsCount) 100 return; 101 try { 102 Context ctx = startContext.clone(); 103 calls = new ArrayList<MessageData>(callsCount); 104 final long oriPosition = file.getFilePointer(); 105 file.seek(filePosition); 106 for (int i = 0; i < callsCount; i++) { 107 int len = file.readInt(); 108 if (GLFramesView.TARGET_BYTE_ORDER == ByteOrder.LITTLE_ENDIAN) 109 len = Integer.reverseBytes(len); 110 final byte[] data = new byte[len]; 111 file.read(data); 112 Message msg = Message.parseFrom(data); 113 ctx.processMessage(msg); 114 final MessageData msgData = new MessageData(Display.getCurrent(), msg, ctx); 115 calls.add(msgData); 116 } 117 file.seek(oriPosition); 118 } catch (IOException e) { 119 e.printStackTrace(); 120 assert false; 121 } 122 } 123 } 124 125 class DebugContext { 126 boolean uiUpdate = false; 127 final int contextId; 128 Context currentContext; 129 private ArrayList<Frame> frames = new ArrayList<Frame>(128); 130 private Frame lastFrame; 131 private Frame loadedFrame; 132 private RandomAccessFile file; 133 134 DebugContext(final int contextId) { 135 this.contextId = contextId; 136 currentContext = new Context(contextId); 137 try { 138 file = new RandomAccessFile("0x" + Integer.toHexString(contextId) + 139 ".gles2dbg", "rw"); 140 } catch (FileNotFoundException e) { 141 e.printStackTrace(); 142 assert false; 143 } 144 } 145 146 /** write message to file; if frame not null, then increase its call count */ 147 void saveMessage(final Message msg, final RandomAccessFile file, Frame frame) { 148 synchronized (file) { 149 if (frame != null) 150 frame.increaseCallsCount(); 151 final byte[] data = msg.toByteArray(); 152 final ByteBuffer len = ByteBuffer.allocate(4); 153 len.order(GLFramesView.TARGET_BYTE_ORDER); 154 len.putInt(data.length); 155 try { 156 if (GLFramesView.TARGET_BYTE_ORDER == ByteOrder.BIG_ENDIAN) 157 file.writeInt(data.length); 158 else 159 file.writeInt(Integer.reverseBytes(data.length)); 160 file.write(data); 161 } catch (IOException e) { 162 e.printStackTrace(); 163 assert false; 164 } 165 } 166 } 167 168 /** 169 * Caches new Message, and formats into MessageData for current frame; this 170 * function is called exactly once for each new Message 171 */ 172 void processMessage(final Message newMsg) { 173 Message msg = newMsg; 174 if (msg.getFunction() == Function.SETPROP) { 175 // GL impl. consts should have been sent before any GL call messages 176 assert frames.size() == 0; 177 assert lastFrame == null; 178 assert msg.getProp() == Prop.GLConstant; 179 switch (GLEnum.valueOf(msg.getArg0())) { 180 case GL_MAX_VERTEX_ATTRIBS: 181 currentContext.serverVertex = new GLServerVertex(msg.getArg1()); 182 break; 183 case GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS: 184 currentContext.serverTexture = new GLServerTexture(currentContext, 185 msg.getArg1()); 186 break; 187 default: 188 assert false; 189 return; 190 } 191 saveMessage(msg, file, null); 192 return; 193 } 194 195 if (lastFrame == null) { 196 // first real message after the GL impl. consts 197 synchronized (file) { 198 try { 199 lastFrame = new Frame(currentContext, file.getFilePointer()); 200 } catch (IOException e) { 201 e.printStackTrace(); 202 assert false; 203 } 204 } 205 synchronized (frames) { 206 frames.add(lastFrame); 207 } 208 assert loadedFrame == null; 209 loadedFrame = lastFrame; 210 } 211 currentContext.processMessage(msg); 212 if (msg.hasDataType() && msg.getDataType() == DataType.ReferencedImage) { 213 // decode referenced image so it doesn't rely on context later on 214 final byte[] referenced = MessageProcessor.lzfDecompressChunks(msg.getData()); 215 currentContext.readPixelRef = MessageProcessor.decodeReferencedImage( 216 currentContext.readPixelRef, referenced); 217 final byte[] decoded = MessageProcessor.lzfCompressChunks( 218 currentContext.readPixelRef, referenced.length); 219 msg = msg.toBuilder().setDataType(DataType.NonreferencedImage) 220 .setData(ByteString.copyFrom(decoded)).build(); 221 } 222 saveMessage(msg, file, lastFrame); 223 if (loadedFrame == lastFrame) { 224 // frame selected for view, so format MessageData 225 final MessageData msgData = new MessageData(Display.getCurrent(), msg, currentContext); 226 lastFrame.add(msgData); 227 uiUpdate = true; 228 } 229 if (msg.getFunction() != Function.eglSwapBuffers) 230 return; 231 synchronized (frames) { 232 if (loadedFrame != lastFrame) 233 lastFrame.unload(); 234 try { 235 frames.add(lastFrame = new Frame(currentContext, file.getFilePointer())); 236 // file.getChannel().force(false); 237 uiUpdate = true; 238 } catch (IOException e) { 239 e.printStackTrace(); 240 assert false; 241 } 242 } 243 return; 244 } 245 246 Frame getFrame(int index) { 247 synchronized (frames) { 248 Frame newFrame = frames.get(index); 249 if (loadedFrame != null && loadedFrame != lastFrame && newFrame != loadedFrame) { 250 loadedFrame.unload(); 251 uiUpdate = true; 252 } 253 loadedFrame = newFrame; 254 synchronized (file) { 255 loadedFrame.load(file); 256 } 257 return loadedFrame; 258 } 259 } 260 261 int frameCount() { 262 synchronized (frames) { 263 return frames.size(); 264 } 265 } 266 } 267 268 /** aggregate of GL states */ 269 public class Context implements Cloneable { 270 public final int contextId; 271 public ArrayList<Context> shares = new ArrayList<Context>(); // self too 272 public GLServerVertex serverVertex; 273 public GLServerShader serverShader = new GLServerShader(this); 274 public GLServerState serverState = new GLServerState(this); 275 public GLServerTexture serverTexture; 276 277 byte[] readPixelRef = new byte[0]; 278 279 public Context(int contextId) { 280 this.contextId = contextId; 281 shares.add(this); 282 } 283 284 @Override 285 public Context clone() { 286 try { 287 Context copy = (Context) super.clone(); 288 // FIXME: context sharing list clone 289 copy.shares = new ArrayList<Context>(1); 290 copy.shares.add(copy); 291 if (serverVertex != null) 292 copy.serverVertex = serverVertex.clone(); 293 copy.serverShader = serverShader.clone(copy); 294 copy.serverState = serverState.clone(); 295 if (serverTexture != null) 296 copy.serverTexture = serverTexture.clone(copy); 297 // don't need to clone readPixelsRef, since referenced images 298 // are decoded when they are encountered 299 return copy; 300 } catch (CloneNotSupportedException e) { 301 e.printStackTrace(); 302 assert false; 303 return null; 304 } 305 } 306 307 /** mainly updating states */ 308 public void processMessage(Message msg) { 309 if (serverVertex.process(msg)) 310 return; 311 if (serverShader.processMessage(msg)) 312 return; 313 if (serverState.processMessage(msg)) 314 return; 315 if (serverTexture.processMessage(msg)) 316 return; 317 } 318 } 319 320 class ContextViewProvider extends LabelProvider implements ITreeContentProvider, 321 ISelectionChangedListener { 322 Context context; 323 final GLFramesView sampleView; 324 325 ContextViewProvider(final GLFramesView sampleView) { 326 this.sampleView = sampleView; 327 } 328 329 @Override 330 public void dispose() { 331 } 332 333 @Override 334 public String getText(Object obj) { 335 if (obj == null) 336 return "null"; 337 if (obj instanceof Entry) { 338 Entry entry = (Entry) obj; 339 String objStr = "null (or default)"; 340 if (entry.obj != null) { 341 objStr = entry.obj.toString(); 342 if (entry.obj instanceof Message) 343 objStr = MessageFormatter.format((Message) entry.obj, false); 344 } 345 return entry.name + " = " + objStr; 346 } 347 return obj.toString(); 348 } 349 350 @Override 351 public Image getImage(Object obj) { 352 if (!(obj instanceof Entry)) 353 return null; 354 final Entry entry = (Entry) obj; 355 if (!(entry.obj instanceof Message)) 356 return null; 357 final Message msg = (Message) entry.obj; 358 switch (msg.getFunction()) { 359 case glTexImage2D: 360 case glTexSubImage2D: 361 case glCopyTexImage2D: 362 case glCopyTexSubImage2D: { 363 entry.image = new MessageData(Display.getCurrent(), msg, null).getImage(); 364 if (entry.image == null) 365 return null; 366 return new Image(Display.getCurrent(), entry.image.getImageData().scaledTo(96, 96)); 367 } 368 default: 369 return null; 370 } 371 } 372 373 @Override 374 public void selectionChanged(SelectionChangedEvent event) { 375 StructuredSelection selection = (StructuredSelection) event 376 .getSelection(); 377 if (null == selection) 378 return; 379 final Object obj = selection.getFirstElement(); 380 if (!(obj instanceof Entry)) 381 return; 382 final Entry entry = (Entry) obj; 383 if (entry.image == null) 384 return; 385 sampleView.tabFolder.setSelection(sampleView.tabItemImage); 386 sampleView.canvas.setBackgroundImage(entry.image); 387 sampleView.canvas.redraw(); 388 } 389 390 @Override 391 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { 392 context = (Context) newInput; 393 } 394 395 class Entry { 396 String name; 397 Object obj; 398 Image image; 399 400 Entry(String name, Object obj) { 401 this.name = name; 402 this.obj = obj; 403 } 404 } 405 406 @Override 407 public Object[] getElements(Object inputElement) { 408 if (inputElement != context) 409 return null; 410 return getChildren(new Entry("Context", inputElement)); 411 } 412 413 @Override 414 public Object[] getChildren(Object parentElement) { 415 if (!(parentElement instanceof Entry)) 416 return null; 417 Entry entry = (Entry) parentElement; 418 ArrayList<Object> children = new ArrayList<Object>(); 419 if (entry.obj == context.serverState.enableDisables) { 420 for (int i = 0; i < context.serverState.enableDisables.size(); i++) { 421 final int key = context.serverState.enableDisables.keyAt(i); 422 final int value = context.serverState.enableDisables.valueAt(i); 423 children.add(GLEnum.valueOf(key).name() + " = " + value); 424 } 425 } else if (entry.obj == context.serverState.integers) { 426 for (int i = 0; i < context.serverState.integers.size(); i++) { 427 final int key = context.serverState.integers.keyAt(i); 428 final Message val = context.serverState.integers.valueAt(i); 429 if (val != null) 430 children.add(GLEnum.valueOf(key).name() + " : " + 431 MessageFormatter.format(val, false)); 432 else 433 children.add(GLEnum.valueOf(key).name() + " : default"); 434 } 435 } else if (entry.obj == context.serverState.lastSetter) { 436 for (int i = 0; i < context.serverState.lastSetter.size(); i++) { 437 final int key = context.serverState.lastSetter.keyAt(i); 438 final Message msg = context.serverState.lastSetter.valueAt(i); 439 if (msg == null) 440 children.add(Function.valueOf(key).name() + " : default"); 441 else 442 children.add(Function.valueOf(key).name() + " : " 443 + MessageFormatter.format(msg, false)); 444 } 445 } else if (entry.obj instanceof SparseArray) { 446 SparseArray<?> sa = (SparseArray<?>) entry.obj; 447 for (int i = 0; i < sa.size(); i++) 448 children.add(new Entry("[" + sa.keyAt(i) + "]", sa.valueAt(i))); 449 } else if (entry.obj instanceof Map) { 450 Set<?> set = ((Map<?, ?>) entry.obj).entrySet(); 451 for (Object o : set) { 452 Map.Entry e = (Map.Entry) o; 453 children.add(new Entry(e.getKey().toString(), e.getValue())); 454 } 455 } else if (entry.obj instanceof SparseIntArray) { 456 SparseIntArray sa = (SparseIntArray) entry.obj; 457 for (int i = 0; i < sa.size(); i++) 458 children.add("[" + sa.keyAt(i) + "] = " + sa.valueAt(i)); 459 } else if (entry.obj instanceof Collection) { 460 Collection<?> collection = (Collection<?>) entry.obj; 461 for (Object o : collection) 462 children.add(new Entry("[?]", o)); 463 } else if (entry.obj.getClass().isArray()) { 464 for (int i = 0; i < Array.getLength(entry.obj); i++) 465 children.add(new Entry("[" + i + "]", Array.get(entry.obj, i))); 466 } else { 467 Field[] fields = entry.obj.getClass().getFields(); 468 for (Field f : fields) { 469 try { 470 children.add(new Entry(f.getName(), f.get(entry.obj))); 471 } catch (IllegalArgumentException e) { 472 e.printStackTrace(); 473 } catch (IllegalAccessException e) { 474 e.printStackTrace(); 475 } 476 } 477 } 478 return children.toArray(); 479 } 480 481 @Override 482 public Object getParent(Object element) { 483 return null; 484 } 485 486 @Override 487 public boolean hasChildren(Object element) { 488 if (element == null) 489 return false; 490 if (!(element instanceof Entry)) 491 return false; 492 Object obj = ((Entry) element).obj; 493 if (obj == null) 494 return false; 495 if (obj instanceof SparseArray) 496 return ((SparseArray<?>) obj).size() > 0; 497 else if (obj instanceof SparseIntArray) 498 return ((SparseIntArray) obj).size() > 0; 499 else if (obj instanceof Collection) 500 return ((Collection<?>) obj).size() > 0; 501 else if (obj instanceof Map) 502 return ((Map<?, ?>) obj).size() > 0; 503 else if (obj.getClass().isArray()) 504 return Array.getLength(obj) > 0; 505 else if (obj instanceof Message) 506 return false; 507 else if (isPrimitive(obj)) 508 return false; 509 else if (obj.getClass().equals(String.class)) 510 return false; 511 else if (obj.getClass().equals(Message.class)) 512 return false; 513 else if (obj instanceof GLEnum) 514 return false; 515 return obj.getClass().getFields().length > 0; 516 } 517 518 static boolean isPrimitive(final Object obj) { 519 final Class<? extends Object> c = obj.getClass(); 520 if (c.isPrimitive()) 521 return true; 522 if (c == Integer.class) 523 return true; 524 if (c == Boolean.class) 525 return true; 526 if (c == Float.class) 527 return true; 528 if (c == Short.class) 529 return true; 530 return false; 531 } 532 } 533