Home | History | Annotate | Download | only in gldebugger
      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