GLES2Dbg: organize calls into frames and contexts

Maintain a current state for each context updated by each call.
eglSwapBuffers Begins a new frame; clone current state.
Use clone to compute context for a call within that frame later on,
 in the context tree view.

Change-Id: I66658561f610025b203a991b5b9f545a9a2f9cd4
Signed-off-by: David Li <davidxli@google.com>
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/Context.java b/tools/glesv2debugger/src/com/android/glesv2debugger/Context.java
index ff096d7..c228df8 100644
--- a/tools/glesv2debugger/src/com/android/glesv2debugger/Context.java
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/Context.java
@@ -21,23 +21,78 @@
 import com.android.sdklib.util.SparseArray;
 import com.android.sdklib.util.SparseIntArray;
 
+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.lang.reflect.Array;
 import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Map;
 import java.util.Set;
 
+class Frame {
+    final Context startContext;
+    ArrayList<MessageData> calls = new ArrayList<MessageData>();
+
+    Frame(final Context context) {
+        this.startContext = context.clone();
+    }
+}
+
+class DebugContext {
+    final int contextId;
+    Context currentContext;
+    ArrayList<Frame> frames = new ArrayList<Frame>(128);
+    private Frame currentFrame;
+
+    DebugContext(final int contextId) {
+        this.contextId = contextId;
+        currentContext = new Context(contextId);
+        frames.add(new Frame(currentContext));
+        currentFrame = frames.get(0);
+    }
+
+    MessageData ProcessMessage(final Message oriMsg) {
+        currentContext.ProcessMessage(oriMsg);
+        Message msg = oriMsg;
+        if (currentContext.processed != null)
+            msg = currentContext.processed;
+        currentContext.processed = null;
+        MessageData msgData = new MessageData(Display.getCurrent(), msg, oriMsg, currentContext);
+        currentFrame.calls.add(msgData);
+        if (msg.getFunction() != Function.eglSwapBuffers)
+            return msgData;
+        frames.add(currentFrame = new Frame(currentContext));
+        return msgData;
+    }
+
+    Context ComputeContext(final Frame frame, final MessageData call) {
+        Context ctx = frame.startContext.clone();
+        for (int i = 0; i < frame.calls.size(); i++)
+            if (call == frame.calls.get(i))
+                return ctx;
+            else
+                ctx.ProcessMessage(frame.calls.get(i).oriMsg);
+        assert false;
+        return ctx;
+    }
+}
+
+/** 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 = new GLServerVertex();
     public GLServerShader serverShader = new GLServerShader(this);
     public GLServerState serverState = new GLServerState(this);
+    public GLServerTexture serverTexture = new GLServerTexture(this);
 
     byte[] readPixelRef = new byte[0];
 
@@ -48,42 +103,44 @@
         shares.add(this);
     }
 
-    // returns instance TODO: return new instance if changed
-    public Context ProcessMessage(Message msg) {
-        GLServerVertex newVertex = serverVertex.Process(msg);
-        if (newVertex != null) {
-            processed = newVertex.processed;
-            assert newVertex == serverVertex;
-            return this;
+    @Override
+    public Context clone() {
+        try {
+            Context copy = (Context) super.clone();
+            copy.serverVertex = serverVertex.clone();
+            copy.serverShader = serverShader.clone(copy);
+            copy.serverState = serverState.clone();
+            copy.serverTexture = serverTexture.clone(copy);
+            return copy;
+        } catch (CloneNotSupportedException e) {
+            e.printStackTrace();
+            assert false;
+            return null;
         }
+    }
 
-        GLServerShader newShader = serverShader.ProcessMessage(msg);
-        if (newShader != null) {
-            assert newShader == serverShader;
-            return this;
+    public void ProcessMessage(Message msg) {
+        if (serverVertex.Process(msg)) {
+            processed = serverVertex.processed;
+            return;
         }
-
-        GLServerState newState = serverState.ProcessMessage(msg);
-        if (newState != null) {
-            if (newState == serverState)
-                return this;
-            Context newContext = null;
-            try {
-                newContext = (Context) clone();
-            } catch (CloneNotSupportedException e) {
-                assert false;
-            }
-            newContext.serverState = newState;
-            newContext.serverShader.context = newContext;
-            return newContext;
-        }
-
-        return this;
+        if (serverShader.ProcessMessage(msg))
+            return;
+        if (serverState.ProcessMessage(msg))
+            return;
+        if (serverTexture.ProcessMessage(msg))
+            return;
     }
 }
 
-class ContextViewProvider extends LabelProvider implements ITreeContentProvider {
+class ContextViewProvider extends LabelProvider implements ITreeContentProvider,
+        ISelectionChangedListener {
     Context context;
+    final SampleView sampleView;
+
+    ContextViewProvider(final SampleView sampleView) {
+        this.sampleView = sampleView;
+    }
 
     @Override
     public void dispose() {
@@ -95,18 +152,54 @@
             return "null";
         if (obj instanceof Entry) {
             Entry entry = (Entry) obj;
-            if (entry != null)
-                return entry.name + " = " + 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);
+            }
+            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;
+        for (int i = 0; i <= sampleView.frameNum.getSelection(); i++) {
+            if (i == sampleView.current.frames.size())
+                return null;
+            final Frame frame = sampleView.current.frames.get(i);
+            for (final MessageData msgData : frame.calls)
+                if (msgData.oriMsg == msg)
+                    return entry.image = msgData.image;
+        }
         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;
     }
@@ -114,6 +207,7 @@
     class Entry {
         String name;
         Object obj;
+        Image image;
 
         Entry(String name, Object obj) {
             this.name = name;
@@ -140,6 +234,16 @@
                 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));
+                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);
@@ -151,11 +255,11 @@
                             + MessageFormatter.Format(msg));
             }
         } else if (entry.obj instanceof SparseArray) {
-            SparseArray sa = (SparseArray) entry.obj;
+            SparseArray<?> sa = (SparseArray<?>) entry.obj;
             for (int i = 0; i < sa.size(); i++)
-                children.add(new Entry(entry.name + "[" + sa.keyAt(i) + "]", sa.valueAt(i)));
+                children.add(new Entry("[" + sa.keyAt(i) + "]", sa.valueAt(i)));
         } else if (entry.obj instanceof Map) {
-            Set set = ((Map) entry.obj).entrySet();
+            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()));
@@ -163,15 +267,14 @@
         } else if (entry.obj instanceof SparseIntArray) {
             SparseIntArray sa = (SparseIntArray) entry.obj;
             for (int i = 0; i < sa.size(); i++)
-                children.add(entry.name + "[" + sa.keyAt(i) + "] = " + sa.valueAt(i));
+                children.add("[" + sa.keyAt(i) + "] = " + sa.valueAt(i));
         } else if (entry.obj instanceof Collection) {
-            Collection collection = (Collection) entry.obj;
+            Collection<?> collection = (Collection<?>) entry.obj;
             for (Object o : collection)
-                children.add(new Entry(entry.name, o));
+                children.add(new Entry("[?]", o));
         } else if (entry.obj.getClass().isArray()) {
-            Object[] list = (Object[]) entry.obj;
-            for (Object o : list)
-                children.add(new Entry(entry.name, o));
+            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) {
@@ -196,26 +299,46 @@
     public boolean hasChildren(Object element) {
         if (element == null)
             return false;
-        if (element.getClass().isPrimitive())
+        if (!(element instanceof Entry))
             return false;
-        if (element.getClass().equals(String.class))
+        Object obj = ((Entry) element).obj;
+        if (obj == null)
             return false;
-        if (element instanceof Entry) {
-            Entry entry = (Entry) element;
-            if (entry.obj != null) {
-                if (entry.obj instanceof SparseArray)
-                    return ((SparseArray) entry.obj).size() > 0;
-                else if (entry.obj instanceof SparseIntArray)
-                    return ((SparseIntArray) entry.obj).size() > 0;
-                else if (entry.obj instanceof Collection)
-                    return ((Collection) entry.obj).size() > 0;
-                else if (entry.obj instanceof Map)
-                    return ((Map) entry.obj).size() > 0;
-                else if (entry.obj.getClass().isArray())
-                    return ((Object[]) entry.obj).length > 0;
-                return entry.obj.getClass().getFields().length > 0;
-            }
-        }
+        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;
     }
 }
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerShader.java b/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerShader.java
index 4915921..cf70993 100644
--- a/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerShader.java
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerShader.java
@@ -23,7 +23,7 @@
 
 class GLShader implements Cloneable {
     public final int name;
-    final GLServerShader context; // the context this was created in
+    GLServerShader context; // the context this was created in
     public final GLEnum type;
     public boolean delete;
     public ArrayList<Integer> programs = new ArrayList<Integer>();
@@ -35,12 +35,12 @@
         this.type = type;
     }
 
-    @Override
-    // deep copy except for context, which is set afterwards
-    public Object clone() {
+    /** deep copy */
+    public GLShader clone(final GLServerShader copyContext) {
         try {
             GLShader shader = (GLShader) super.clone();
             shader.programs = (ArrayList<Integer>) programs.clone();
+            shader.context = copyContext;
             return shader;
         } catch (CloneNotSupportedException e) {
             e.printStackTrace();
@@ -52,7 +52,7 @@
 
 class GLProgram implements Cloneable {
     public final int name;
-    final GLServerShader context; // the context this was created in
+    GLServerShader context; // the context this was created in
     public boolean delete;
     public int vert, frag;
 
@@ -61,11 +61,12 @@
         this.context = context;
     }
 
-    @Override
-    // deep copy except for context, which is set afterwards
-    public Object clone() {
+    /** deep copy */
+    public GLProgram clone(final GLServerShader copyContext) {
         try {
-            return super.clone();
+            GLProgram copy = (GLProgram) super.clone();
+            copy.context = copyContext;
+            return copy;
         } catch (CloneNotSupportedException e) {
             e.printStackTrace();
             assert false;
@@ -85,22 +86,22 @@
         this.context = context;
     }
 
-    @Override
-    // deep copy except for context, which is set afterwards
-    public Object clone() {
+    /** deep copy */
+    public GLServerShader clone(final Context copyContext) {
         try {
             GLServerShader copy = (GLServerShader) super.clone();
+            copy.context = copyContext;
 
             copy.shaders = new SparseArray<GLShader>(shaders.size());
             for (int i = 0; i < shaders.size(); i++)
-                copy.shaders.append(shaders.keyAt(i), (GLShader) shaders.valueAt(i).clone());
+                copy.shaders.append(shaders.keyAt(i), shaders.valueAt(i).clone(copy));
 
             copy.programs = new SparseArray<GLProgram>(programs.size());
             for (int i = 0; i < programs.size(); i++)
-                copy.programs.append(programs.keyAt(i), (GLProgram) programs.valueAt(i).clone());
+                copy.programs.append(programs.keyAt(i), programs.valueAt(i).clone(copy));
 
             if (current != null)
-                copy.current = (GLProgram) current.clone();
+                copy.current = copy.programs.get(current.name);
             return copy;
         } catch (CloneNotSupportedException e) {
             e.printStackTrace();
@@ -109,38 +110,38 @@
         }
     }
 
-    // returns instance if processed
-    public GLServerShader ProcessMessage(final Message msg) {
+    /** returns true if processed */
+    public boolean ProcessMessage(final Message msg) {
         boolean oldUiUpdate = uiUpdate;
         uiUpdate = true;
         switch (msg.getFunction()) {
             case glAttachShader:
                 glAttachShader(msg);
-                return this;
+                return true;
             case glCreateProgram:
                 glCreateProgram(msg);
-                return this;
+                return true;
             case glCreateShader:
                 glCreateShader(msg);
-                return this;
+                return true;
             case glDeleteProgram:
                 glDeleteProgram(msg);
-                return this;
+                return true;
             case glDeleteShader:
                 glDeleteShader(msg);
-                return this;
+                return true;
             case glDetachShader:
                 glDetachShader(msg);
-                return this;
+                return true;
             case glShaderSource:
                 glShaderSource(msg);
-                return this;
+                return true;
             case glUseProgram:
                 glUseProgram(msg);
-                return this;
+                return true;
             default:
                 uiUpdate = oldUiUpdate;
-                return null;
+                return false;
         }
     }
 
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerState.java b/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerState.java
index b79ef37..adab930 100644
--- a/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerState.java
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerState.java
@@ -41,7 +41,12 @@
     final Context context;
     public GLStencilState front = new GLStencilState(), back = new GLStencilState();
     public SparseIntArray enableDisables;
-    public SparseArray<Message> lastSetter; // keyed by Function.getNumber()
+
+    /** integer states set via a GL function and GLEnum; keyed by GLEnum.value */
+    public SparseArray<Message> integers;
+
+    /** states set only via a GL function; keyed by Function.getNumber() */
+    public SparseArray<Message> lastSetter;
 
     GLServerState(final Context context) {
         this.context = context;
@@ -62,36 +67,61 @@
         lastSetter.put(Function.glBlendEquationSeparate.getNumber(), null);
         // glBlendFunc overwrites glBlendFuncSeparate
         lastSetter.put(Function.glBlendFuncSeparate.getNumber(), null);
+        lastSetter.put(Function.glClearColor.getNumber(), null);
+        lastSetter.put(Function.glClearDepthf.getNumber(), null);
+        lastSetter.put(Function.glClearStencil.getNumber(), null);
         lastSetter.put(Function.glColorMask.getNumber(), null);
+        lastSetter.put(Function.glCullFace.getNumber(), null);
         lastSetter.put(Function.glDepthMask.getNumber(), null);
         lastSetter.put(Function.glDepthFunc.getNumber(), null);
+        lastSetter.put(Function.glDepthRangef.getNumber(), null);
+        lastSetter.put(Function.glFrontFace.getNumber(), null);
+        lastSetter.put(Function.glLineWidth.getNumber(), null);
+        lastSetter.put(Function.glPolygonOffset.getNumber(), null);
+        lastSetter.put(Function.glSampleCoverage.getNumber(), null);
         lastSetter.put(Function.glScissor.getNumber(), null);
         lastSetter.put(Function.glStencilMaskSeparate.getNumber(), null);
+        lastSetter.put(Function.glViewport.getNumber(), null);
+
+        integers = new SparseArray<Message>();
+        integers.put(GLEnum.GL_PACK_ALIGNMENT.value, null);
+        integers.put(GLEnum.GL_UNPACK_ALIGNMENT.value, null);
     }
 
-    // returns instance if processed (returns new instance if changed)
-    public GLServerState ProcessMessage(final Message msg) {
+    /** returns true if processed */
+    public boolean ProcessMessage(final Message msg) {
         switch (msg.getFunction()) {
             case glBlendColor:
-                return Setter(msg);
             case glBlendEquation:
-                return Setter(msg);
             case glBlendEquationSeparate:
-                return Setter(msg);
             case glBlendFunc:
-                return Setter(msg);
             case glBlendFuncSeparate:
-                return Setter(msg);
+            case glClearColor:
+            case glClearDepthf:
+            case glClearStencil:
             case glColorMask:
-                return Setter(msg);
+            case glCullFace:
             case glDepthMask:
-                return Setter(msg);
             case glDepthFunc:
+            case glDepthRangef:
                 return Setter(msg);
             case glDisable:
                 return EnableDisable(false, msg);
             case glEnable:
                 return EnableDisable(true, msg);
+            case glFrontFace:
+            case glLineWidth:
+                return Setter(msg);
+            case glPixelStorei:
+                if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_PACK_ALIGNMENT)
+                    integers.put(msg.getArg0(), msg);
+                else if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_UNPACK_ALIGNMENT)
+                    integers.put(msg.getArg0(), msg);
+                else
+                    assert false;
+                return true;
+            case glPolygonOffset:
+            case glSampleCoverage:
             case glScissor:
                 return Setter(msg);
             case glStencilFunc: {
@@ -104,7 +134,6 @@
             case glStencilFuncSeparate:
                 return glStencilFuncSeparate(msg);
             case glStencilMask:
-                return Setter(msg);
             case glStencilMaskSeparate:
                 return Setter(msg);
             case glStencilOp: {
@@ -117,43 +146,42 @@
             }
             case glStencilOpSeparate:
                 return glStencilOpSeparate(msg);
+            case glViewport:
+                return Setter(msg);
             default:
-                return null;
+                return false;
         }
     }
 
-    GLServerState Setter(final Message msg) {
-        GLServerState newState = (GLServerState) this.clone();
-        // TODO: compare for change
+    boolean Setter(final Message msg) {
         switch (msg.getFunction()) {
             case glBlendFunc:
-                newState.lastSetter.put(Function.glBlendFuncSeparate.getNumber(), msg);
+                lastSetter.put(Function.glBlendFuncSeparate.getNumber(), msg);
                 break;
             case glBlendEquation:
-                newState.lastSetter.put(Function.glBlendEquationSeparate.getNumber(), msg);
+                lastSetter.put(Function.glBlendEquationSeparate.getNumber(), msg);
                 break;
             case glStencilMask:
-                newState.lastSetter.put(Function.glStencilMaskSeparate.getNumber(), msg);
+                lastSetter.put(Function.glStencilMaskSeparate.getNumber(), msg);
                 break;
             default:
-                newState.lastSetter.put(msg.getFunction().getNumber(), msg);
+                lastSetter.put(msg.getFunction().getNumber(), msg);
                 break;
         }
-        return newState;
+        return true;
     }
 
-    GLServerState EnableDisable(boolean enable, final Message msg) {
+    boolean EnableDisable(boolean enable, final Message msg) {
         int index = enableDisables.indexOfKey(msg.getArg0());
         assert index >= 0;
         if ((enableDisables.valueAt(index) != 0) == enable)
-            return this;
-        GLServerState newState0 = (GLServerState) this.clone();
-        newState0.enableDisables.put(msg.getArg0(), enable ? 1 : 0);
-        return newState0;
+            return true; // TODO: redundant
+        enableDisables.put(msg.getArg0(), enable ? 1 : 0);
+        return true;
     }
 
     // void StencilFuncSeparate( enum face, enum func, int ref, uint mask )
-    GLServerState glStencilFuncSeparate(final Message msg) {
+    boolean glStencilFuncSeparate(final Message msg) {
         GLEnum ff = front.func, bf = back.func;
         int fr = front.ref, br = back.ref;
         int fm = front.mask, bm = back.mask;
@@ -170,19 +198,18 @@
         }
         if (ff == front.func && fr == front.ref && fm == front.mask)
             if (bf == back.func && br == back.ref && bm == back.mask)
-                return this;
-        GLServerState newState = (GLServerState) this.clone();
-        newState.front.func = ff;
-        newState.front.ref = fr;
-        newState.front.mask = fm;
-        newState.back.func = bf;
-        newState.back.ref = br;
-        newState.back.mask = bm;
-        return newState;
+                return true; // TODO: redundant
+        front.func = ff;
+        front.ref = fr;
+        front.mask = fm;
+        back.func = bf;
+        back.ref = br;
+        back.mask = bm;
+        return true;
     }
 
     // void StencilOpSeparate( enum face, enum sfail, enum dpfail, enum dppass )
-    GLServerState glStencilOpSeparate(final Message msg) {
+    boolean glStencilOpSeparate(final Message msg) {
         GLEnum fsf = front.sf, fdf = front.df, fdp = front.dp;
         GLEnum bsf = back.sf, bdf = back.df, bdp = back.dp;
         final GLEnum face = GLEnum.valueOf(msg.getArg0());
@@ -198,39 +225,41 @@
         }
         if (fsf == front.sf && fdf == front.df && fdp == front.dp)
             if (bsf == back.sf && bdf == back.df && bdp == back.dp)
-                return this;
-        GLServerState newState = (GLServerState) this.clone();
-        newState.front.sf = fsf;
-        newState.front.df = fdf;
-        newState.front.dp = fdp;
-        newState.back.sf = bsf;
-        newState.back.df = bdf;
-        newState.back.dp = bdp;
-        return newState;
+                return true; // TODO: redundant
+        front.sf = fsf;
+        front.df = fdf;
+        front.dp = fdp;
+        back.sf = bsf;
+        back.df = bdf;
+        back.dp = bdp;
+        return true;
     }
 
+    /** deep copy */
     @Override
-    public Object clone() {
+    public GLServerState clone() {
         try {
             GLServerState newState = (GLServerState) super.clone();
             newState.front = (GLStencilState) front.clone();
             newState.back = (GLStencilState) back.clone();
 
             newState.enableDisables = new SparseIntArray(enableDisables.size());
-            for (int i = 0; i < enableDisables.size(); i++) {
-                final int key = enableDisables.keyAt(i);
-                newState.enableDisables.append(key, enableDisables.valueAt(i));
-            }
+            for (int i = 0; i < enableDisables.size(); i++)
+                newState.enableDisables.append(enableDisables.keyAt(i),
+                        enableDisables.valueAt(i));
+
+            newState.integers = new SparseArray<Message>(integers.size());
+            for (int i = 0; i < integers.size(); i++)
+                newState.integers.append(integers.keyAt(i), integers.valueAt(i));
 
             newState.lastSetter = new SparseArray<Message>(lastSetter.size());
-            for (int i = 0; i < lastSetter.size(); i++) {
-                final int key = lastSetter.keyAt(i);
-                newState.lastSetter.append(key, lastSetter.valueAt(i));
-            }
+            for (int i = 0; i < lastSetter.size(); i++)
+                newState.lastSetter.append(lastSetter.keyAt(i), lastSetter.valueAt(i));
 
             return newState;
         } catch (CloneNotSupportedException e) {
             e.printStackTrace();
+            assert false;
             return null;
         }
     }
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerTexture.java b/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerTexture.java
new file mode 100644
index 0000000..d246e3f
--- /dev/null
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerTexture.java
@@ -0,0 +1,232 @@
+/*
+ ** 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.sdklib.util.SparseArray;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+class GLTexture implements Cloneable {
+    public final int name;
+    public final GLEnum target;
+    public ArrayList<Message> contentChanges = new ArrayList<Message>();
+    public GLEnum wrapS = GLEnum.GL_REPEAT, wrapT = GLEnum.GL_REPEAT;
+    public GLEnum min = GLEnum.GL_NEAREST_MIPMAP_LINEAR;
+    public GLEnum mag = GLEnum.GL_LINEAR;
+    public GLEnum format;
+    public int width, height;
+
+    GLTexture(final int name, final GLEnum target) {
+        this.name = name;
+        this.target = target;
+    }
+
+    @Override
+    public GLTexture clone() {
+        try {
+            GLTexture copy = (GLTexture) super.clone();
+            copy.contentChanges = (ArrayList<Message>) contentChanges.clone();
+            return copy;
+        } catch (CloneNotSupportedException e) {
+            e.printStackTrace();
+            assert false;
+            return null;
+        }
+    }
+
+    boolean ProcessMessage(final Message msg) {
+        switch (msg.getFunction()) {
+            case glCompressedTexImage2D:
+            case glCopyTexImage2D:
+            case glTexImage2D:
+                if (msg.getArg1() == 0) { // level 0
+                    format = GLEnum.valueOf(msg.getArg2());
+                    width = msg.getArg3();
+                    height = msg.getArg4();
+                }
+                //$FALL-THROUGH$
+            case glCompressedTexSubImage2D:
+            case glCopyTexSubImage2D:
+            case glTexSubImage2D:
+            case glGenerateMipmap:
+                contentChanges.add(msg);
+                break;
+            default:
+                assert false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return target.name() + " " + contentChanges.size() + " content change(s)";
+    }
+}
+
+public class GLServerTexture implements Cloneable {
+    Context context;
+
+    public GLEnum activeTexture = GLEnum.GL_TEXTURE0;
+    public int[] tmu2D = new int[32];
+    public int[] tmuCube = new int[32];
+    public SparseArray<GLTexture> textures = new SparseArray<GLTexture>();
+    public GLTexture tex2D = null, texCube = null;
+
+    GLServerTexture(final Context context) {
+        this.context = context;
+        textures.append(0, null);
+    }
+
+    public GLServerTexture clone(final Context copyContext) {
+        try {
+            GLServerTexture copy = (GLServerTexture) super.clone();
+            copy.context = copyContext;
+
+            copy.tmu2D = tmu2D.clone();
+            copy.tmuCube = tmuCube.clone();
+
+            copy.textures = new SparseArray<GLTexture>(textures.size());
+            for (int i = 0; i < textures.size(); i++)
+                if (textures.valueAt(i) != null)
+                    copy.textures.append(textures.keyAt(i), textures.valueAt(i).clone());
+                else
+                    copy.textures.append(textures.keyAt(i), null);
+
+            if (tex2D != null)
+                copy.tex2D = copy.textures.get(tex2D.name);
+            if (texCube != null)
+                copy.texCube = copy.textures.get(texCube.name);
+
+            return copy;
+        } catch (CloneNotSupportedException e) {
+            e.printStackTrace();
+            assert false;
+            return null;
+        }
+    }
+
+    public boolean ProcessMessage(final Message msg) {
+        switch (msg.getFunction()) {
+            case glActiveTexture:
+                activeTexture = GLEnum.valueOf(msg.getArg0());
+                return true;
+            case glBindTexture:
+                return BindTexture(msg.getArg0(), msg.getArg1());
+            case glCompressedTexImage2D:
+            case glCompressedTexSubImage2D:
+            case glCopyTexImage2D:
+            case glCopyTexSubImage2D:
+            case glTexImage2D:
+            case glTexSubImage2D:
+                switch (GLEnum.valueOf(msg.getArg0())) {
+                    case GL_TEXTURE_2D:
+                        if (tex2D != null)
+                            return tex2D.ProcessMessage(msg);
+                        return true;
+                    case GL_TEXTURE_CUBE_MAP_POSITIVE_X_OES:
+                    case GL_TEXTURE_CUBE_MAP_NEGATIVE_X_OES:
+                    case GL_TEXTURE_CUBE_MAP_POSITIVE_Y_OES:
+                    case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_OES:
+                    case GL_TEXTURE_CUBE_MAP_POSITIVE_Z_OES:
+                    case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_OES:
+                        if (texCube != null)
+                            return texCube.ProcessMessage(msg);
+                        return true;
+                    default:
+                        return true;
+                }
+            case glDeleteTextures: {
+                final ByteBuffer names = msg.getData().asReadOnlyByteBuffer();
+                names.order(SampleView.targetByteOrder);
+                for (int i = 0; i < msg.getArg0(); i++) {
+                    final int name = names.getInt();
+                    if (tex2D != null && tex2D.name == name)
+                        BindTexture(GLEnum.GL_TEXTURE_2D.value, 0);
+                    if (texCube != null && texCube.name == name)
+                        BindTexture(GLEnum.GL_TEXTURE_CUBE_MAP_OES.value, 0);
+                    if (name != 0)
+                        textures.remove(name);
+                }
+                return true;
+            }
+            case glGenerateMipmap:
+                if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_TEXTURE_2D && tex2D != null)
+                    return tex2D.ProcessMessage(msg);
+                else if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_TEXTURE_CUBE_MAP_OES
+                        && texCube != null)
+                    return texCube.ProcessMessage(msg);
+                return true;
+            case glTexParameteri:
+                return TexParameter(msg.getArg0(), msg.getArg1(), msg.getArg2());
+            case glTexParameterf:
+                return TexParameter(msg.getArg0(), msg.getArg1(),
+                        (int) Float.intBitsToFloat(msg.getArg2()));
+            default:
+                return false;
+        }
+    }
+
+    boolean BindTexture(final int target, final int name) {
+        final int index = activeTexture.value - GLEnum.GL_TEXTURE0.value;
+        if (GLEnum.valueOf(target) == GLEnum.GL_TEXTURE_2D) {
+            tex2D = textures.get(name);
+            if (name != 0 && tex2D == null)
+                textures.put(name, tex2D = new GLTexture(name,
+                        GLEnum.GL_TEXTURE_2D));
+            if (index >= 0 && index < tmu2D.length)
+                tmu2D[index] = name;
+        } else if (GLEnum.valueOf(target) == GLEnum.GL_TEXTURE_CUBE_MAP_OES) {
+            texCube = textures.get(name);
+            if (name != 0 && texCube == null)
+                textures.put(name, texCube = new GLTexture(name,
+                        GLEnum.GL_TEXTURE_CUBE_MAP_OES));
+            if (index >= 0 && index < tmu2D.length)
+                tmu2D[index] = name;
+        } else
+            assert false;
+        return true;
+    }
+
+    boolean TexParameter(final int target, final int pname, final int param) {
+        GLTexture tex = null;
+        if (GLEnum.valueOf(target) == GLEnum.GL_TEXTURE_2D)
+            tex = tex2D;
+        else if (GLEnum.valueOf(target) == GLEnum.GL_TEXTURE_CUBE_MAP_OES)
+            tex = texCube;
+        if (tex == null)
+            return true;
+        final GLEnum p = GLEnum.valueOf(param);
+        switch (GLEnum.valueOf(pname)) {
+            case GL_TEXTURE_WRAP_S:
+                tex.wrapS = p;
+                return true;
+            case GL_TEXTURE_WRAP_T:
+                tex.wrapT = p;
+                return true;
+            case GL_TEXTURE_MIN_FILTER:
+                tex.min = p;
+                return true;
+            case GL_TEXTURE_MAG_FILTER:
+                tex.mag = p;
+                return true;
+            default:
+                return true;
+        }
+    }
+}
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerVertex.java b/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerVertex.java
index 4a23bbb..a9b5ab8 100644
--- a/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerVertex.java
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerVertex.java
@@ -17,17 +17,40 @@
 package com.android.glesv2debugger;
 
 import com.android.glesv2debugger.DebuggerMessage.Message;
+import com.android.sdklib.util.SparseArray;
 
 import java.nio.ByteBuffer;
-import java.util.HashMap;
 
-class GLBuffer {
+class GLBuffer implements Cloneable {
+    public final int name;
     public GLEnum usage;
     public GLEnum target;
     public ByteBuffer data;
+
+    public GLBuffer(final int name) {
+        this.name = name;
+    }
+
+    /** deep copy */
+    @Override
+    public GLBuffer clone() {
+        try {
+            GLBuffer copy = (GLBuffer) super.clone();
+            if (data != null) {
+                copy.data = ByteBuffer.allocate(data.capacity());
+                data.position(0);
+                copy.data.put(data);
+            }
+            return copy;
+        } catch (CloneNotSupportedException e) {
+            e.printStackTrace();
+            assert false;
+            return null;
+        }
+    }
 }
 
-class GLAttribPointer {
+class GLAttribPointer implements Cloneable {
     public int size; // number of values per vertex
     public GLEnum type; // data type
     public int stride; // bytes
@@ -35,19 +58,32 @@
     public GLBuffer buffer;
     public boolean normalized;
     public boolean enabled;
+
+    /** deep copy, re-maps buffer into copyBuffers */
+    public GLAttribPointer clone(SparseArray<GLBuffer> copyBuffers) {
+        try {
+            GLAttribPointer copy = (GLAttribPointer) super.clone();
+            if (buffer != null)
+                copy.buffer = copyBuffers.get(buffer.name);
+            return copy;
+        } catch (CloneNotSupportedException e) {
+            e.printStackTrace();
+            assert false;
+            return null;
+        }
+    }
 }
 
-public class GLServerVertex {
-
-    public  HashMap<Integer, GLBuffer> buffers;
+public class GLServerVertex implements Cloneable {
+    public SparseArray<GLBuffer> buffers = new SparseArray<GLBuffer>();
     public GLBuffer attribBuffer, indexBuffer; // current binding
     public GLAttribPointer attribPointers[];
     public float defaultAttribs[][];
     int maxAttrib;
 
     public GLServerVertex() {
-        buffers = new HashMap<Integer, GLBuffer>();
-        buffers.put(0, null);
+        buffers.append(0, null);
+        // TODO: get MAX_VERTEX_ATTRIBS from server
         attribPointers = new GLAttribPointer[16];
         for (int i = 0; i < attribPointers.length; i++)
             attribPointers[i] = new GLAttribPointer();
@@ -60,70 +96,102 @@
         }
     }
 
+    /** deep copy */
+    @Override
+    public GLServerVertex clone() {
+        try {
+            GLServerVertex copy = (GLServerVertex) super.clone();
+
+            copy.buffers = new SparseArray<GLBuffer>(buffers.size());
+            for (int i = 0; i < buffers.size(); i++)
+                if (buffers.valueAt(i) != null)
+                    copy.buffers.append(buffers.keyAt(i), buffers.valueAt(i).clone());
+                else
+                    copy.buffers.append(buffers.keyAt(i), null);
+
+            if (attribBuffer != null)
+                copy.attribBuffer = copy.buffers.get(attribBuffer.name);
+            if (indexBuffer != null)
+                copy.indexBuffer = copy.buffers.get(indexBuffer.name);
+
+            copy.attribPointers = new GLAttribPointer[attribPointers.length];
+            for (int i = 0; i < attribPointers.length; i++)
+                copy.attribPointers[i] = attribPointers[i].clone(copy.buffers);
+
+            copy.defaultAttribs = defaultAttribs.clone();
+
+            return copy;
+        } catch (CloneNotSupportedException e) {
+            e.printStackTrace();
+            assert false;
+            return null;
+        }
+    }
+
     Message processed = null; // return; glDrawArrays/Elements with fetched data
 
-    // returns instance if processed TODO: return new instance if changed
-    public GLServerVertex Process(final Message msg) {
+    /** returns true if processed */
+    public boolean Process(final Message msg) {
         processed = null;
         switch (msg.getFunction()) {
             case glBindBuffer:
                 glBindBuffer(msg);
-                return this;
+                return true;
             case glBufferData:
                 glBufferData(msg);
-                return this;
+                return true;
             case glBufferSubData:
                 glBufferSubData(msg);
-                return this;
+                return true;
             case glDeleteBuffers:
                 glDeleteBuffers(msg);
-                return this;
+                return true;
             case glDrawArrays:
                 if (msg.hasArg7())
                     processed = glDrawArrays(msg);
-                return this;
+                return true;
             case glDrawElements:
                 if (msg.hasArg7())
                     processed = glDrawElements(msg);
-                return this;
+                return true;
             case glDisableVertexAttribArray:
                 glDisableVertexAttribArray(msg);
-                return this;
+                return true;
             case glEnableVertexAttribArray:
                 glEnableVertexAttribArray(msg);
-                return this;
+                return true;
             case glGenBuffers:
                 glGenBuffers(msg);
-                return this;
+                return true;
             case glVertexAttribPointer:
                 glVertexAttribPointer(msg);
-                return this;
+                return true;
             case glVertexAttrib1f:
                 glVertexAttrib1f(msg);
-                return this;
+                return true;
             case glVertexAttrib1fv:
                 glVertexAttrib1fv(msg);
-                return this;
+                return true;
             case glVertexAttrib2f:
                 glVertexAttrib2f(msg);
-                return this;
+                return true;
             case glVertexAttrib2fv:
                 glVertexAttrib2fv(msg);
-                return this;
+                return true;
             case glVertexAttrib3f:
                 glVertexAttrib3f(msg);
-                return this;
+                return true;
             case glVertexAttrib3fv:
                 glVertexAttrib3fv(msg);
-                return this;
+                return true;
             case glVertexAttrib4f:
                 glVertexAttrib4f(msg);
-                return this;
+                return true;
             case glVertexAttrib4fv:
                 glVertexAttrib4fv(msg);
-                return this;
+                return true;
             default:
-                return null;
+                return false;
         }
     }
 
@@ -181,9 +249,10 @@
     public void glDeleteBuffers(Message msg) {
         final int n = msg.getArg0();
         final ByteBuffer names = msg.getData().asReadOnlyByteBuffer();
+        names.order(SampleView.targetByteOrder);
         for (int i = 0; i < n; i++) {
-            int name = Integer.reverseBytes(names.getInt());
-            GLBuffer buffer = buffers.get(name);
+            final int name = names.getInt();
+            final GLBuffer buffer = buffers.get(name);
             for (int j = 0; j < attribPointers.length; j++)
                 if (attribPointers[j].buffer == buffer) {
                     attribPointers[j].buffer = null;
@@ -204,27 +273,27 @@
 
     float FetchConvert(final ByteBuffer src, final GLEnum type, final boolean normalized) {
         if (GLEnum.GL_FLOAT == type)
-            return Float.intBitsToFloat(Integer.reverseBytes(src.getInt()));
+            return Float.intBitsToFloat(src.getInt());
         else if (GLEnum.GL_UNSIGNED_INT == type)
             if (normalized)
-                return (Integer.reverseBytes(src.getInt()) & 0xffffffffL) / (2e32f - 1);
+                return (src.getInt() & 0xffffffffL) / (2e32f - 1);
             else
-                return Integer.reverseBytes(src.getInt()) & 0xffffffffL;
+                return src.getInt() & 0xffffffffL;
         else if (GLEnum.GL_INT == type)
             if (normalized)
-                return (Integer.reverseBytes(src.getInt()) * 2 + 1) / (2e32f - 1);
+                return (src.getInt() * 2 + 1) / (2e32f - 1);
             else
-                return Integer.reverseBytes(src.getInt());
+                return src.getInt();
         else if (GLEnum.GL_UNSIGNED_SHORT == type)
             if (normalized)
-                return (Short.reverseBytes(src.getShort()) & 0xffff) / (2e16f - 1);
+                return (src.getShort() & 0xffff) / (2e16f - 1);
             else
-                return Short.reverseBytes(src.getShort()) & 0xffff;
+                return src.getShort() & 0xffff;
         else if (GLEnum.GL_SHORT == type)
             if (normalized)
-                return (Short.reverseBytes(src.getShort()) * 2 + 1) / (2e16f - 1);
+                return (src.getShort() * 2 + 1) / (2e16f - 1);
             else
-                return Short.reverseBytes(src.getShort());
+                return src.getShort();
         else if (GLEnum.GL_UNSIGNED_BYTE == type)
             if (normalized)
                 return (src.get() & 0xff) / (2e8f - 1);
@@ -237,9 +306,9 @@
                 return src.get();
         else if (GLEnum.GL_FIXED == type)
             if (normalized)
-                return (Integer.reverseBytes(src.getInt()) * 2 + 1) / (2e32f - 1);
+                return (src.getInt() * 2 + 1) / (2e32f - 1);
             else
-                return Integer.reverseBytes(src.getInt()) / (2e16f);
+                return src.getInt() / (2e16f);
         else
             assert false;
         return 0;
@@ -276,7 +345,10 @@
         final ByteBuffer buffer = ByteBuffer.allocate(4 * 4 * maxAttrib * count);
         ByteBuffer arrays = null;
         if (msg.hasData()) // server sends user pointer attribs
+        {
             arrays = msg.getData().asReadOnlyByteBuffer();
+            arrays.order(SampleView.targetByteOrder);
+        }
         for (int i = first; i < first + count; i++)
             Fetch(i, arrays, buffer);
         assert null == arrays || arrays.remaining() == 0;
@@ -294,16 +366,20 @@
         final ByteBuffer buffer = ByteBuffer.allocate(4 * 4 * maxAttrib * count);
         ByteBuffer arrays = null, index = null;
         if (msg.hasData()) // server sends user pointer attribs
+        {
             arrays = msg.getData().asReadOnlyByteBuffer();
+            arrays.order(SampleView.targetByteOrder);
+        }
         if (null == indexBuffer)
             index = arrays; // server also interleaves user pointer indices
         else {
             index = indexBuffer.data;
+            index.order(SampleView.targetByteOrder);
             index.position(msg.getArg3());
         }
         if (GLEnum.GL_UNSIGNED_SHORT == type)
             for (int i = 0; i < count; i++)
-                Fetch(Short.reverseBytes(index.getShort()) & 0xffff, arrays, buffer);
+                Fetch(index.getShort() & 0xffff, arrays, buffer);
         else if (GLEnum.GL_UNSIGNED_BYTE == type)
             for (int i = 0; i < count; i++)
                 Fetch(index.get() & 0xff, arrays, buffer);
@@ -324,10 +400,12 @@
     public void glGenBuffers(Message msg) {
         final int n = msg.getArg0();
         final ByteBuffer buffer = msg.getData().asReadOnlyByteBuffer();
+        buffer.order(SampleView.targetByteOrder);
         for (int i = 0; i < n; i++) {
-            int name = Integer.reverseBytes(buffer.getInt());
-            if (!buffers.containsKey(name))
-                buffers.put(name, new GLBuffer());
+            final int name = buffer.getInt();
+            final int index = buffers.indexOfKey(name);
+            if (index < 0)
+                buffers.append(name, new GLBuffer(name));
         }
     }
 
@@ -347,53 +425,56 @@
 
     // void glVertexAttrib1f(GLuint indx, GLfloat x)
     public void glVertexAttrib1f(Message msg) {
-        glVertexAttrib4f(msg.getArg0(), Float.intBitsToFloat(Integer.reverseBytes(msg.getArg1())),
+        glVertexAttrib4f(msg.getArg0(), Float.intBitsToFloat(msg.getArg1()),
                 0, 0, 1);
     }
 
     // void glVertexAttrib1fv(GLuint indx, const GLfloat* values)
     public void glVertexAttrib1fv(Message msg) {
         final ByteBuffer values = msg.getData().asReadOnlyByteBuffer();
+        values.order(SampleView.targetByteOrder);
         glVertexAttrib4f(msg.getArg0(),
-                Float.intBitsToFloat(Integer.reverseBytes(values.getInt())),
+                Float.intBitsToFloat(values.getInt()),
                 0, 0, 1);
     }
 
     // void glVertexAttrib2f(GLuint indx, GLfloat x, GLfloat y)
     public void glVertexAttrib2f(Message msg) {
-        glVertexAttrib4f(msg.getArg0(), Float.intBitsToFloat(Integer.reverseBytes(msg.getArg1())),
-                Float.intBitsToFloat(Integer.reverseBytes(msg.getArg2())), 0, 1);
+        glVertexAttrib4f(msg.getArg0(), Float.intBitsToFloat(msg.getArg1()),
+                Float.intBitsToFloat(msg.getArg2()), 0, 1);
     }
 
     // void glVertexAttrib2fv(GLuint indx, const GLfloat* values)
     public void glVertexAttrib2fv(Message msg) {
         final ByteBuffer values = msg.getData().asReadOnlyByteBuffer();
+        values.order(SampleView.targetByteOrder);
         glVertexAttrib4f(msg.getArg0(),
-                Float.intBitsToFloat(Integer.reverseBytes(values.getInt())),
-                Float.intBitsToFloat(Integer.reverseBytes(values.getInt())), 0, 1);
+                Float.intBitsToFloat(values.getInt()),
+                Float.intBitsToFloat(values.getInt()), 0, 1);
     }
 
     // void glVertexAttrib3f(GLuint indx, GLfloat x, GLfloat y, GLfloat z)
     public void glVertexAttrib3f(Message msg) {
-        glVertexAttrib4f(msg.getArg0(), Float.intBitsToFloat(Integer.reverseBytes(msg.getArg1())),
-                Float.intBitsToFloat(Integer.reverseBytes(msg.getArg2())),
-                Float.intBitsToFloat(Integer.reverseBytes(msg.getArg3())), 1);
+        glVertexAttrib4f(msg.getArg0(), Float.intBitsToFloat(msg.getArg1()),
+                Float.intBitsToFloat(msg.getArg2()),
+                Float.intBitsToFloat(msg.getArg3()), 1);
     }
 
     // void glVertexAttrib3fv(GLuint indx, const GLfloat* values)
     public void glVertexAttrib3fv(Message msg) {
         final ByteBuffer values = msg.getData().asReadOnlyByteBuffer();
+        values.order(SampleView.targetByteOrder);
         glVertexAttrib4f(msg.getArg0(),
-                Float.intBitsToFloat(Integer.reverseBytes(values.getInt())),
-                Float.intBitsToFloat(Integer.reverseBytes(values.getInt())),
-                Float.intBitsToFloat(Integer.reverseBytes(values.getInt())), 1);
+                Float.intBitsToFloat(values.getInt()),
+                Float.intBitsToFloat(values.getInt()),
+                Float.intBitsToFloat(values.getInt()), 1);
     }
 
     public void glVertexAttrib4f(Message msg) {
-        glVertexAttrib4f(msg.getArg0(), Float.intBitsToFloat(Integer.reverseBytes(msg.getArg1())),
-                Float.intBitsToFloat(Integer.reverseBytes(msg.getArg2())),
-                Float.intBitsToFloat(Integer.reverseBytes(msg.getArg3())),
-                Float.intBitsToFloat(Integer.reverseBytes(msg.getArg4())));
+        glVertexAttrib4f(msg.getArg0(), Float.intBitsToFloat(msg.getArg1()),
+                Float.intBitsToFloat(msg.getArg2()),
+                Float.intBitsToFloat(msg.getArg3()),
+                Float.intBitsToFloat(msg.getArg4()));
     }
 
     void glVertexAttrib4f(int indx, float x, float y, float z, float w) {
@@ -406,10 +487,11 @@
     // void glVertexAttrib4fv(GLuint indx, const GLfloat* values)
     public void glVertexAttrib4fv(Message msg) {
         final ByteBuffer values = msg.getData().asReadOnlyByteBuffer();
+        values.order(SampleView.targetByteOrder);
         glVertexAttrib4f(msg.getArg0(),
-                Float.intBitsToFloat(Integer.reverseBytes(values.getInt())),
-                Float.intBitsToFloat(Integer.reverseBytes(values.getInt())),
-                Float.intBitsToFloat(Integer.reverseBytes(values.getInt())),
-                Float.intBitsToFloat(Integer.reverseBytes(values.getInt())));
+                Float.intBitsToFloat(values.getInt()),
+                Float.intBitsToFloat(values.getInt()),
+                Float.intBitsToFloat(values.getInt()),
+                Float.intBitsToFloat(values.getInt()));
     }
 }
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/MessageData.java b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageData.java
index 16eee59..13965d3 100644
--- a/tools/glesv2debugger/src/com/android/glesv2debugger/MessageData.java
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageData.java
@@ -26,17 +26,20 @@
 import org.eclipse.swt.graphics.ImageData;
 
 public class MessageData {
-    public final Message msg;
+    public final Message msg, oriMsg;
     public Image image; // texture
     public String shader; // shader source
     public String text;
+    public String[] columns = new String[3];
     public float[] data;
     public int maxAttrib; // used for formatting data
     public GLEnum dataType; // could be float, int; mainly for formatting use
     Context context; // the context before this call
 
-    public MessageData(final Device device, final Message msg, final Context context) {
+    public MessageData(final Device device, final Message msg, final Message oriMsg,
+            final Context context) {
         this.msg = msg;
+        this.oriMsg = oriMsg;
         this.context = context;
         image = null;
         shader = null;
@@ -46,21 +49,26 @@
         ImageData imageData = null;
         if (function != Message.Function.ACK)
             assert msg.hasTime();
-        builder.append(function);
+        builder.append(columns[0] = function.name());
         while (builder.length() < 30)
             builder.append(' ');
-        builder.append(String.format("%.3f", msg.getTime()));
+        columns[1] = String.format("%.3f", msg.getTime());
         if (msg.hasClock())
-            builder.append(String.format(":%.3f", msg.getClock()));
-        builder.append(String.format("  0x%08X", msg.getContextId()));
+            columns[1] += String.format(":%.3f", msg.getClock());
+        builder.append(columns[1]);
+
         builder.append("  ");
+        builder.append(String.format("0x%08X", msg.getContextId()));
+        builder.append("  ");
+        columns[2] = "";
         if (msg.getType() == Type.BeforeCall) // incomplete call, client SKIPPED
-            builder.append("[BeforeCall(AfterCall missing)] ");
+            columns[2] = "[BeforeCall(AfterCall missing)] ";
         else if (msg.getType() == Type.AfterGeneratedCall)
-            builder.append("[AfterGeneratedCall] ");
+            columns[2] = "[AfterGeneratedCall] ";
         else
             assert msg.getType() == Type.AfterCall;
-        builder.append(MessageFormatter.Format(msg));
+        columns[2] += MessageFormatter.Format(msg);
+        builder.append(columns[2]);
         switch (function) {
             case glDrawArrays: // msg was modified by GLServerVertex
             case glDrawElements:
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/SampleView.java b/tools/glesv2debugger/src/com/android/glesv2debugger/SampleView.java
index 87d97a4..9c2a28f 100644
--- a/tools/glesv2debugger/src/com/android/glesv2debugger/SampleView.java
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/SampleView.java
@@ -20,6 +20,7 @@
 import com.android.glesv2debugger.DebuggerMessage.Message.Function;
 import com.android.glesv2debugger.DebuggerMessage.Message.Prop;
 import com.android.glesv2debugger.DebuggerMessage.Message.Type;
+import com.android.sdklib.util.SparseArray;
 
 import org.eclipse.jface.action.Action;
 import org.eclipse.jface.action.IMenuListener;
@@ -28,27 +29,30 @@
 import org.eclipse.jface.action.MenuManager;
 import org.eclipse.jface.action.Separator;
 import org.eclipse.jface.dialogs.MessageDialog;
-import org.eclipse.jface.viewers.DoubleClickEvent;
-import org.eclipse.jface.viewers.IDoubleClickListener;
-import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.ColumnWeightData;
 import org.eclipse.jface.viewers.ISelectionChangedListener;
 import org.eclipse.jface.viewers.IStructuredContentProvider;
-import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.ITableLabelProvider;
 import org.eclipse.jface.viewers.LabelProvider;
 import org.eclipse.jface.viewers.ListViewer;
 import org.eclipse.jface.viewers.SelectionChangedEvent;
 import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.TableLayout;
 import org.eclipse.jface.viewers.TreeViewer;
 import org.eclipse.jface.viewers.Viewer;
 import org.eclipse.jface.viewers.ViewerFilter;
 import org.eclipse.jface.viewers.ViewerSorter;
 import org.eclipse.jface.window.Window;
 import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
 import org.eclipse.swt.graphics.Font;
 import org.eclipse.swt.graphics.GC;
 import org.eclipse.swt.graphics.Image;
 import org.eclipse.swt.graphics.Point;
 import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Canvas;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Event;
@@ -56,11 +60,13 @@
 import org.eclipse.swt.widgets.Menu;
 import org.eclipse.swt.widgets.ScrollBar;
 import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Slider;
 import org.eclipse.swt.widgets.TabFolder;
 import org.eclipse.swt.widgets.TabItem;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
 import org.eclipse.swt.widgets.Text;
 import org.eclipse.ui.IActionBars;
-import org.eclipse.ui.ISharedImages;
 import org.eclipse.ui.IWorkbenchActionConstants;
 import org.eclipse.ui.PlatformUI;
 import org.eclipse.ui.part.ViewPart;
@@ -70,9 +76,7 @@
 import java.io.PrintWriter;
 import java.nio.ByteOrder;
 import java.text.SimpleDateFormat;
-import java.util.ArrayList;
 import java.util.Calendar;
-import java.util.HashMap;
 
 /**
  * This sample class demonstrates how to plug-in a new workbench view. The view
@@ -89,13 +93,14 @@
  * <p>
  */
 
-public class SampleView extends ViewPart implements Runnable {
+public class SampleView extends ViewPart implements Runnable, SelectionListener {
     public static final ByteOrder targetByteOrder = ByteOrder.LITTLE_ENDIAN;
 
     boolean running = false;
     Thread thread;
-    MessageQueue messageQueue;
-    ViewContentProvider viewContentProvider;
+    MessageQueue messageQueue = new MessageQueue(this);
+    SparseArray<DebugContext> debugContexts = new SparseArray<DebugContext>();
+
     /**
      * The ID of the view as specified by the extension.
      */
@@ -104,54 +109,33 @@
     TabFolder tabFolder;
     TabItem tabItemText, tabItemImage, tabItemBreakpointOption;
     TabItem tabItemShaderEditor, tabContextViewer;
-    ListViewer viewer;
+    ListViewer viewer; // or TableViewer
+    Slider frameNum; // scale max cannot overlap min, so max is array size
     TreeViewer contextViewer;
     BreakpointOption breakpointOption;
     ShaderEditor shaderEditor;
     Canvas canvas;
     Text text;
     Action actionConnect; // connect / disconnect
-    Action doubleClickAction;
+
     Action actionAutoScroll;
     Action actionFilter;
     Action actionCapture;
     Action actionPort;
 
+    Action actContext; // for toggling contexts
+    DebugContext current = null;
+
     Point origin = new Point(0, 0); // for smooth scrolling canvas
     String[] filters = null;
-    public HashMap<Integer, Context> contexts = new HashMap<Integer, Context>();
 
-    /*
-     * The content provider class is responsible for providing objects to the
-     * view. It can wrap existing objects in adapters or simply return objects
-     * as-is. These objects may be sensitive to the current input of the view,
-     * or ignore it and always show the same content (like Task List, for
-     * example).
-     */
-
-    class ViewContentProvider implements IStructuredContentProvider {
-        ArrayList<MessageData> entries = new ArrayList<MessageData>();
-
-        public void add(final ArrayList<MessageData> msgs) {
-            entries.addAll(msgs);
-            viewer.getList().getDisplay().syncExec(new Runnable() {
-                @Override
-                public void run() {
-                    viewer.add(msgs.toArray());
-                    org.eclipse.swt.widgets.ScrollBar bar = viewer
-                            .getList().getVerticalBar();
-                    if (null != bar && actionAutoScroll.isChecked()) {
-                        bar.setSelection(bar.getMaximum());
-                        viewer.getList().setSelection(
-                                entries.size() - 1);
-                        // MessageDataSelected(entries.get(entries.size() - 1));
-                    }
-                }
-            });
-        }
+    class ViewContentProvider extends LabelProvider implements IStructuredContentProvider,
+            ITableLabelProvider {
+        Frame frame = null;
 
         @Override
         public void inputChanged(Viewer v, Object oldInput, Object newInput) {
+            frame = (Frame) newInput;
         }
 
         @Override
@@ -160,26 +144,36 @@
 
         @Override
         public Object[] getElements(Object parent) {
-            return entries.toArray();
+            return frame.calls.toArray();
         }
-    }
 
-    class ViewLabelProvider extends LabelProvider implements
-            ILabelProvider {
         @Override
         public String getText(Object obj) {
             MessageData msgData = (MessageData) obj;
-            if (null == msgData)
-                return obj.toString();
             return msgData.text;
         }
 
         @Override
         public Image getImage(Object obj) {
             MessageData msgData = (MessageData) obj;
-            if (null == msgData.image)
-                return PlatformUI.getWorkbench().getSharedImages()
-                        .getImage(ISharedImages.IMG_OBJ_ELEMENT);
+            return msgData.image;
+        }
+
+        @Override
+        public String getColumnText(Object obj, int index) {
+            MessageData msgData = (MessageData) obj;
+            if (index >= msgData.columns.length)
+                return null;
+            return msgData.columns[index];
+        }
+
+        @Override
+        public Image getColumnImage(Object obj, int index) {
+            if (index > -1)
+                return null;
+            MessageData msgData = (MessageData) obj;
+            if (msgData.image == null)
+                return null;
             return msgData.image;
         }
     }
@@ -207,12 +201,7 @@
         }
     }
 
-    /**
-     * The constructor.
-     */
     public SampleView() {
-        messageQueue = new MessageQueue(this);
-
         MessageParserEx messageParserEx = new MessageParserEx();
         Message.Builder builder = Message.newBuilder();
         messageParserEx.Parse(builder, "glUniform4fv(1,2,[0,1,2,3,4,5,6,7])");
@@ -221,18 +210,64 @@
                         "void glShaderSource(shader=4, count=1, string=\"dksjafhskjahourehghskjg\", length=0x0)");
     }
 
-    public void CreateView(Composite parent) {
-        viewer = new ListViewer(parent);
+    public void CreateLeftPane(Composite parent) {
+        Composite composite = new Composite(parent, 0);
+
+        GridLayout gridLayout = new GridLayout();
+        gridLayout.numColumns = 1;
+        composite.setLayout(gridLayout);
+
+        frameNum = new Slider(composite, SWT.BORDER | SWT.HORIZONTAL);
+        frameNum.setMinimum(0);
+        frameNum.setMaximum(1);
+        frameNum.setSelection(0);
+        frameNum.addSelectionListener(this);
+
+        GridData gridData = new GridData();
+        gridData.horizontalAlignment = SWT.FILL;
+        gridData.grabExcessHorizontalSpace = true;
+        gridData.verticalAlignment = SWT.FILL;
+        frameNum.setLayoutData(gridData);
+
+        Table table = new Table(composite, SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI
+                | SWT.FULL_SELECTION);
+        TableLayout layout = new TableLayout();
+        table.setLayout(layout);
+        table.setLinesVisible(true);
+        table.setHeaderVisible(true);
+        String[] headings = {
+                "Name", "Elapsed (ms)", "Detail"
+        };
+        int[] weights = {
+                50, 16, 60
+        };
+        int[] widths = {
+                180, 90, 200
+        };
+        for (int i = 0; i < headings.length; i++) {
+            layout.addColumnData(new ColumnWeightData(weights[i], widths[i],
+                    true));
+            TableColumn nameCol = new TableColumn(table, SWT.NONE, i);
+            nameCol.setText(headings[i]);
+        }
+
+        // viewer = new TableViewer(table);
+        viewer = new ListViewer(composite, SWT.DEFAULT);
         viewer.getList().setFont(new Font(viewer.getList().getDisplay(), "Courier", 10, SWT.BOLD));
-        viewContentProvider = new ViewContentProvider();
-        viewer.setContentProvider(viewContentProvider);
-        viewer.setLabelProvider(new ViewLabelProvider());
+        ViewContentProvider contentProvider = new ViewContentProvider();
+        viewer.setContentProvider(contentProvider);
+        viewer.setLabelProvider(contentProvider);
         // viewer.setSorter(new NameSorter());
-        viewer.setInput(getViewSite());
         viewer.setFilters(new ViewerFilter[] {
                 new Filter()
         });
 
+        gridData = new GridData();
+        gridData.horizontalAlignment = SWT.FILL;
+        gridData.grabExcessHorizontalSpace = true;
+        gridData.verticalAlignment = SWT.FILL;
+        gridData.grabExcessVerticalSpace = true;
+        viewer.getControl().setLayoutData(gridData);
     }
 
     /**
@@ -241,15 +276,12 @@
      */
     @Override
     public void createPartControl(Composite parent) {
-        CreateView(parent);
+        CreateLeftPane(parent);
 
         // Create the help context id for the viewer's control
         PlatformUI.getWorkbench().getHelpSystem()
                 .setHelp(viewer.getControl(), "GLESv2DebuggerClient.viewer");
 
-        // layoutComposite = new LayoutComposite(parent, 0);
-        // layoutComposite.setLayout(new FillLayout());
-
         tabFolder = new TabFolder(parent, SWT.BORDER);
 
         text = new Text(tabFolder, SWT.NO_BACKGROUND | SWT.READ_ONLY
@@ -276,7 +308,8 @@
         tabItemShaderEditor.setControl(shaderEditor);
 
         contextViewer = new TreeViewer(tabFolder);
-        ContextViewProvider contextViewProvider = new ContextViewProvider();
+        ContextViewProvider contextViewProvider = new ContextViewProvider(this);
+        contextViewer.addSelectionChangedListener(contextViewProvider);
         contextViewer.setContentProvider(contextViewProvider);
         contextViewer.setLabelProvider(contextViewProvider);
         tabContextViewer = new TabItem(tabFolder, SWT.NONE);
@@ -363,7 +396,6 @@
 
         makeActions();
         hookContextMenu();
-        hookDoubleClickAction();
         hookSelectionChanged();
         contributeToActionBars();
     }
@@ -473,6 +505,24 @@
             }
         });
 
+        actContext = new Action("Context: 0x", Action.AS_DROP_DOWN_MENU) {
+            @Override
+            public void run()
+                              {
+                                  if (debugContexts.size() < 2)
+                                      return;
+                                  final String idStr = this.getText().substring(
+                                          "Context: 0x".length());
+                                  if (idStr.length() == 0)
+                                      return;
+                                  final int contextId = Integer.parseInt(idStr, 16);
+                                  int index = debugContexts.indexOfKey(contextId);
+                                  index = (index + 1) % debugContexts.size();
+                                  ChangeContext(debugContexts.valueAt(index));
+                              }
+        };
+        manager.add(actContext);
+
         actionPort = new Action("5039", Action.AS_DROP_DOWN_MENU)
         {
             @Override
@@ -521,30 +571,16 @@
         };
         actionConnect.setText("Connect");
         actionConnect.setToolTipText("Connect to debuggee");
-
-        doubleClickAction = new Action() {
-            @Override
-            public void run() {
-                IStructuredSelection selection = (IStructuredSelection) viewer
-                        .getSelection();
-                MessageData msgData = (MessageData) selection.getFirstElement();
-            }
-        };
-    }
-
-    private void hookDoubleClickAction() {
-        viewer.addDoubleClickListener(new IDoubleClickListener() {
-            @Override
-            public void doubleClick(DoubleClickEvent event) {
-                doubleClickAction.run();
-            }
-        });
     }
 
     void MessageDataSelected(final MessageData msgData) {
         if (null == msgData)
             return;
-        contextViewer.setInput(msgData.context);
+        if (frameNum.getSelection() == frameNum.getMaximum())
+            return; // scale max cannot overlap min, so max is array size
+        final Frame frame = current.frames.get(frameNum.getSelection());
+        final Context context = current.ComputeContext(frame, msgData);
+        contextViewer.setInput(context);
         if (null != msgData.image) {
             canvas.setBackgroundImage(msgData.image);
             tabFolder.setSelection(tabItemImage);
@@ -615,16 +651,32 @@
         } catch (IOException e1) {
             showError(e1);
         }
-        ArrayList<MessageData> msgs = new ArrayList<MessageData>();
-        boolean shaderEditorUpdate = false;
+
+        int newMessages = 0;
+
+        boolean shaderEditorUpdate = false, currentUpdate = false;
         while (running) {
             if (!messageQueue.IsRunning())
                 break;
 
-            Message msg = messageQueue.RemoveCompleteMessage(0);
-            if (msgs.size() > 60 || (msgs.size() > 0 && null == msg)) {
-                viewContentProvider.add(msgs);
-                msgs.clear();
+            final Message oriMsg = messageQueue.RemoveCompleteMessage(0);
+            if (newMessages > 60 || (newMessages > 0 && null == oriMsg)) {
+                newMessages = 0;
+
+                if (currentUpdate || current == null)
+                    getSite().getShell().getDisplay().syncExec(new Runnable() {
+                        @Override
+                        public void run() {
+                            if (current == null)
+                                ChangeContext(debugContexts.valueAt(0));
+                            else
+                                viewer.refresh(false);
+                            frameNum.setMaximum(current.frames.size());
+                            if (actionAutoScroll.isChecked())
+                                viewer.getList().setSelection(viewer.getList().getItemCount() - 1);
+                        }
+                    });
+                currentUpdate = false;
 
                 if (shaderEditorUpdate)
                     this.getSite().getShell().getDisplay().syncExec(new Runnable() {
@@ -635,7 +687,7 @@
                     });
                 shaderEditorUpdate = false;
             }
-            if (null == msg) {
+            if (null == oriMsg) {
                 try {
                     Thread.sleep(1);
                     continue;
@@ -644,21 +696,20 @@
                 }
             }
 
-            Context context = contexts.get(msg.getContextId());
-            if (null == context) {
-                context = new Context(msg.getContextId());
-                contexts.put(msg.getContextId(), context);
+            DebugContext debugContext = debugContexts.get(oriMsg.getContextId());
+            if (debugContext == null) {
+                debugContext = new DebugContext(oriMsg.getContextId());
+                debugContexts.put(oriMsg.getContextId(), debugContext);
             }
-            Context newContext = context.ProcessMessage(msg);
-            // TODO: full cloning on change not implemented yet
-            if (newContext.processed != null)
-                msg = newContext.processed;
-            contexts.put(msg.getContextId(), newContext);
-            shaderEditorUpdate |= newContext.serverShader.uiUpdate;
-            newContext.serverShader.uiUpdate = false;
 
-            final MessageData msgData = new MessageData(this.getViewSite()
-                    .getShell().getDisplay(), msg, context);
+            final MessageData msgData = debugContext.ProcessMessage(oriMsg);
+            if (current == debugContext) {
+                currentUpdate = true;
+            }
+
+            shaderEditorUpdate |= debugContext.currentContext.serverShader.uiUpdate;
+            debugContext.currentContext.serverShader.uiUpdate = false;
+
             if (null != writer) {
                 writer.write(msgData.text + "\n");
                 if (msgData.msg.getFunction() == Function.eglSwapBuffers) {
@@ -666,7 +717,7 @@
                     writer.flush();
                 }
             }
-            msgs.add(msgData);
+            newMessages++;
         }
         if (running)
             ConnectDisconnect(); // error occurred, disconnect
@@ -675,4 +726,37 @@
             writer.close();
         }
     }
+
+    /** can be called from non-UI thread */
+    void ChangeContext(final DebugContext newContext) {
+        getSite().getShell().getDisplay().syncExec(new Runnable() {
+            @Override
+            public void run() {
+                current = newContext;
+                frameNum.setMaximum(current.frames.size());
+                frameNum.setSelection(0);
+                viewer.setInput(current.frames.get(frameNum.getSelection()));
+                shaderEditor.Update();
+                actContext.setText("Context: 0x" + Integer.toHexString(current.contextId));
+                getViewSite().getActionBars().getToolBarManager().update(true);
+            }
+        });
+    }
+
+    @Override
+    public void widgetSelected(SelectionEvent e) {
+        if (e.widget != frameNum)
+            assert false;
+        if (current == null)
+            return;
+        if (frameNum.getSelection() == current.frames.size())
+            return; // scale maximum cannot overlap minimum
+        Frame frame = current.frames.get(frameNum.getSelection());
+        viewer.setInput(frame);
+    }
+
+    @Override
+    public void widgetDefaultSelected(SelectionEvent e) {
+        widgetSelected(e);
+    }
 }
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/ShaderEditor.java b/tools/glesv2debugger/src/com/android/glesv2debugger/ShaderEditor.java
index 3aca1c2..dfb19d2 100644
--- a/tools/glesv2debugger/src/com/android/glesv2debugger/ShaderEditor.java
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/ShaderEditor.java
@@ -102,7 +102,9 @@
     public void Update() {
         list.removeAll();
         String progs = "Current Programs: ";
-        for (Context context : sampleView.contexts.values()) {
+        for (int j = 0; j < sampleView.debugContexts.size(); j++) {
+            final Context context = sampleView.debugContexts.valueAt(j).currentContext;
+
             if (context.serverShader.current != null) {
                 progs += context.serverShader.current.name + "(0x";
                 progs += Integer.toHexString(context.contextId) + ") ";
@@ -130,6 +132,7 @@
                 }
                 list.add(builder.toString());
             }
+
         }
 
         currentPrograms.setText(progs);
@@ -336,7 +339,8 @@
         String[] details = list.getSelection()[0].split("\\s+");
         final int contextId = Integer.parseInt(details[0], 16);
         int name = Integer.parseInt(details[2]);
-        current = sampleView.contexts.get(contextId).serverShader.shaders.get(name);
+        current = sampleView.debugContexts.get(contextId).currentContext.serverShader.shaders
+                .get(name);
         styledText.setText(current.source);
     }