GLES2Dbg: reconstruct vertex attributes to match indices

Rather than converting glDrawElements into glDrawArrays and
 uploading all attributes each draw call.
Also added CaptureDraw and CaptureSwap options.

Change-Id: If8ac6556a2674868ce83f074ce4068a6af2d3a0e
Signed-off-by: David Li <davidxli@google.com>
diff --git a/tools/glesv2debugger/generate_MessageParser_java.py b/tools/glesv2debugger/generate_MessageParser_java.py
index 0c5934b..9078d8e 100755
--- a/tools/glesv2debugger/generate_MessageParser_java.py
+++ b/tools/glesv2debugger/generate_MessageParser_java.py
@@ -81,25 +81,31 @@
 
     ByteString ParseFloats(int count) {
         ByteBuffer buffer = ByteBuffer.allocate(count * 4);
+        buffer.order(SampleView.targetByteOrder);
         String [] arg = GetList();
         for (int i = 0; i < count; i++)
             buffer.putFloat(Float.parseFloat(arg[i].trim()));
+        buffer.rewind();
         return ByteString.copyFrom(buffer);
     }
 
     ByteString ParseInts(int count) {
         ByteBuffer buffer = ByteBuffer.allocate(count * 4);
+        buffer.order(SampleView.targetByteOrder);
         String [] arg = GetList();
         for (int i = 0; i < count; i++)
             buffer.putInt(Integer.parseInt(arg[i].trim()));
+        buffer.rewind();
         return ByteString.copyFrom(buffer);
     }
 
     ByteString ParseUInts(int count) {
         ByteBuffer buffer = ByteBuffer.allocate(count * 4);
+        buffer.order(SampleView.targetByteOrder);
         String [] arg = GetList();
         for (int i = 0; i < count; i++)
             buffer.putInt((int)(Long.parseLong(arg[i].trim()) & 0xffffffff));
+        buffer.rewind();
         return ByteString.copyFrom(buffer);
     }
 
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/BreakpointOption.java b/tools/glesv2debugger/src/com/android/glesv2debugger/BreakpointOption.java
index 0733f28..2ed03c4 100644
--- a/tools/glesv2debugger/src/com/android/glesv2debugger/BreakpointOption.java
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/BreakpointOption.java
@@ -102,13 +102,15 @@
     public boolean ProcessMessage(final MessageQueue queue, final Message msg) throws IOException {
         // use DefaultProcessMessage just to register the GL call
         // but do not send response
+        final int contextId = msg.getContextId();
         if (msg.getType() == Type.BeforeCall || msg.getType() == Type.AfterCall)
             queue.DefaultProcessMessage(msg, true, false);
         final Message.Builder builder = Message.newBuilder();
-        builder.setContextId(msg.getContextId());
+        builder.setContextId(contextId);
         builder.setType(Type.Response);
         builder.setExpectResponse(true);
         final Shell shell = sampleView.getViewSite().getShell();
+        final boolean send[] = new boolean[1];
         shell.getDisplay().syncExec(new Runnable() {
             @Override
             public void run() {
@@ -149,8 +151,8 @@
                     {
                         builder.setFunction(Function.SKIP);
                         // AfterCall is skipped, so push BeforeCall to complete
-                        if (msg.getType() == Type.BeforeCall)
-                            queue.CompletePartialMessage(msg.getContextId());
+                        if (queue.GetPartialMessage(contextId) != null)
+                            queue.CompletePartialMessage(contextId);
                     }
                     else if (s.startsWith("c"))
                         builder.setFunction(Function.CONTINUE);
@@ -165,9 +167,10 @@
                     {
                         MessageParserEx.instance.Parse(builder, inputDialog.getValue());
                         lastFunction = builder.getFunction();
+                        builder.setExpectResponse(true);
                         // AfterCall is skipped, so push BeforeCall to complete
-                        if (msg.getType() == Type.BeforeCall)
-                            queue.CompletePartialMessage(msg.getContextId());
+                        if (queue.GetPartialMessage(contextId) != null)
+                            queue.CompletePartialMessage(contextId);
                     }
                 }
                 // else defaults to continue BeforeCall and skip AfterCall
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/CodeGen.java b/tools/glesv2debugger/src/com/android/glesv2debugger/CodeGen.java
index 7f4bcef..d347bde 100644
--- a/tools/glesv2debugger/src/com/android/glesv2debugger/CodeGen.java
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/CodeGen.java
@@ -212,13 +212,13 @@
             code.format("glTexParameteriv(%s, GL_TEXTURE_MAG_FILTER, (GLint[]){%s});CHKERR;\n",
                     tex.target, tex.mag);
         }
-        for (int i = 0; i < serverTexture.tmu2D.length && i < 16; i++) {
+        for (int i = 0; i < serverTexture.tmu2D.length; i++) {
             code.format("glActiveTexture(%s);CHKERR;\n",
                     GLEnum.valueOf(GLEnum.GL_TEXTURE0.value + i));
             code.format("glBindTexture(GL_TEXTURE_2D, texture_%d);CHKERR;\n",
                     serverTexture.tmu2D[i]);
         }
-        for (int i = 0; i < serverTexture.tmuCube.length && i < 16; i++) {
+        for (int i = 0; i < serverTexture.tmuCube.length; i++) {
             code.format("glActiveTexture(%s);CHKERR;\n",
                     GLEnum.valueOf(GLEnum.GL_TEXTURE0.value + i));
             code.format("glBindTexture(GL_TEXTURE_CUBE_MAP, texture_%d);CHKERR;\n",
@@ -414,54 +414,233 @@
         if (namesArray.indexOfKey(name) < 0) {
             namesHeader.format("extern GLuint %s;\n", id);
             namesSource.format("GLuint %s = 0;\n", id);
-        }
-        code.format("%s = %d;\n", id, name);
+        } else if (namesArray.get(name) != name)
+            code.format("%s = %d;\n", id, name); // name was deleted
         namesArray.put(name, name);
         code.write(MessageFormatter.Format(msg, true));
         code.write(";CHKERR;\n");
     }
 
-    private void CodeGenDrawArrays(final GLServerVertex v, final ByteBuffer[] attribs,
-            final int mode, final int count) throws IOException {
-        code.write("{\n");
-        code.format("    FILE * attribFile = fopen(\"/sdcard/frame_data.bin\", \"rb\");CHKERR;\n");
-        code.format("    assert(attribFile);CHKERR;\n");
-        code.format("    fseek(attribFile, %d, SEEK_SET);CHKERR;\n", dataOut.getChannel()
-                .position());
-        code.format("    glBindBuffer(GL_ARRAY_BUFFER, 0);CHKERR;\n");
-        for (int i = 0; i < attribs.length; i++) {
+    private void CodeGenDrawArrays(final GLServerVertex v, final MessageData msgData)
+            throws IOException {
+        final int maxAttrib = msgData.msg.getArg7();
+        if (maxAttrib < 1) {
+            code.write("// no vertex data\n");
+            return;
+        }
+        final byte[] data = msgData.msg.getData().toByteArray();
+        final GLEnum mode = GLEnum.valueOf(msgData.msg.getArg0());
+        final int first = msgData.msg.getArg1(), count = msgData.msg.getArg2();
+        int attribDataStride = 0;
+        for (int i = 0; i < maxAttrib; i++) {
             final GLAttribPointer att = v.attribPointers[i];
             if (!att.enabled)
                 continue;
-            final byte[] data = attribs[i].array();
-            final String typeName = "GL" + att.type.name().substring(3).toLowerCase();
-            code.format("    %s * attrib%d = (%s *)malloc(%d);CHKERR;\n", typeName, i, typeName,
-                    data.length);
-            dataOut.write(data);
-            code.format("    fread(attrib%d, %d, 1, attribFile);CHKERR;\n", i, data.length);
-            // code.format("    for (unsigned int i = 0; i < %d; i++)\n", count
-            // * att.size);
-            // code.format("        printf(\"%%f \\n\", attrib%d[i]);CHKERR;\n",
-            // i);
-            code.format("    glVertexAttribPointer(%d, %d, %s, %b, %d, attrib%d);CHKERR;\n",
-                    i, att.size, att.type, att.normalized,
-                    att.size * GLServerVertex.TypeSize(att.type), i);
+            if (att.buffer != null)
+                continue;
+            attribDataStride += att.elemSize;
         }
-        code.format("    fclose(attribFile);CHKERR;\n");
-        code.format("    glDrawArrays(%s, 0, %d);CHKERR;\n", GLEnum.valueOf(mode), count);
-        for (int i = 0; i < attribs.length; i++)
-            if (v.attribPointers[i].enabled)
-                code.format("    free(attrib%d);CHKERR;\n", i);
-
-        if (v.attribBuffer != null)
-            code.format("    glBindBuffer(GL_ARRAY_BUFFER, %d);CHKERR;\n",
-                    v.attribBuffer.name);
+        assert attribDataStride * count == data.length;
+        code.write("{\n");
+        if (attribDataStride > 0) {
+            code.format("    FILE * attribFile = fopen(\"/sdcard/frame_data.bin\", \"rb\");CHKERR;\n");
+            code.format("    assert(attribFile);CHKERR;\n");
+            code.format("    fseek(attribFile, %d, SEEK_SET);CHKERR;\n", dataOut.getChannel()
+                    .position());
+            dataOut.write(data);
+            code.format("    char * const attribData = (char *)malloc(%d);\n", first
+                    * attribDataStride + data.length);
+            code.format("    assert(attribData);\n");
+            code.format("    fread(attribData + %d, %d, 1, attribFile);\n",
+                    first * attribDataStride, data.length);
+            code.format("    fclose(attribFile);\n");
+            code.format("    glBindBuffer(GL_ARRAY_BUFFER, 0);CHKERR;\n");
+            int attribDataOffset = 0;
+            for (int i = 0; i < maxAttrib; i++) {
+                final GLAttribPointer att = v.attribPointers[i];
+                if (!att.enabled)
+                    continue;
+                if (att.buffer != null)
+                    continue;
+                code.format(
+                        "    glVertexAttribPointer(%d, %d, %s, %b, %d, attribData + %d);CHKERR;\n",
+                        i, att.size, att.type, att.normalized,
+                        attribDataStride, attribDataOffset);
+                attribDataOffset += att.elemSize;
+            }
+            if (v.attribBuffer != null)
+                code.format("    glBindBuffer(GL_ARRAY_BUFFER, %d);CHKERR;\n",
+                        v.attribBuffer.name);
+        }
+        code.format("    glDrawArrays(%s, %d, %d);CHKERR;\n", mode, first, count);
+        if (attribDataStride > 0)
+            code.format("    free(attribData);CHKERR;\n");
         code.write("};\n");
     }
 
-    private void CodeGenFunction(final Context ctx, final MessageData msgData) throws IOException {
+    private void CodeGenDrawElements(final GLServerVertex v, final MessageData msgData)
+            throws IOException {
+        final int maxAttrib = msgData.msg.getArg7();
+        if (maxAttrib < 1) {
+            code.write("// no vertex data\n");
+            return;
+        }
+        final GLEnum mode = GLEnum.valueOf(msgData.msg.getArg0());
+        final int count = msgData.msg.getArg1();
+        final GLEnum type = GLEnum.valueOf(msgData.msg.getArg2());
+        String typeName = "GLubyte";
+        if (type == GLEnum.GL_UNSIGNED_SHORT)
+            typeName = "GLushort";
+        int attribDataStride = 0;
+        for (int i = 0; i < maxAttrib; i++) {
+            final GLAttribPointer att = v.attribPointers[i];
+            if (!att.enabled)
+                continue;
+            if (att.buffer != null)
+                continue;
+            attribDataStride += att.elemSize;
+        }
+        code.write("{\n");
+        if (v.indexBuffer == null || attribDataStride > 0) {
+            // need to load user pointer indices and/or attributes
+            final byte[] element = new byte[attribDataStride];
+            final ByteBuffer data = msgData.msg.getData().asReadOnlyByteBuffer();
+            data.order(SampleView.targetByteOrder);
+            final ByteBuffer indexData = ByteBuffer.allocate(count * GLServerVertex.TypeSize(type));
+            indexData.order(SampleView.targetByteOrder);
+            final ByteBuffer attribData = ByteBuffer.allocate(count * attribDataStride);
+            attribData.order(SampleView.targetByteOrder);
+            int maxIndex = -1;
+            ByteBuffer indexSrc = data;
+            if (v.indexBuffer != null) {
+                indexSrc = v.indexBuffer.data;
+                indexSrc.position(msgData.msg.getArg3());
+            }
+            indexSrc.order(SampleView.targetByteOrder);
+            for (int i = 0; i < count; i++) {
+                int index = -1;
+                if (type == GLEnum.GL_UNSIGNED_BYTE) {
+                    byte idx = indexSrc.get();
+                    index = idx & 0xff;
+                    indexData.put(idx);
+                } else if (type == GLEnum.GL_UNSIGNED_SHORT) {
+                    short idx = indexSrc.getShort();
+                    index = idx & 0xffff;
+                    indexData.putShort(idx);
+                } else
+                    assert false;
+                data.get(element);
+                attribData.put(element);
+                if (index > maxIndex)
+                    maxIndex = index;
+            }
+            code.format("    FILE * attribFile = fopen(\"/sdcard/frame_data.bin\", \"rb\");CHKERR;\n");
+            code.format("    assert(attribFile);CHKERR;\n");
+            code.format("    fseek(attribFile, 0x%X, SEEK_SET);CHKERR;\n",
+                    dataOut.getChannel().position());
+            dataOut.write(indexData.array());
+            code.format("    %s * const indexData = (%s *)malloc(%d);\n", typeName, typeName,
+                    indexData.capacity());
+            code.format("    assert(indexData);\n");
+            code.format("    fread(indexData, %d, 1, attribFile);\n", indexData.capacity());
+            if (attribDataStride > 0) {
+                code.format("    glBindBuffer(GL_ARRAY_BUFFER, 0);CHKERR;\n");
+                for (int i = 0; i < maxAttrib; i++) {
+                    final GLAttribPointer att = v.attribPointers[i];
+                    if (!att.enabled)
+                        continue;
+                    if (att.buffer != null)
+                        continue;
+                    code.format("    char * const attrib%d = (char *)malloc(%d);\n",
+                            i, att.elemSize * (maxIndex + 1));
+                    code.format("    assert(attrib%d);\n", i);
+                    code.format(
+                            "    glVertexAttribPointer(%d, %d, %s, %b, %d, attrib%d);CHKERR;\n",
+                            i, att.size, att.type, att.normalized, att.elemSize, i);
+                }
+                dataOut.write(attribData.array());
+                code.format("    for (%s i = 0; i < %d; i++) {\n", typeName, count);
+                for (int i = 0; i < maxAttrib; i++) {
+                    final GLAttribPointer att = v.attribPointers[i];
+                    if (!att.enabled)
+                        continue;
+                    if (att.buffer != null)
+                        continue;
+                    code.format(
+                            "        fread(attrib%d + indexData[i] * %d, %d, 1, attribFile);\n",
+                            i, att.elemSize, att.elemSize);
+                }
+                code.format("    }\n");
+                if (v.attribBuffer != null)
+                    code.format("    glBindBuffer(GL_ARRAY_BUFFER, %d);CHKERR;\n",
+                            v.attribBuffer.name);
+            }
+            code.format("    fclose(attribFile);\n");
+        }
+        if (v.indexBuffer != null)
+            code.format("    glDrawElements(%s, %d, %s, (const void *)%d);CHKERR;\n",
+                    mode, count, type, msgData.msg.getArg3());
+        else {
+            code.format("    glDrawElements(%s, %d, %s, indexData);CHKERR;\n",
+                    mode, count, type);
+            code.format("    free(indexData);\n");
+        }
+        for (int i = 0; i < maxAttrib; i++) {
+            final GLAttribPointer att = v.attribPointers[i];
+            if (!att.enabled)
+                continue;
+            if (att.buffer != null)
+                continue;
+            code.format("    free(attrib%d);\n", i);
+        }
+        code.write("};\n");
+    }
+
+    private void CodeGenDraw(final GLServerVertex v, final MessageData msgData)
+            throws IOException {
+        final int maxAttrib = msgData.msg.getArg7();
+        if (maxAttrib < 1) {
+            code.write("// no vertex data\n");
+            return;
+        }
+        final int count = msgData.attribs[0].length / 4;
+        final GLEnum mode = GLEnum.valueOf(msgData.msg.getArg0());
+        final ByteBuffer attribData = ByteBuffer.allocate(maxAttrib * count * 16);
+        attribData.order(SampleView.targetByteOrder);
+        for (int i = 0; i < count; i++)
+            for (int j = 0; j < maxAttrib; j++)
+                for (int k = 0; k < 4; k++)
+                    attribData.putFloat(msgData.attribs[j][i * 4 + k]);
+        assert attribData.remaining() == 0;
+        code.write("{\n");
+        code.format("    FILE * attribFile = fopen(\"/sdcard/frame_data.bin\", \"rb\");CHKERR;\n");
+        code.format("    assert(attribFile);CHKERR;\n");
+        code.format("    fseek(attribFile, 0x%X, SEEK_SET);CHKERR;\n",
+                dataOut.getChannel().position());
+        dataOut.write(attribData.array());
+        code.format("    char * const attribData = (char *)malloc(%d);\n", attribData.capacity());
+        code.format("    assert(attribData);\n");
+        code.format("    fread(attribData, %d, 1, attribFile);\n", attribData.capacity());
+        code.format("    fclose(attribFile);\n");
+        code.format("    glBindBuffer(GL_ARRAY_BUFFER, 0);CHKERR;\n");
+        for (int i = 0; i < maxAttrib; i++) {
+            final GLAttribPointer att = v.attribPointers[i];
+            assert msgData.attribs[i].length == count * 4;
+            code.format(
+                    "    glVertexAttribPointer(%d, %d, GL_FLOAT, GL_FALSE, %d, attribData + %d);CHKERR;\n",
+                        i, att.size, maxAttrib * 16, i * 16);
+        }
+        code.format("    glDrawArrays(%s, 0, %d);CHKERR;\n", mode, count);
+        code.format("    free(attribData);\n");
+        if (v.attribBuffer != null)
+            code.format("    glBindBuffer(GL_ARRAY_BUFFER, %d);CHKERR;\n",
+                        v.attribBuffer.name);
+        code.write("};\n");
+    }
+
+    private void CodeGenFunction(final Context ctx, final MessageData msgData)
+            throws IOException {
         final Message msg = msgData.msg;
-        final Message oriMsg = msgData.oriMsg;
         String call = MessageFormatter.Format(msg, true);
         switch (msg.getFunction()) {
             case glActiveTexture:
@@ -536,10 +715,12 @@
             case glDisableVertexAttribArray:
                 break;
             case glDrawArrays:
-                CodeGenDrawArrays(ctx.serverVertex, msgData.attribs, msg.getArg0(), msg.getArg2());
+                // CodeGenDraw(ctx.serverVertex, msgData);
+                CodeGenDrawArrays(ctx.serverVertex, msgData);
                 return;
             case glDrawElements:
-                CodeGenDrawArrays(ctx.serverVertex, msgData.attribs, msg.getArg0(), msg.getArg1());
+                // CodeGenDraw(ctx.serverVertex, msgData);
+                CodeGenDrawElements(ctx.serverVertex, msgData);
                 return;
             case glEnable:
             case glEnableVertexAttribArray:
@@ -677,8 +858,10 @@
             case glVertexAttrib4fv:
                 break;
             case glVertexAttribPointer:
-                // pointer set during glDrawArrays/Elements from captured data
-                call = call.replace("arg5", "NULL");
+                // if it's user pointer, then CodeGenDrawArrays/Elements will
+                // replace it with loaded data just before the draw
+                call = call.replace("arg5", "(const void *)0x" +
+                        Integer.toHexString(msg.getArg5()));
                 break;
             case glViewport:
                 break;
@@ -688,6 +871,12 @@
                 assert false;
                 return;
         }
+        if (call.indexOf("glEnable(/*cap*/ GL_TEXTURE_2D)") >= 0)
+            return;
+        else if (call.indexOf("glDisable(/*cap*/ GL_TEXTURE_2D)") >= 0)
+            return;
+        else if (call.indexOf("glActiveTexture(/*texture*/ GL_TEXTURE_2D)") >= 0)
+            return;
         code.write(call + ";CHKERR;\n");
     }
 
@@ -716,7 +905,7 @@
         namesHeader.write("#include <assert.h>\n");
         namesHeader.write("#include <GLES2/gl2.h>\n");
         namesHeader.write("#include <GLES2/gl2ext.h>\n");
-        namesHeader.write("#define CHKERR /*assert(GL_NO_ERROR == glGetError());/**/\n");
+        namesHeader.write("#define CHKERR assert(GL_NO_ERROR == glGetError());/**/\n");
         namesHeader.write("void FrameSetup();\n");
         namesHeader.write("extern const unsigned int FrameCount;\n");
         namesHeader.write("extern const GLuint program_0;\n");
@@ -776,6 +965,7 @@
     private DebugContext dbgCtx;
     private int count;
     private IProgressMonitor progress;
+
     @Override
     public void run(IProgressMonitor monitor) throws InvocationTargetException,
             InterruptedException {
@@ -800,7 +990,7 @@
                 final MessageData msgData = frame.Get(j);
                 code.format("/* frame function %d: %s %s*/\n", j, msgData.msg.getFunction(),
                         MessageFormatter.Format(msgData.msg, false));
-                ctx.ProcessMessage(msgData.oriMsg);
+                ctx.ProcessMessage(msgData.msg);
                 try {
                     CodeGenFunction(ctx, msgData);
                 } catch (IOException e) {
@@ -865,7 +1055,7 @@
             final MessageData msgData = frame.Get(i);
             code.format("/* frame function %d: %s %s*/\n", i, msgData.msg.getFunction(),
                     MessageFormatter.Format(msgData.msg, false));
-            ctx.ProcessMessage(msgData.oriMsg);
+            ctx.ProcessMessage(msgData.msg);
             try {
                 CodeGenFunction(ctx, msgData);
             } catch (IOException e) {
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/Context.java b/tools/glesv2debugger/src/com/android/glesv2debugger/Context.java
index 23160c4..2296b31 100644
--- a/tools/glesv2debugger/src/com/android/glesv2debugger/Context.java
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/Context.java
@@ -17,9 +17,11 @@
 package com.android.glesv2debugger;
 
 import com.android.glesv2debugger.DebuggerMessage.Message;
+import com.android.glesv2debugger.DebuggerMessage.Message.DataType;
 import com.android.glesv2debugger.DebuggerMessage.Message.Function;
 import com.android.sdklib.util.SparseArray;
 import com.android.sdklib.util.SparseIntArray;
+import com.google.protobuf.ByteString;
 
 import org.eclipse.jface.viewers.ISelectionChangedListener;
 import org.eclipse.jface.viewers.ITreeContentProvider;
@@ -68,7 +70,7 @@
             if (call == calls.get(i))
                 return ctx;
             else
-                ctx.ProcessMessage(calls.get(i).oriMsg);
+                ctx.ProcessMessage(calls.get(i).msg);
         assert false;
         return ctx;
     }
@@ -106,10 +108,9 @@
                     len = Integer.reverseBytes(len);
                 final byte[] data = new byte[len];
                 file.read(data);
-                final Message oriMsg = Message.parseFrom(data);
-                final Message msg = ctx.ProcessMessage(oriMsg);
-                final MessageData msgData = new MessageData(Display.getCurrent(), msg, oriMsg, ctx);
-                msgData.attribs = ctx.serverVertex.fetchedAttribs;
+                Message msg = Message.parseFrom(data);
+                ctx.ProcessMessage(msg);
+                final MessageData msgData = new MessageData(Display.getCurrent(), msg, ctx);
                 calls.add(msgData);
             }
             file.seek(oriPosition);
@@ -133,8 +134,8 @@
         this.contextId = contextId;
         currentContext = new Context(contextId);
         try {
-            file = new RandomAccessFile(Integer.toHexString(contextId) + ".gles2dbg",
-                    "rw");
+            file = new RandomAccessFile("0x" + Integer.toHexString(contextId) +
+                    ".gles2dbg", "rw");
             frames.add(new Frame(currentContext, file.getFilePointer()));
 
         } catch (FileNotFoundException e) {
@@ -148,10 +149,25 @@
         loadedFrame = lastFrame;
     }
 
-    /** Writes oriMsg to file, and formats into MessageData for current frame */
-    void ProcessMessage(final Message oriMsg) {
+    /**
+     * Caches new Message, and formats into MessageData for current frame; this
+     * function is called exactly once for each new Message
+     */
+    void ProcessMessage(final Message newMsg) {
+        Message msg = newMsg;
+        currentContext.ProcessMessage(newMsg);
+        if (msg.hasDataType() && msg.getDataType() == DataType.ReferencedImage) {
+            final byte[] referenced = MessageProcessor.LZFDecompressChunks(msg.getData());
+            currentContext.readPixelRef = MessageProcessor.DecodeReferencedImage(
+                    currentContext.readPixelRef, referenced);
+            final byte[] decoded = MessageProcessor.LZFCompressChunks(
+                    currentContext.readPixelRef, referenced.length);
+            msg = newMsg.toBuilder().setDataType(DataType.NonreferencedImage)
+                    .setData(ByteString.copyFrom(decoded)).build();
+        }
         synchronized (file) {
-            final byte[] data = oriMsg.toByteArray();
+            lastFrame.IncreaseCallsCount();
+            final byte[] data = msg.toByteArray();
             final ByteBuffer len = ByteBuffer.allocate(4);
             len.order(SampleView.targetByteOrder);
             len.putInt(data.length);
@@ -166,13 +182,8 @@
                 assert false;
             }
         }
-
-        lastFrame.IncreaseCallsCount();
-        final Message msg = currentContext.ProcessMessage(oriMsg);
         if (loadedFrame == lastFrame) {
-            final MessageData msgData = new MessageData(Display.getCurrent(), msg, oriMsg,
-                     currentContext);
-            msgData.attribs = currentContext.serverVertex.fetchedAttribs;
+            final MessageData msgData = new MessageData(Display.getCurrent(), msg, currentContext);
             lastFrame.Add(msgData);
             uiUpdate = true;
         }
@@ -242,7 +253,8 @@
             copy.serverShader = serverShader.clone(copy);
             copy.serverState = serverState.clone();
             copy.serverTexture = serverTexture.clone(copy);
-            copy.readPixelRef = readPixelRef.clone();
+            // don't need to clone readPixelsRef, since referenced images
+            // are decoded when they are encountered
             return copy;
         } catch (CloneNotSupportedException e) {
             e.printStackTrace();
@@ -251,21 +263,16 @@
         }
     }
 
-    /** returns processed Message, which could be a new Message */
-    public Message ProcessMessage(Message msg) {
-        if (serverVertex.Process(msg)) {
-            if (serverVertex.processed != null)
-                return serverVertex.processed;
-            else
-                return msg;
-        }
+    /** mainly updating states */
+    public void ProcessMessage(Message msg) {
+        if (serverVertex.Process(msg))
+            return;
         if (serverShader.ProcessMessage(msg))
-            return msg;
+            return;
         if (serverState.ProcessMessage(msg))
-            return msg;
+            return;
         if (serverTexture.ProcessMessage(msg))
-            return msg;
-        return msg;
+            return;
     }
 }
 
@@ -310,10 +317,10 @@
         switch (msg.getFunction()) {
             case glTexImage2D:
             case glTexSubImage2D:
-                return entry.image = new MessageData(Display.getCurrent(), msg, msg, null).image;
             case glCopyTexImage2D:
             case glCopyTexSubImage2D:
-                return null; // TODO: compute context for reference frame
+                return entry.image = new MessageData(Display.getCurrent(), msg, null)
+                        .GetImage();
             default:
                 return null;
         }
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/DebuggerMessage.java b/tools/glesv2debugger/src/com/android/glesv2debugger/DebuggerMessage.java
index d871afb..996f749 100644
--- a/tools/glesv2debugger/src/com/android/glesv2debugger/DebuggerMessage.java
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/DebuggerMessage.java
@@ -528,9 +528,10 @@
     
     public enum Prop
         implements com.google.protobuf.Internal.EnumLite {
-      Capture(0, 0),
+      CaptureDraw(0, 0),
       TimeMode(1, 1),
       ExpectResponse(2, 2),
+      CaptureSwap(3, 3),
       ;
       
       
@@ -538,9 +539,10 @@
       
       public static Prop valueOf(int value) {
         switch (value) {
-          case 0: return Capture;
+          case 0: return CaptureDraw;
           case 1: return TimeMode;
           case 2: return ExpectResponse;
+          case 3: return CaptureSwap;
           default: return null;
         }
       }
@@ -693,6 +695,20 @@
     public boolean hasPixelType() { return hasPixelType; }
     public int getPixelType() { return pixelType_; }
     
+    // optional int32 image_width = 26;
+    public static final int IMAGE_WIDTH_FIELD_NUMBER = 26;
+    private boolean hasImageWidth;
+    private int imageWidth_ = 0;
+    public boolean hasImageWidth() { return hasImageWidth; }
+    public int getImageWidth() { return imageWidth_; }
+    
+    // optional int32 image_height = 27;
+    public static final int IMAGE_HEIGHT_FIELD_NUMBER = 27;
+    private boolean hasImageHeight;
+    private int imageHeight_ = 0;
+    public boolean hasImageHeight() { return hasImageHeight; }
+    public int getImageHeight() { return imageHeight_; }
+    
     // optional float time = 11;
     public static final int TIME_FIELD_NUMBER = 11;
     private boolean hasTime;
@@ -718,7 +734,7 @@
       function_ = com.android.glesv2debugger.DebuggerMessage.Message.Function.NEG;
       type_ = com.android.glesv2debugger.DebuggerMessage.Message.Type.BeforeCall;
       dataType_ = com.android.glesv2debugger.DebuggerMessage.Message.DataType.ReferencedImage;
-      prop_ = com.android.glesv2debugger.DebuggerMessage.Message.Prop.Capture;
+      prop_ = com.android.glesv2debugger.DebuggerMessage.Message.Prop.CaptureDraw;
     }
     public final boolean isInitialized() {
       if (!hasContextId) return false;
@@ -794,6 +810,12 @@
       if (hasPixelType()) {
         output.writeInt32(25, getPixelType());
       }
+      if (hasImageWidth()) {
+        output.writeInt32(26, getImageWidth());
+      }
+      if (hasImageHeight()) {
+        output.writeInt32(27, getImageHeight());
+      }
     }
     
     private int memoizedSerializedSize = -1;
@@ -886,6 +908,14 @@
         size += com.google.protobuf.CodedOutputStream
           .computeInt32Size(25, getPixelType());
       }
+      if (hasImageWidth()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(26, getImageWidth());
+      }
+      if (hasImageHeight()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(27, getImageHeight());
+      }
       memoizedSerializedSize = size;
       return size;
     }
@@ -1084,6 +1114,12 @@
         if (other.hasPixelType()) {
           setPixelType(other.getPixelType());
         }
+        if (other.hasImageWidth()) {
+          setImageWidth(other.getImageWidth());
+        }
+        if (other.hasImageHeight()) {
+          setImageHeight(other.getImageHeight());
+        }
         if (other.hasTime()) {
           setTime(other.getTime());
         }
@@ -1211,6 +1247,14 @@
               setPixelType(input.readInt32());
               break;
             }
+            case 208: {
+              setImageWidth(input.readInt32());
+              break;
+            }
+            case 216: {
+              setImageHeight(input.readInt32());
+              break;
+            }
           }
         }
       }
@@ -1552,6 +1596,42 @@
         return this;
       }
       
+      // optional int32 image_width = 26;
+      public boolean hasImageWidth() {
+        return result.hasImageWidth();
+      }
+      public int getImageWidth() {
+        return result.getImageWidth();
+      }
+      public Builder setImageWidth(int value) {
+        result.hasImageWidth = true;
+        result.imageWidth_ = value;
+        return this;
+      }
+      public Builder clearImageWidth() {
+        result.hasImageWidth = false;
+        result.imageWidth_ = 0;
+        return this;
+      }
+      
+      // optional int32 image_height = 27;
+      public boolean hasImageHeight() {
+        return result.hasImageHeight();
+      }
+      public int getImageHeight() {
+        return result.getImageHeight();
+      }
+      public Builder setImageHeight(int value) {
+        result.hasImageHeight = true;
+        result.imageHeight_ = value;
+        return this;
+      }
+      public Builder clearImageHeight() {
+        result.hasImageHeight = false;
+        result.imageHeight_ = 0;
+        return this;
+      }
+      
       // optional float time = 11;
       public boolean hasTime() {
         return result.hasTime();
@@ -1587,7 +1667,7 @@
       }
       public Builder clearProp() {
         result.hasProp = false;
-        result.prop_ = com.android.glesv2debugger.DebuggerMessage.Message.Prop.Capture;
+        result.prop_ = com.android.glesv2debugger.DebuggerMessage.Message.Prop.CaptureDraw;
         return this;
       }
       
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerTexture.java b/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerTexture.java
index fe3a34c..5235d9b 100644
--- a/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerTexture.java
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerTexture.java
@@ -83,8 +83,8 @@
     Context context;
 
     public GLEnum activeTexture = GLEnum.GL_TEXTURE0;
-    public int[] tmu2D = new int[32];
-    public int[] tmuCube = new int[32];
+    public int[] tmu2D = new int[16]; // TODO: MAX_COMBINED_TEXTURE_IMAGE_UNITS
+    public int[] tmuCube = new int[16];
     public SparseArray<GLTexture> textures = new SparseArray<GLTexture>();
     public GLTexture tex2D = null, texCube = null;
 
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerVertex.java b/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerVertex.java
index 8ee4c22..ed1572b 100644
--- a/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerVertex.java
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerVertex.java
@@ -39,6 +39,7 @@
             GLBuffer copy = (GLBuffer) super.clone();
             if (data != null) {
                 copy.data = ByteBuffer.allocate(data.capacity());
+                copy.data.order(SampleView.targetByteOrder);
                 data.position(0);
                 copy.data.put(data);
             }
@@ -55,6 +56,13 @@
     public int size; // number of values per vertex
     public GLEnum type; // data type
     public int stride; // bytes
+    /**
+     * element stride in bytes, used when fetching from buffer; not for fetching
+     * from user pointer since server already packed elements
+     */
+    int elemStride; // in bytes
+    /** element size in bytes */
+    int elemSize;
     public int ptr; // pointer in debugger server or byte offset into buffer
     public GLBuffer buffer;
     public boolean normalized;
@@ -80,9 +88,6 @@
     public GLBuffer attribBuffer, indexBuffer; // current binding
     public GLAttribPointer attribPointers[];
     public float defaultAttribs[][];
-    int maxAttrib;
-
-    ByteBuffer[] fetchedAttribs;
 
     public GLServerVertex() {
         buffers.append(0, null);
@@ -131,11 +136,8 @@
         }
     }
 
-    Message processed = null; // return; glDrawArrays/Elements with fetched data
-
     /** returns true if processed */
     public boolean Process(final Message msg) {
-        processed = null;
         switch (msg.getFunction()) {
             case glBindBuffer:
                 glBindBuffer(msg);
@@ -150,12 +152,7 @@
                 glDeleteBuffers(msg);
                 return true;
             case glDrawArrays:
-                if (msg.hasArg7())
-                    processed = glDrawArrays(msg);
-                return true;
             case glDrawElements:
-                if (msg.hasArg7())
-                    processed = glDrawElements(msg);
                 return true;
             case glDisableVertexAttribArray:
                 glDisableVertexAttribArray(msg);
@@ -341,86 +338,72 @@
         }
     }
 
-    void Fetch(int index, final ByteBuffer nonVBO, final ByteBuffer dst) {
+    void Fetch(final int maxAttrib, final int index, final int dstIdx, final ByteBuffer nonVBO,
+            final float[][] fetchedAttribs) {
         for (int i = 0; i < maxAttrib; i++) {
             final GLAttribPointer attrib = attribPointers[i];
             int size = 0;
             if (attrib.enabled) {
                 size = attrib.size;
-                final ByteBuffer fetched = fetchedAttribs[i];
-                final byte[] element = new byte[TypeSize(attrib.type) * size];
                 if (null != attrib.buffer) {
                     final ByteBuffer src = attrib.buffer.data;
-                    src.position(attrib.ptr + index * attrib.stride);
-                    src.get(element);
-                    src.position(attrib.ptr + index * attrib.stride);
+                    src.position(attrib.ptr + index * attrib.elemStride);
                     for (int j = 0; j < size; j++)
-                        dst.putFloat(FetchConvert(src, attrib.type, attrib.normalized));
-                } else {
-                    final int position = nonVBO.position();
-                    nonVBO.get(element);
-                    nonVBO.position(position);
+                        fetchedAttribs[i][dstIdx * 4 + j] = FetchConvert(src, attrib.type,
+                                attrib.normalized);
+                } else
                     for (int j = 0; j < size; j++)
-                        dst.putFloat(FetchConvert(nonVBO, attrib.type, attrib.normalized));
-                }
-                fetched.put(element);
+                        fetchedAttribs[i][dstIdx * 4 + j] = FetchConvert(nonVBO, attrib.type,
+                                attrib.normalized);
             }
             if (size < 1)
-                dst.putFloat(defaultAttribs[i][0]);
+                fetchedAttribs[i][dstIdx * 4 + 0] = defaultAttribs[i][0];
             if (size < 2)
-                dst.putFloat(defaultAttribs[i][1]);
+                fetchedAttribs[i][dstIdx * 4 + 1] = defaultAttribs[i][1];
             if (size < 3)
-                dst.putFloat(defaultAttribs[i][2]);
+                fetchedAttribs[i][dstIdx * 4 + 2] = defaultAttribs[i][2];
             if (size < 4)
-                dst.putFloat(defaultAttribs[i][3]);
+                fetchedAttribs[i][dstIdx * 4 + 3] = defaultAttribs[i][3];
         }
     }
 
-    // void glDrawArrays(GLenum mode, GLint first, GLsizei count)
-    public Message glDrawArrays(Message msg) {
-        maxAttrib = msg.getArg7();
-        fetchedAttribs = new ByteBuffer[maxAttrib];
-        for (int i = 0; i < maxAttrib; i++) {
-            if (!attribPointers[i].enabled)
-                continue;
-            fetchedAttribs[i] = ByteBuffer.allocate(TypeSize(attribPointers[i].type)
-                    * attribPointers[i].size * msg.getArg2());
-        }
+    /**
+     * fetches and converts vertex data from buffers, defaults and user pointers
+     * into MessageData; mainly for display use
+     */
+    public void glDrawArrays(MessageData msgData) {
+        final Message msg = msgData.msg;
+        if (!msg.hasArg7())
+            return;
+        final int maxAttrib = msg.getArg7();
         final int first = msg.getArg1(), count = msg.getArg2();
-        final ByteBuffer buffer = ByteBuffer.allocate(4 * 4 * maxAttrib * count);
+        msgData.attribs = new float[maxAttrib][count * 4];
         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);
+        for (int i = 0; i < count; i++)
+            Fetch(maxAttrib, first + i, i, arrays, msgData.attribs);
         assert null == arrays || arrays.remaining() == 0;
-        for (int i = 0; i < maxAttrib; i++) {
-            if (!attribPointers[i].enabled)
-                continue;
-            assert fetchedAttribs[i].remaining() == 0;
-        }
-        buffer.rewind();
-        return msg.toBuilder().setData(com.google.protobuf.ByteString.copyFrom(buffer))
-                .setArg8(GLEnum.GL_FLOAT.value).build();
     }
 
     // void glDrawElements(GLenum mode, GLsizei count, GLenum type, const
     // GLvoid* indices)
-    public Message glDrawElements(Message msg) {
-        maxAttrib = msg.getArg7();
-        fetchedAttribs = new ByteBuffer[maxAttrib];
-        for (int i = 0; i < maxAttrib; i++) {
-            if (!attribPointers[i].enabled)
-                continue;
-            fetchedAttribs[i] = ByteBuffer.allocate(TypeSize(attribPointers[i].type)
-                    * attribPointers[i].size * msg.getArg1());
-        }
+    /**
+     * fetches and converts vertex data from buffers, defaults and user pointers
+     * and indices from buffer/pointer into MessageData; mainly for display use
+     */
+    public void glDrawElements(MessageData msgData) {
+        final Message msg = msgData.msg;
+        if (!msg.hasArg7())
+            return;
+        final int maxAttrib = msg.getArg7();
         final int count = msg.getArg1();
         final GLEnum type = GLEnum.valueOf(msg.getArg2());
-        final ByteBuffer buffer = ByteBuffer.allocate(4 * 4 * maxAttrib * count);
+        msgData.attribs = new float[maxAttrib][count * 4];
+        msgData.indices = new short[count];
         ByteBuffer arrays = null, index = null;
         if (msg.hasData()) // server sends user pointer attribs
         {
@@ -431,26 +414,21 @@
             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(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);
-        else
+        if (GLEnum.GL_UNSIGNED_SHORT == type) {
+            for (int i = 0; i < count; i++) {
+                msgData.indices[i] = index.getShort();
+                Fetch(maxAttrib, msgData.indices[i] & 0xffff, i, arrays, msgData.attribs);
+            }
+        } else if (GLEnum.GL_UNSIGNED_BYTE == type) {
+            for (int i = 0; i < count; i++) {
+                msgData.indices[i] = (short) (index.get() & 0xff);
+                Fetch(maxAttrib, msgData.indices[i], i, arrays, msgData.attribs);
+            }
+        } else
             assert false;
         assert null == arrays || arrays.remaining() == 0;
-        for (int i = 0; i < maxAttrib; i++) {
-            if (!attribPointers[i].enabled)
-                continue;
-            assert fetchedAttribs[i].remaining() == 0;
-        }
-        buffer.rewind();
-        return msg.toBuilder().setData(com.google.protobuf.ByteString.copyFrom(buffer))
-                .setArg8(GLEnum.GL_FLOAT.value).build();
     }
 
     // void glEnableVertexAttribArray(GLuint index)
@@ -480,8 +458,11 @@
         attrib.type = GLEnum.valueOf(msg.getArg2());
         attrib.normalized = msg.getArg3() != 0;
         attrib.stride = msg.getArg4();
-        if (0 == attrib.stride)
-            attrib.stride = attrib.size * TypeSize(attrib.type);
+        attrib.elemSize = attrib.size * TypeSize(attrib.type);
+        if (attrib.stride == 0)
+            attrib.elemStride = attrib.elemSize;
+        else
+            attrib.elemStride = attrib.stride;
         attrib.ptr = msg.getArg5();
         attrib.buffer = attribBuffer;
     }
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/MessageData.java b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageData.java
index 1cd14d8..50fdf1c 100644
--- a/tools/glesv2debugger/src/com/android/glesv2debugger/MessageData.java
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageData.java
@@ -17,35 +17,28 @@
 package com.android.glesv2debugger;
 
 import com.android.glesv2debugger.DebuggerMessage.Message;
-import com.android.glesv2debugger.DebuggerMessage.Message.DataType;
 import com.android.glesv2debugger.DebuggerMessage.Message.Function;
 import com.android.glesv2debugger.DebuggerMessage.Message.Type;
 
 import org.eclipse.swt.graphics.Device;
 import org.eclipse.swt.graphics.Image;
 import org.eclipse.swt.graphics.ImageData;
-
-import java.nio.ByteBuffer;
+import org.eclipse.swt.widgets.Display;
 
 public class MessageData {
-    public final Message msg, oriMsg;
-    public Image image = null; // texture
+    public final Message msg;
+    private Image image = null; // texture
     public String shader = null; // shader source
     public String text;
     public String[] columns = new String[3];
-    public float[] data = null;
-    public int maxAttrib; // used for formatting data
-    public GLEnum dataType; // could be float, int; mainly for formatting use
 
-    ByteBuffer[] attribs = null;
+    float[][] attribs = null;
+    short[] indices;
 
-    public MessageData(final Device device, final Message msg, final Message oriMsg,
-            final Context context) {
+    public MessageData(final Device device, final Message msg, final Context context) {
         this.msg = msg;
-        this.oriMsg = oriMsg;
         StringBuilder builder = new StringBuilder();
         final Function function = msg.getFunction();
-        ImageData imageData = null;
         if (function != Message.Function.ACK && msg.getType() != Type.BeforeCall)
             assert msg.hasTime();
         builder.append(columns[0] = function.name());
@@ -69,65 +62,67 @@
         columns[2] += MessageFormatter.Format(msg, false);
         builder.append(columns[2]);
         switch (function) {
-            case glDrawArrays: // msg was modified by GLServerVertex
-            case glDrawElements:
-                if (!msg.hasArg8() || !msg.hasData())
+            case glDrawArrays:
+                if (!msg.hasArg7())
                     break;
-                dataType = GLEnum.valueOf(msg.getArg8());
-                maxAttrib = msg.getArg7();
-                data = MessageProcessor.ReceiveData(dataType, msg.getData());
+                context.serverVertex.glDrawArrays(this);
+                break;
+            case glDrawElements:
+                if (!msg.hasArg7())
+                    break;
+                context.serverVertex.glDrawElements(this);
                 break;
             case glShaderSource:
                 shader = msg.getData().toStringUtf8();
                 break;
+
+        }
+        text = builder.toString();
+    }
+
+    public Image GetImage() {
+        if (image != null)
+            return image;
+        ImageData imageData = null;
+        switch (msg.getFunction()) {
             case glTexImage2D:
                 if (!msg.hasData())
-                    break;
+                    return null;
                 imageData = MessageProcessor.ReceiveImage(msg.getArg3(), msg
                         .getArg4(), msg.getArg6(), msg.getArg7(), msg.getData());
-                if (null == imageData)
-                    break;
-                image = new Image(device, imageData);
-                break;
+                return image = new Image(Display.getCurrent(), imageData);
             case glTexSubImage2D:
                 assert msg.hasData();
                 imageData = MessageProcessor.ReceiveImage(msg.getArg4(), msg
                         .getArg5(), msg.getArg6(), msg.getArg7(), msg.getData());
-                if (null == imageData)
-                    break;
-                image = new Image(device, imageData);
-                break;
+                return image = new Image(Display.getCurrent(), imageData);
             case glCopyTexImage2D:
-                assert msg.getDataType() == DataType.ReferencedImage;
-                MessageProcessor.ref = context.readPixelRef;
                 imageData = MessageProcessor.ReceiveImage(msg.getArg5(), msg.getArg6(),
                         msg.getPixelFormat(), msg.getPixelType(), msg.getData());
-                MessageProcessor.ref = null;
-                image = new Image(device, imageData);
                 imageData = imageData.scaledTo(imageData.width, -imageData.height);
-                break;
+                return image = new Image(Display.getCurrent(), imageData);
             case glCopyTexSubImage2D:
-                assert msg.getDataType() == DataType.ReferencedImage;
-                MessageProcessor.ref = context.readPixelRef;
                 imageData = MessageProcessor.ReceiveImage(msg.getArg6(), msg.getArg7(),
                         msg.getPixelFormat(), msg.getPixelType(), msg.getData());
-                MessageProcessor.ref = null;
                 imageData = imageData.scaledTo(imageData.width, -imageData.height);
-                image = new Image(device, imageData);
-                break;
+                return image = new Image(Display.getCurrent(), imageData);
             case glReadPixels:
                 if (!msg.hasData())
-                    break;
-                if (msg.getDataType() == DataType.ReferencedImage)
-                    MessageProcessor.ref = context.readPixelRef;
+                    return null;
                 imageData = MessageProcessor.ReceiveImage(msg.getArg2(), msg.getArg3(),
                         msg.getArg4(), msg.getArg5(), msg.getData());
-                context.readPixelRef = MessageProcessor.ref;
-                MessageProcessor.ref = null;
                 imageData = imageData.scaledTo(imageData.width, -imageData.height);
-                image = new Image(device, imageData);
-                break;
+                return image = new Image(Display.getCurrent(), imageData);
+            case eglSwapBuffers:
+                if (!msg.hasData())
+                    return null;
+                imageData = MessageProcessor.ReceiveImage(msg.getImageWidth(),
+                        msg.getImageHeight(), msg.getPixelFormat(), msg.getPixelType(),
+                        msg.getData());
+                imageData = imageData.scaledTo(imageData.width, -imageData.height);
+                return image = new Image(Display.getCurrent(), imageData);
+            default:
+                return null;
         }
-        text = builder.toString();
     }
 }
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/MessageParser.java b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageParser.java
index 545f261..4bcff32 100644
--- a/tools/glesv2debugger/src/com/android/glesv2debugger/MessageParser.java
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageParser.java
@@ -45,25 +45,31 @@
 
     ByteString ParseFloats(int count) {
         ByteBuffer buffer = ByteBuffer.allocate(count * 4);
+        buffer.order(SampleView.targetByteOrder);
         String [] arg = GetList();
         for (int i = 0; i < count; i++)
             buffer.putFloat(Float.parseFloat(arg[i].trim()));
+        buffer.rewind();
         return ByteString.copyFrom(buffer);
     }
 
     ByteString ParseInts(int count) {
         ByteBuffer buffer = ByteBuffer.allocate(count * 4);
+        buffer.order(SampleView.targetByteOrder);
         String [] arg = GetList();
         for (int i = 0; i < count; i++)
             buffer.putInt(Integer.parseInt(arg[i].trim()));
+        buffer.rewind();
         return ByteString.copyFrom(buffer);
     }
 
     ByteString ParseUInts(int count) {
         ByteBuffer buffer = ByteBuffer.allocate(count * 4);
+        buffer.order(SampleView.targetByteOrder);
         String [] arg = GetList();
         for (int i = 0; i < count; i++)
             buffer.putInt((int)(Long.parseLong(arg[i].trim()) & 0xffffffff));
+        buffer.rewind();
         return ByteString.copyFrom(buffer);
     }
 
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/MessageProcessor.java b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageProcessor.java
index 0fe7f22..ec1d51b 100644
--- a/tools/glesv2debugger/src/com/android/glesv2debugger/MessageProcessor.java
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageProcessor.java
@@ -23,6 +23,7 @@
 import org.eclipse.swt.graphics.PaletteData;
 
 import java.nio.ByteBuffer;
+import java.util.Arrays;
 
 public class MessageProcessor {
     static void showError(final String message) {
@@ -30,8 +31,11 @@
         MessageDialog.openError(null, "MessageProcessor", message);
     }
 
-    public static byte[] ref; // inout; used for glReadPixels
-
+    /**
+     * data layout: uint32 total decompressed length, (chunks: uint32 chunk
+     * decompressed size, uint32 chunk compressed size, chunk data)+. 0 chunk
+     * compressed size means chunk is not compressed
+     */
     public static byte[] LZFDecompressChunks(final ByteString data) {
         ByteBuffer in = data.asReadOnlyByteBuffer();
         in.order(SampleView.targetByteOrder);
@@ -60,6 +64,44 @@
         return out.array();
     }
 
+    /** same data layout as LZFDecompressChunks */
+    public static byte[] LZFCompressChunks(final byte[] in, final int inSize) {
+        byte[] chunk = new byte[256 * 1024]; // chunk size is arbitrary
+        final ByteBuffer out = ByteBuffer.allocate(4 + (inSize + chunk.length - 1)
+                / chunk.length * (chunk.length + 4 * 2));
+        out.order(SampleView.targetByteOrder);
+        out.putInt(inSize);
+        for (int i = 0; i < inSize; i += chunk.length) {
+            int chunkIn = chunk.length;
+            if (i + chunkIn > inSize)
+                chunkIn = inSize - i;
+            final byte[] inChunk = java.util.Arrays.copyOfRange(in, i, i + chunkIn);
+            final int chunkOut = org.liblzf.CLZF
+                    .lzf_compress(inChunk, chunkIn, chunk, chunk.length);
+            out.putInt(chunkIn);
+            out.putInt(chunkOut);
+            if (chunkOut == 0) // compressed bigger than chunk (uncompressed)
+                out.put(inChunk);
+            else
+                out.put(chunk, 0, chunkOut);
+        }
+        return Arrays.copyOf(out.array(), out.position());
+    }
+
+    /**
+     * returns new ref, which is also the decoded image; ref could be bigger
+     * than pixels, in which case the first pixels.length bytes form the image
+     */
+    public static byte[] DecodeReferencedImage(byte[] ref, byte[] pixels) {
+        if (ref.length < pixels.length)
+            ref = new byte[pixels.length];
+        for (int i = 0; i < pixels.length; i++)
+            ref[i] ^= pixels[i];
+        for (int i = pixels.length; i < ref.length; i++)
+            ref[i] = 0; // clear unused ref to maintain consistency
+        return ref;
+    }
+
     public static ImageData ReceiveImage(int width, int height, int format,
             int type, final ByteString data) {
         assert width > 0 && height > 0;
@@ -75,6 +117,7 @@
                 break;
             default:
                 showError("unsupported texture type " + type);
+                return null;
         }
 
         switch (GLEnum.valueOf(format)) {
@@ -122,42 +165,9 @@
                 showError("unsupported texture format: " + format);
                 return null;
         }
-
         byte[] pixels = LZFDecompressChunks(data);
         assert pixels.length == width * height * (bpp / 8);
-
         PaletteData palette = new PaletteData(redMask, greenMask, blueMask);
-        if (null != ref) {
-            if (ref.length < pixels.length)
-                ref = new byte[width * height * (bpp / 8)];
-            for (int i = 0; i < pixels.length; i++)
-                ref[i] ^= pixels[i];
-            for (int i = pixels.length; i < ref.length; i++)
-                ref[i] = 0; // clear unused ref to maintain consistency
-            return new ImageData(width, height, bpp, palette, 1, ref);
-        } else
-            return new ImageData(width, height, bpp, palette, 1, pixels);
-    }
-
-    static public float[] ReceiveData(final GLEnum type, final ByteString data) {
-        final ByteBuffer buffer = data.asReadOnlyByteBuffer();
-        if (type == GLEnum.GL_FLOAT) {
-            float[] elements = new float[buffer.remaining() / 4];
-            for (int i = 0; i < elements.length; i++)
-                elements[i] = buffer.getFloat();
-            return elements;
-        } else if (type == GLEnum.GL_UNSIGNED_SHORT) {
-            float[] elements = new float[buffer.remaining() / 2];
-            for (int i = 0; i < elements.length; i++)
-                elements[i] = buffer.getShort() & 0xffff;
-            return elements;
-        } else if (type == GLEnum.GL_UNSIGNED_BYTE) {
-            float[] elements = new float[buffer.remaining() / 4];
-            for (int i = 0; i < elements.length; i++)
-                elements[i] = buffer.get() & 0xff;
-            return elements;
-        } else
-            assert false;
-        return null;
+        return new ImageData(width, height, bpp, palette, 1, pixels);
     }
 }
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/SampleView.java b/tools/glesv2debugger/src/com/android/glesv2debugger/SampleView.java
index 3b267ff..dfadf23 100644
--- a/tools/glesv2debugger/src/com/android/glesv2debugger/SampleView.java
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/SampleView.java
@@ -30,7 +30,6 @@
 import org.eclipse.jface.action.Separator;
 import org.eclipse.jface.dialogs.InputDialog;
 import org.eclipse.jface.dialogs.MessageDialog;
-import org.eclipse.jface.viewers.ColumnWeightData;
 import org.eclipse.jface.viewers.ISelectionChangedListener;
 import org.eclipse.jface.viewers.IStructuredContentProvider;
 import org.eclipse.jface.viewers.ITableLabelProvider;
@@ -38,7 +37,6 @@
 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;
@@ -65,8 +63,6 @@
 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.IWorkbenchActionConstants;
@@ -108,7 +104,7 @@
     TabFolder tabFolder;
     TabItem tabItemText, tabItemImage, tabItemBreakpointOption;
     TabItem tabItemShaderEditor, tabContextViewer;
-    ListViewer viewer; // or TableViewer
+    ListViewer viewer; // ListViewer / TableViewer
     Slider frameNum; // scale max cannot overlap min, so max is array size
     TreeViewer contextViewer;
     BreakpointOption breakpointOption;
@@ -119,7 +115,6 @@
 
     Action actionAutoScroll;
     Action actionFilter;
-    Action actionCapture;
     Action actionPort;
 
     Action actContext; // for toggling contexts
@@ -155,7 +150,7 @@
         @Override
         public Image getImage(Object obj) {
             MessageData msgData = (MessageData) obj;
-            return msgData.image;
+            return msgData.GetImage();
         }
 
         @Override
@@ -171,9 +166,7 @@
             if (index > -1)
                 return null;
             MessageData msgData = (MessageData) obj;
-            if (msgData.image == null)
-                return null;
-            return msgData.image;
+            return msgData.GetImage();
         }
     }
 
@@ -228,31 +221,33 @@
         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]);
-        }
+        // 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));
+        viewer.getList().setFont(new Font(viewer.getList().getDisplay(),
+                "Courier", 10, SWT.BOLD));
         ViewContentProvider contentProvider = new ViewContentProvider();
         viewer.setContentProvider(contentProvider);
         viewer.setLabelProvider(contentProvider);
@@ -435,6 +430,8 @@
         actionConnect = new Action("Connect", Action.AS_PUSH_BUTTON) {
             @Override
             public void run() {
+                if (!running)
+                    ChangeContext(null); // viewer will switch to newest context
                 ConnectDisconnect();
             }
         };
@@ -446,7 +443,10 @@
             public void run()
             {
                 if (!running)
+                {
+                    ChangeContext(null); // viewer will switch to newest context
                     OpenFile();
+                }
             }
         });
 
@@ -479,22 +479,57 @@
         };
         manager.add(actionFilter);
 
-        actionCapture = new Action("Capture", Action.AS_CHECK_BOX) {
+        manager.add(new Action("CaptureDraw", Action.AS_DROP_DOWN_MENU)
+        {
             @Override
-            public void run() {
+            public void run()
+            {
+                int contextId = 0;
+                if (current != null)
+                    contextId = current.contextId;
+                InputDialog inputDialog = new InputDialog(shell,
+                        "Capture glDrawArrays/Elements",
+                        "Enter number of glDrawArrays/Elements to glReadPixels for "
+                                + "context 0x" + Integer.toHexString(contextId) +
+                                "\n(0x0 is any context)", "9001", null);
+                if (inputDialog.open() != Window.OK)
+                    return;
                 Message.Builder builder = Message.newBuilder();
-                builder.setContextId(0); // FIXME: proper context id
+                builder.setContextId(contextId);
                 builder.setType(Type.Response);
                 builder.setExpectResponse(false);
                 builder.setFunction(Function.SETPROP);
-                builder.setProp(Prop.Capture);
-                builder.setArg0(isChecked() ? 1 : 0);
+                builder.setProp(Prop.CaptureDraw);
+                builder.setArg0(Integer.parseInt(inputDialog.getValue()));
                 messageQueue.AddCommand(builder.build());
-                manager.update(true);
             }
-        };
-        actionCapture.setChecked(false);
-        manager.add(actionCapture);
+        });
+
+        manager.add(new Action("CaptureSwap", Action.AS_DROP_DOWN_MENU)
+        {
+            @Override
+            public void run()
+            {
+                int contextId = 0;
+                if (current != null)
+                    contextId = current.contextId;
+                InputDialog inputDialog = new InputDialog(shell,
+                        "Capture eglSwapBuffers",
+                        "Enter number of eglSwapBuffers to glReadPixels for "
+                                + "context 0x" + Integer.toHexString(contextId) +
+                                "\n(0x0 is any context)", "9001", null);
+                if (inputDialog.open() != Window.OK)
+                    return;
+                Message.Builder builder = Message.newBuilder();
+                builder.setContextId(contextId);
+                builder.setType(Type.Response);
+                builder.setExpectResponse(false);
+                builder.setFunction(Function.SETPROP);
+                builder.setProp(Prop.CaptureSwap);
+                builder.setArg0(Integer.parseInt(inputDialog.getValue()));
+                messageQueue.AddCommand(builder.build());
+            }
+        });
 
         manager.add(new Action("SYSTEM_TIME_THREAD", Action.AS_DROP_DOWN_MENU)
         {
@@ -633,23 +668,28 @@
         final Frame frame = current.GetFrame(frameNum.getSelection());
         final Context context = frame.ComputeContext(msgData);
         contextViewer.setInput(context);
-        if (null != msgData.image) {
-            canvas.setBackgroundImage(msgData.image);
+        if (msgData.GetImage() != null) {
+            canvas.setBackgroundImage(msgData.GetImage());
             tabFolder.setSelection(tabItemImage);
             canvas.redraw();
         } else if (null != msgData.shader) {
             text.setText(msgData.shader);
             tabFolder.setSelection(tabItemText);
-        } else if (null != msgData.data) {
+        } else if (null != msgData.attribs) {
             StringBuilder builder = new StringBuilder();
-            for (int i = 0; i < msgData.data.length; i++) {
-                builder.append(String.format("%.3g", msgData.data[i]));
-                if (i % (4 * msgData.maxAttrib) == (4 * msgData.maxAttrib - 1))
-                    builder.append('\n');
-                else if (i % 4 == 3)
-                    builder.append(" -");
-                if (i < msgData.data.length - 1)
-                    builder.append(' ');
+            final int maxAttrib = msgData.msg.getArg7();
+            for (int i = 0; i < msgData.attribs[0].length / 4; i++) {
+                if (msgData.indices != null) {
+                    builder.append(msgData.indices[i] & 0xffff);
+                    builder.append(": ");
+                }
+                for (int j = 0; j < maxAttrib; j++) {
+                    for (int k = 0; k < 4; k++)
+                        builder.append(String.format("%.3g ", msgData.attribs[j][i * 4 + k]));
+                    if (j < maxAttrib - 1)
+                        builder.append("|| ");
+                }
+                builder.append('\n');
             }
             text.setText(builder.toString());
             tabFolder.setSelection(tabItemText);
@@ -703,9 +743,8 @@
                     getSite().getShell().getDisplay().syncExec(new Runnable() {
                         @Override
                         public void run() {
-                            if (current == null)
-                                ChangeContext(debugContexts.valueAt(0));
-                            else if (frameNum.getSelection() == current.FrameCount() - 1)
+                            if (frameNum.getSelection() == current.FrameCount() - 1 ||
+                                    frameNum.getSelection() == current.FrameCount() - 2)
                             {
                                 viewer.refresh(false);
                                 if (actionAutoScroll.isChecked())
@@ -734,18 +773,16 @@
                     showError(e);
                 }
             }
-
             DebugContext debugContext = debugContexts.get(oriMsg.getContextId());
             if (debugContext == null) {
                 debugContext = new DebugContext(oriMsg.getContextId());
                 debugContexts.put(oriMsg.getContextId(), debugContext);
             }
-
             debugContext.ProcessMessage(oriMsg);
-
             shaderEditorUpdate |= debugContext.currentContext.serverShader.uiUpdate;
             debugContext.currentContext.serverShader.uiUpdate = false;
-
+            if (current == null)
+                ChangeContext(debugContext);
             newMessages++;
         }
         if (running)
@@ -758,11 +795,25 @@
             @Override
             public void run() {
                 current = newContext;
-                frameNum.setMaximum(current.FrameCount());
-                frameNum.setSelection(0);
-                viewer.setInput(current.GetFrame(frameNum.getSelection()));
+                if (current != null)
+                {
+                    frameNum.setMaximum(current.FrameCount());
+                    if (frameNum.getSelection() >= current.FrameCount())
+                        if (current.FrameCount() > 0)
+                            frameNum.setSelection(current.FrameCount() - 1);
+                        else
+                            frameNum.setSelection(0);
+                    viewer.setInput(current.GetFrame(frameNum.getSelection()));
+                    actContext.setText("Context: 0x" + Integer.toHexString(current.contextId));
+                }
+                else
+                {
+                    frameNum.setMaximum(1); // cannot overlap min
+                    frameNum.setSelection(0);
+                    viewer.setInput(null);
+                    actContext.setText("Context: 0x");
+                }
                 shaderEditor.Update();
-                actContext.setText("Context: 0x" + Integer.toHexString(current.contextId));
                 getViewSite().getActionBars().getToolBarManager().update(true);
             }
         });