/* ** Copyright 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.glesv2debugger; import com.android.glesv2debugger.DebuggerMessage.Message; import com.android.glesv2debugger.DebuggerMessage.Message.DataType; import com.android.glesv2debugger.DebuggerMessage.Message.Function; import com.android.glesv2debugger.DebuggerMessage.Message.Prop; import com.android.sdklib.util.SparseArray; import com.android.sdklib.util.SparseIntArray; import com.google.protobuf.ByteString; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Display; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Collection; import java.util.Map; import java.util.Set; class Frame { public final long filePosition; private int callsCount; final Context startContext; private ArrayList<MessageData> calls = new ArrayList<MessageData>(); Frame(final Context context, final long filePosition) { this.startContext = context.clone(); this.filePosition = filePosition; } void add(final MessageData msgData) { calls.add(msgData); } void increaseCallsCount() { callsCount++; } Context computeContext(final MessageData call) { Context ctx = startContext.clone(); for (int i = 0; i < calls.size(); i++) if (call == calls.get(i)) return ctx; else ctx.processMessage(calls.get(i).msg); assert false; return ctx; } int size() { return callsCount; } MessageData get(final int i) { return calls.get(i); } ArrayList<MessageData> get() { return calls; } void unload() { if (calls == null) return; calls.clear(); calls = null; } void load(final RandomAccessFile file) { if (calls != null && calls.size() == callsCount) return; try { Context ctx = startContext.clone(); calls = new ArrayList<MessageData>(callsCount); final long oriPosition = file.getFilePointer(); file.seek(filePosition); for (int i = 0; i < callsCount; i++) { int len = file.readInt(); if (SampleView.targetByteOrder == ByteOrder.LITTLE_ENDIAN) len = Integer.reverseBytes(len); final byte[] data = new byte[len]; file.read(data); Message msg = Message.parseFrom(data); ctx.processMessage(msg); final MessageData msgData = new MessageData(Display.getCurrent(), msg, ctx); calls.add(msgData); } file.seek(oriPosition); } catch (IOException e) { e.printStackTrace(); assert false; } } } class DebugContext { boolean uiUpdate = false; final int contextId; Context currentContext; private ArrayList<Frame> frames = new ArrayList<Frame>(128); private Frame lastFrame; private Frame loadedFrame; private RandomAccessFile file; DebugContext(final int contextId) { this.contextId = contextId; currentContext = new Context(contextId); try { file = new RandomAccessFile("0x" + Integer.toHexString(contextId) + ".gles2dbg", "rw"); } catch (FileNotFoundException e) { e.printStackTrace(); assert false; } } /** write message to file; if frame not null, then increase its call count */ void saveMessage(final Message msg, final RandomAccessFile file, Frame frame) { synchronized (file) { if (frame != null) frame.increaseCallsCount(); final byte[] data = msg.toByteArray(); final ByteBuffer len = ByteBuffer.allocate(4); len.order(SampleView.targetByteOrder); len.putInt(data.length); try { if (SampleView.targetByteOrder == ByteOrder.BIG_ENDIAN) file.writeInt(data.length); else file.writeInt(Integer.reverseBytes(data.length)); file.write(data); } catch (IOException e) { e.printStackTrace(); assert false; } } } /** * Caches new Message, and formats into MessageData for current frame; this * function is called exactly once for each new Message */ void processMessage(final Message newMsg) { Message msg = newMsg; if (msg.getFunction() == Function.SETPROP) { // GL impl. consts should have been sent before any GL call messages assert frames.size() == 0; assert lastFrame == null; assert msg.getProp() == Prop.GLConstant; switch (GLEnum.valueOf(msg.getArg0())) { case GL_MAX_VERTEX_ATTRIBS: currentContext.serverVertex = new GLServerVertex(msg.getArg1()); break; case GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS: currentContext.serverTexture = new GLServerTexture(currentContext, msg.getArg1()); break; default: assert false; return; } saveMessage(msg, file, null); return; } if (lastFrame == null) { // first real message after the GL impl. consts synchronized (file) { try { lastFrame = new Frame(currentContext, file.getFilePointer()); } catch (IOException e) { e.printStackTrace(); assert false; } } synchronized (frames) { frames.add(lastFrame); } assert loadedFrame == null; loadedFrame = lastFrame; } currentContext.processMessage(msg); if (msg.hasDataType() && msg.getDataType() == DataType.ReferencedImage) { // decode referenced image so it doesn't rely on context later on final byte[] referenced = MessageProcessor.lzfDecompressChunks(msg.getData()); currentContext.readPixelRef = MessageProcessor.decodeReferencedImage( currentContext.readPixelRef, referenced); final byte[] decoded = MessageProcessor.lzfCompressChunks( currentContext.readPixelRef, referenced.length); msg = msg.toBuilder().setDataType(DataType.NonreferencedImage) .setData(ByteString.copyFrom(decoded)).build(); } saveMessage(msg, file, lastFrame); if (loadedFrame == lastFrame) { // frame selected for view, so format MessageData final MessageData msgData = new MessageData(Display.getCurrent(), msg, currentContext); lastFrame.add(msgData); uiUpdate = true; } if (msg.getFunction() != Function.eglSwapBuffers) return; synchronized (frames) { if (loadedFrame != lastFrame) lastFrame.unload(); try { frames.add(lastFrame = new Frame(currentContext, file.getFilePointer())); // file.getChannel().force(false); uiUpdate = true; } catch (IOException e) { e.printStackTrace(); assert false; } } return; } Frame getFrame(int index) { synchronized (frames) { Frame newFrame = frames.get(index); if (loadedFrame != null && loadedFrame != lastFrame && newFrame != loadedFrame) { loadedFrame.unload(); uiUpdate = true; } loadedFrame = newFrame; synchronized (file) { loadedFrame.load(file); } return loadedFrame; } } int frameCount() { synchronized (frames) { return frames.size(); } } } /** aggregate of GL states */ public class Context implements Cloneable { public final int contextId; public ArrayList<Context> shares = new ArrayList<Context>(); // self too public GLServerVertex serverVertex; public GLServerShader serverShader = new GLServerShader(this); public GLServerState serverState = new GLServerState(this); public GLServerTexture serverTexture; byte[] readPixelRef = new byte[0]; public Context(int contextId) { this.contextId = contextId; shares.add(this); } @Override public Context clone() { try { Context copy = (Context) super.clone(); // FIXME: context sharing list clone copy.shares = new ArrayList<Context>(1); copy.shares.add(copy); if (serverVertex != null) copy.serverVertex = serverVertex.clone(); copy.serverShader = serverShader.clone(copy); copy.serverState = serverState.clone(); if (serverTexture != null) copy.serverTexture = serverTexture.clone(copy); // don't need to clone readPixelsRef, since referenced images // are decoded when they are encountered return copy; } catch (CloneNotSupportedException e) { e.printStackTrace(); assert false; return null; } } /** mainly updating states */ public void processMessage(Message msg) { if (serverVertex.process(msg)) return; if (serverShader.processMessage(msg)) return; if (serverState.processMessage(msg)) return; if (serverTexture.processMessage(msg)) return; } } class ContextViewProvider extends LabelProvider implements ITreeContentProvider, ISelectionChangedListener { Context context; final SampleView sampleView; ContextViewProvider(final SampleView sampleView) { this.sampleView = sampleView; } @Override public void dispose() { } @Override public String getText(Object obj) { if (obj == null) return "null"; if (obj instanceof Entry) { Entry entry = (Entry) obj; String objStr = "null (or default)"; if (entry.obj != null) { objStr = entry.obj.toString(); if (entry.obj instanceof Message) objStr = MessageFormatter.format((Message) entry.obj, false); } return entry.name + " = " + objStr; } return obj.toString(); } @Override public Image getImage(Object obj) { if (!(obj instanceof Entry)) return null; final Entry entry = (Entry) obj; if (!(entry.obj instanceof Message)) return null; final Message msg = (Message) entry.obj; switch (msg.getFunction()) { case glTexImage2D: case glTexSubImage2D: case glCopyTexImage2D: case glCopyTexSubImage2D: { entry.image = new MessageData(Display.getCurrent(), msg, null).getImage(); if (entry.image == null) return null; return new Image(Display.getCurrent(), entry.image.getImageData().scaledTo(96, 96)); } default: return null; } } @Override public void selectionChanged(SelectionChangedEvent event) { StructuredSelection selection = (StructuredSelection) event .getSelection(); if (null == selection) return; final Object obj = selection.getFirstElement(); if (!(obj instanceof Entry)) return; final Entry entry = (Entry) obj; if (entry.image == null) return; sampleView.tabFolder.setSelection(sampleView.tabItemImage); sampleView.canvas.setBackgroundImage(entry.image); sampleView.canvas.redraw(); } @Override public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { context = (Context) newInput; } class Entry { String name; Object obj; Image image; Entry(String name, Object obj) { this.name = name; this.obj = obj; } } @Override public Object[] getElements(Object inputElement) { if (inputElement != context) return null; return getChildren(new Entry("Context", inputElement)); } @Override public Object[] getChildren(Object parentElement) { if (!(parentElement instanceof Entry)) return null; Entry entry = (Entry) parentElement; ArrayList<Object> children = new ArrayList<Object>(); if (entry.obj == context.serverState.enableDisables) { for (int i = 0; i < context.serverState.enableDisables.size(); i++) { final int key = context.serverState.enableDisables.keyAt(i); final int value = context.serverState.enableDisables.valueAt(i); children.add(GLEnum.valueOf(key).name() + " = " + value); } } else if (entry.obj == context.serverState.integers) { for (int i = 0; i < context.serverState.integers.size(); i++) { final int key = context.serverState.integers.keyAt(i); final Message val = context.serverState.integers.valueAt(i); if (val != null) children.add(GLEnum.valueOf(key).name() + " : " + MessageFormatter.format(val, false)); else children.add(GLEnum.valueOf(key).name() + " : default"); } } else if (entry.obj == context.serverState.lastSetter) { for (int i = 0; i < context.serverState.lastSetter.size(); i++) { final int key = context.serverState.lastSetter.keyAt(i); final Message msg = context.serverState.lastSetter.valueAt(i); if (msg == null) children.add(Function.valueOf(key).name() + " : default"); else children.add(Function.valueOf(key).name() + " : " + MessageFormatter.format(msg, false)); } } else if (entry.obj instanceof SparseArray) { SparseArray<?> sa = (SparseArray<?>) entry.obj; for (int i = 0; i < sa.size(); i++) children.add(new Entry("[" + sa.keyAt(i) + "]", sa.valueAt(i))); } else if (entry.obj instanceof Map) { Set<?> set = ((Map<?, ?>) entry.obj).entrySet(); for (Object o : set) { Map.Entry e = (Map.Entry) o; children.add(new Entry(e.getKey().toString(), e.getValue())); } } else if (entry.obj instanceof SparseIntArray) { SparseIntArray sa = (SparseIntArray) entry.obj; for (int i = 0; i < sa.size(); i++) children.add("[" + sa.keyAt(i) + "] = " + sa.valueAt(i)); } else if (entry.obj instanceof Collection) { Collection<?> collection = (Collection<?>) entry.obj; for (Object o : collection) children.add(new Entry("[?]", o)); } else if (entry.obj.getClass().isArray()) { for (int i = 0; i < Array.getLength(entry.obj); i++) children.add(new Entry("[" + i + "]", Array.get(entry.obj, i))); } else { Field[] fields = entry.obj.getClass().getFields(); for (Field f : fields) { try { children.add(new Entry(f.getName(), f.get(entry.obj))); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } } return children.toArray(); } @Override public Object getParent(Object element) { return null; } @Override public boolean hasChildren(Object element) { if (element == null) return false; if (!(element instanceof Entry)) return false; Object obj = ((Entry) element).obj; if (obj == null) return false; if (obj instanceof SparseArray) return ((SparseArray<?>) obj).size() > 0; else if (obj instanceof SparseIntArray) return ((SparseIntArray) obj).size() > 0; else if (obj instanceof Collection) return ((Collection<?>) obj).size() > 0; else if (obj instanceof Map) return ((Map<?, ?>) obj).size() > 0; else if (obj.getClass().isArray()) return Array.getLength(obj) > 0; else if (obj instanceof Message) return false; else if (isPrimitive(obj)) return false; else if (obj.getClass().equals(String.class)) return false; else if (obj.getClass().equals(Message.class)) return false; else if (obj instanceof GLEnum) return false; return obj.getClass().getFields().length > 0; } static boolean isPrimitive(final Object obj) { final Class<? extends Object> c = obj.getClass(); if (c.isPrimitive()) return true; if (c == Integer.class) return true; if (c == Boolean.class) return true; if (c == Float.class) return true; if (c == Short.class) return true; return false; } }