Added state tracking and vertex data capturing.

Change-Id: I91604740aa73a5611f0eb511d02f09a767273700
Signed-off-by: David Li <davidxli@google.com>
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/Activator.java b/tools/glesv2debugger/src/com/android/glesv2debugger/Activator.java
index f859510..6083c0f 100755
--- a/tools/glesv2debugger/src/com/android/glesv2debugger/Activator.java
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/Activator.java
@@ -43,6 +43,7 @@
      * org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext

      * )

      */

+    @Override

     public void start(BundleContext context) throws Exception {

         super.start(context);

         plugin = this;

@@ -54,6 +55,7 @@
      * org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext

      * )

      */

+    @Override

     public void stop(BundleContext context) throws Exception {

         plugin = null;

         super.stop(context);

diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerVertex.java b/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerVertex.java
new file mode 100644
index 0000000..0ff0347
--- /dev/null
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/GLServerVertex.java
@@ -0,0 +1,348 @@
+/*
+ ** 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 java.nio.ByteBuffer;
+import java.util.HashMap;
+
+class GLBuffer {
+    GLEnum usage;
+    GLEnum target;
+    ByteBuffer data;
+}
+
+class GLAttribPointer {
+    int size; // number of values per vertex
+    GLEnum type; // data type
+    int stride; // bytes
+    int ptr; // pointer in debugger server or byte offset into buffer
+    GLBuffer buffer;
+    boolean normalized;
+    boolean enabled;
+}
+
+public class GLServerVertex {
+
+    HashMap<Integer, GLBuffer> buffers;
+    GLBuffer attribBuffer, indexBuffer; // current binding
+    GLAttribPointer attribPointers[];
+    float defaultAttribs[][];
+    int maxAttrib;
+
+    public GLServerVertex() {
+        buffers = new HashMap<Integer, GLBuffer>();
+        buffers.put(0, null);
+        attribPointers = new GLAttribPointer[16];
+        for (int i = 0; i < attribPointers.length; i++)
+            attribPointers[i] = new GLAttribPointer();
+        defaultAttribs = new float[16][4];
+        for (int i = 0; i < defaultAttribs.length; i++) {
+            defaultAttribs[i][0] = 0;
+            defaultAttribs[i][1] = 0;
+            defaultAttribs[i][2] = 0;
+            defaultAttribs[i][3] = 1;
+        }
+    }
+
+    // void API_ENTRY(glBindBuffer)(GLenum target, GLuint buffer)
+    public void glBindBuffer(Message msg) {
+        if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_ARRAY_BUFFER) {
+            attribBuffer = buffers.get(msg.getArg1());
+            if (null != attribBuffer)
+                attribBuffer.target = GLEnum.GL_ARRAY_BUFFER;
+        } else if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_ELEMENT_ARRAY_BUFFER) {
+            indexBuffer = buffers.get(msg.getArg1());
+            if (null != indexBuffer)
+                indexBuffer.target = GLEnum.GL_ELEMENT_ARRAY_BUFFER;
+        } else
+            assert false;
+    }
+
+    // void API_ENTRY(glBufferData)(GLenum target, GLsizeiptr size, const
+    // GLvoid:size:in data, GLenum usage)
+    public void glBufferData(Message msg) {
+        if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_ARRAY_BUFFER) {
+            attribBuffer.usage = GLEnum.valueOf(msg.getArg3());
+            attribBuffer.data = msg.getData().asReadOnlyByteBuffer();
+        } else if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_ELEMENT_ARRAY_BUFFER) {
+            indexBuffer.usage = GLEnum.valueOf(msg.getArg3());
+            indexBuffer.data = msg.getData().asReadOnlyByteBuffer();
+        } else
+            assert false;
+    }
+
+    // void API_ENTRY(glBufferSubData)(GLenum target, GLintptr offset,
+    // GLsizeiptr size, const GLvoid:size:in data)
+    public void glBufferSubData(Message msg) {
+        if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_ARRAY_BUFFER) {
+            if (attribBuffer.data.isReadOnly()) {
+                ByteBuffer buffer = ByteBuffer.allocate(attribBuffer.data.capacity());
+                buffer.put(attribBuffer.data);
+                attribBuffer.data = buffer;
+            }
+            attribBuffer.data.position(msg.getArg1());
+            attribBuffer.data.put(msg.getData().asReadOnlyByteBuffer());
+        } else if (GLEnum.valueOf(msg.getArg0()) == GLEnum.GL_ELEMENT_ARRAY_BUFFER) {
+            if (indexBuffer.data.isReadOnly()) {
+                ByteBuffer buffer = ByteBuffer.allocate(indexBuffer.data.capacity());
+                buffer.put(indexBuffer.data);
+                indexBuffer.data = buffer;
+            }
+            indexBuffer.data.position(msg.getArg1());
+            indexBuffer.data.put(msg.getData().asReadOnlyByteBuffer());
+        } else
+            assert false;
+    }
+
+    // void glDeleteBuffers(GLsizei n, const GLuint* buffers)
+    public void glDeleteBuffers(Message msg) {
+        final int n = msg.getArg0();
+        final ByteBuffer names = msg.getData().asReadOnlyByteBuffer();
+        for (int i = 0; i < n; i++) {
+            int name = Integer.reverseBytes(names.getInt());
+            GLBuffer buffer = buffers.get(name);
+            for (int j = 0; j < attribPointers.length; j++)
+                if (attribPointers[j].buffer == buffer) {
+                    attribPointers[j].buffer = null;
+                    attribPointers[j].enabled = false;
+                }
+            if (attribBuffer == buffer)
+                attribBuffer = null;
+            if (indexBuffer == buffer)
+                indexBuffer = null;
+            buffers.remove(name);
+        }
+    }
+
+    // void glDisableVertexAttribArray(GLuint index)
+    public void glDisableVertexAttribArray(Message msg) {
+        attribPointers[msg.getArg0()].enabled = false;
+    }
+
+    float FetchConvert(final ByteBuffer src, final GLEnum type, final boolean normalized) {
+        if (GLEnum.GL_FLOAT == type)
+            return Float.intBitsToFloat(Integer.reverseBytes(src.getInt()));
+        else if (GLEnum.GL_UNSIGNED_INT == type)
+            if (normalized)
+                return (Integer.reverseBytes(src.getInt()) & 0xffffffffL) / (2e32f - 1);
+            else 
+                return Integer.reverseBytes(src.getInt()) & 0xffffffffL;
+        else if (GLEnum.GL_INT == type)
+            if (normalized)
+                return (Integer.reverseBytes(src.getInt()) * 2 + 1) / (2e32f - 1);
+            else 
+                return Integer.reverseBytes(src.getInt());
+        else if (GLEnum.GL_UNSIGNED_SHORT == type)
+            if (normalized)
+                return (Short.reverseBytes(src.getShort()) & 0xffff) / (2e16f - 1);
+            else 
+                return Short.reverseBytes(src.getShort()) & 0xffff;
+        else if (GLEnum.GL_SHORT == type)
+            if (normalized)
+                return (Short.reverseBytes(src.getShort()) * 2 + 1) / (2e16f - 1);
+            else 
+                return Short.reverseBytes(src.getShort());
+        else if (GLEnum.GL_UNSIGNED_BYTE == type)
+            if (normalized)
+                return (src.get() & 0xff) / (2e8f - 1);
+            else 
+                return src.get() & 0xff;
+        else if (GLEnum.GL_BYTE == type)
+            if (normalized)
+                return (src.get() * 2 + 1) / (2e8f - 1);
+            else 
+                return src.get();
+        else if (GLEnum.GL_FIXED == type)
+            if (normalized)
+                return (Integer.reverseBytes(src.getInt()) * 2 + 1) / (2e32f - 1);
+            else
+                return Integer.reverseBytes(src.getInt()) / (2e16f);
+        else
+            assert false;
+        return 0;
+    }
+
+    void Fetch(int index, final ByteBuffer nonVBO, final ByteBuffer dst) {
+        for (int i = 0; i < maxAttrib; i++) {
+            final GLAttribPointer attrib = attribPointers[i];
+            int size = 0;
+            if (attrib.enabled)
+                size = attrib.size;
+            if (null != attrib.buffer) {
+                final ByteBuffer src = attrib.buffer.data;
+                src.position(attrib.ptr + i * attrib.stride);
+                dst.putFloat(FetchConvert(src, attrib.type, attrib.normalized));
+            } else
+                for (int j = 0; j < size; j++)
+                    dst.putFloat(FetchConvert(nonVBO, attrib.type, attrib.normalized));
+            if (size < 1)
+                dst.putFloat(defaultAttribs[i][0]);
+            if (size < 2)
+                dst.putFloat(defaultAttribs[i][1]);
+            if (size < 3)
+                dst.putFloat(defaultAttribs[i][2]);
+            if (size < 4)
+                dst.putFloat(defaultAttribs[i][3]);
+        }
+    }
+
+    // void glDrawArrays(GLenum mode, GLint first, GLsizei count)
+    public Message glDrawArrays(Message msg) {
+        maxAttrib = msg.getArg7();
+        final int first = msg.getArg1(), count = msg.getArg2();
+        final ByteBuffer buffer = ByteBuffer.allocate(4 * 4 * maxAttrib * count);
+        ByteBuffer arrays = null;
+        if (msg.hasData()) // server sends user pointer attribs
+            arrays = msg.getData().asReadOnlyByteBuffer();
+        for (int i = first; i < first + count; i++)
+            Fetch(i, arrays, buffer);
+        assert null == arrays || arrays.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();
+        final int count = msg.getArg1();
+        final GLEnum type = GLEnum.valueOf(msg.getArg2());
+        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();
+        if (null == indexBuffer)
+            index = arrays; // server also interleaves user pointer indices
+        else {
+            index = indexBuffer.data;
+            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);
+        else if (GLEnum.GL_UNSIGNED_BYTE == type)
+            for (int i = 0; i < count; i++)
+                Fetch(index.get() & 0xff, arrays, buffer);
+        else
+            assert false;
+        assert null == arrays || arrays.remaining() == 0;
+        buffer.rewind();
+        return msg.toBuilder().setData(com.google.protobuf.ByteString.copyFrom(buffer))
+                .setArg8(GLEnum.GL_FLOAT.value).build();
+    }
+
+    // void glEnableVertexAttribArray(GLuint index)
+    public void glEnableVertexAttribArray(Message msg) {
+        attribPointers[msg.getArg0()].enabled = true;
+    }
+
+    // void API_ENTRY(glGenBuffers)(GLsizei n, GLuint:n:out buffers)
+    public void glGenBuffers(Message msg) {
+        final int n = msg.getArg0();
+        final ByteBuffer buffer = msg.getData().asReadOnlyByteBuffer();
+        for (int i = 0; i < n; i++) {
+            int name = Integer.reverseBytes(buffer.getInt());
+            if (!buffers.containsKey(name))
+                buffers.put(name, new GLBuffer());
+        }
+    }
+
+    // void glVertexAttribPointer(GLuint index, GLint size, GLenum type,
+    // GLboolean normalized, GLsizei stride, const GLvoid* ptr)
+    public void glVertexAttribPointer(Message msg) {
+        GLAttribPointer attrib = attribPointers[msg.getArg0()];
+        attrib.size = msg.getArg1();
+        attrib.type = GLEnum.valueOf(msg.getArg2());
+        attrib.normalized = msg.getArg3() != 0;
+        attrib.stride = msg.getArg4();
+        if (0 == attrib.stride)
+            attrib.stride = attrib.size * 4;
+        attrib.ptr = msg.getArg5();
+        attrib.buffer = attribBuffer;
+    }
+
+    // void glVertexAttrib1f(GLuint indx, GLfloat x)
+    public void glVertexAttrib1f(Message msg) {
+        glVertexAttrib4f(msg.getArg0(), Float.intBitsToFloat(Integer.reverseBytes(msg.getArg1())),
+                0, 0, 1);
+    }
+
+    // void glVertexAttrib1fv(GLuint indx, const GLfloat* values)
+    public void glVertexAttrib1fv(Message msg) {
+        final ByteBuffer values = msg.getData().asReadOnlyByteBuffer();
+        glVertexAttrib4f(msg.getArg0(),
+                Float.intBitsToFloat(Integer.reverseBytes(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);
+    }
+
+    // void glVertexAttrib2fv(GLuint indx, const GLfloat* values)
+    public void glVertexAttrib2fv(Message msg) {
+        final ByteBuffer values = msg.getData().asReadOnlyByteBuffer();
+        glVertexAttrib4f(msg.getArg0(),
+                Float.intBitsToFloat(Integer.reverseBytes(values.getInt())),
+                Float.intBitsToFloat(Integer.reverseBytes(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);
+    }
+
+    // void glVertexAttrib3fv(GLuint indx, const GLfloat* values)
+    public void glVertexAttrib3fv(Message msg) {
+        final ByteBuffer values = msg.getData().asReadOnlyByteBuffer();
+        glVertexAttrib4f(msg.getArg0(),
+                Float.intBitsToFloat(Integer.reverseBytes(values.getInt())),
+                Float.intBitsToFloat(Integer.reverseBytes(values.getInt())),
+                Float.intBitsToFloat(Integer.reverseBytes(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())));
+    }
+
+    void glVertexAttrib4f(int indx, float x, float y, float z, float w) {
+        defaultAttribs[indx][0] = x;
+        defaultAttribs[indx][1] = y;
+        defaultAttribs[indx][2] = z;
+        defaultAttribs[indx][3] = w;
+    }
+
+    // void glVertexAttrib4fv(GLuint indx, const GLfloat* values)
+    public void glVertexAttrib4fv(Message msg) {
+        final ByteBuffer values = msg.getData().asReadOnlyByteBuffer();
+        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())));
+    }
+}
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/MessageData.java b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageData.java
index 729592e..110c6f8 100644
--- a/tools/glesv2debugger/src/com/android/glesv2debugger/MessageData.java
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageData.java
@@ -25,8 +25,10 @@
     public Image image; // texture
     public String shader; // shader source
     public String[] columns;
-    public float[] data; // vertex attributes
-
+    public float[] data;
+    public int maxAttrib; // used for formatting data
+    public GLEnum dataType; // could be float, int; mainly for formatting use
+    
     public MessageData(final Device device, final DebuggerMessage.Message msg) {
         image = null;
         shader = null;
@@ -46,11 +48,13 @@
         columns[2] = Integer.toHexString(msg.getContextId());
         columns[3] = MessageFormatter.Format(msg);
         switch (function) {
-            case glBufferData:
-                data = MessageProcessor.ReceiveData(msg.getArg0(), msg.getData());
-                break;
-            case glBufferSubData:
-                data = MessageProcessor.ReceiveData(msg.getArg0(), msg.getData());
+            case glDrawArrays: // msg was modified by GLServerVertex
+            case glDrawElements:
+                if (!msg.hasArg8() || !msg.hasData())
+                    break;
+                dataType = GLEnum.valueOf(msg.getArg8());
+                maxAttrib = msg.getArg7();
+                data = MessageProcessor.ReceiveData(dataType, msg.getData());
                 break;
             case glShaderSource:
                 shader = msg.getData().toStringUtf8();
@@ -76,6 +80,14 @@
                     break;
                 image = new Image(device, imageData);
                 break;
+            case glCopyTexImage2D:
+                imageData = MessageProcessor.ReceiveImage(msg.getArg5(), msg.getArg6(), GLEnum.GL_RGBA.value, GLEnum.GL_UNSIGNED_BYTE.value, msg.getData().toByteArray());
+                image = new Image(device, imageData);
+                break;
+            case glCopyTexSubImage2D:
+                imageData = MessageProcessor.ReceiveImage(msg.getArg6(), msg.getArg7(), GLEnum.GL_RGBA.value, GLEnum.GL_UNSIGNED_BYTE.value, msg.getData().toByteArray());
+                image = new Image(device, imageData);
+                break;
             case glReadPixels:
                 if (!msg.hasData())
                     break;
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/MessageProcessor.java b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageProcessor.java
index 0c4a005..da6a9b8 100644
--- a/tools/glesv2debugger/src/com/android/glesv2debugger/MessageProcessor.java
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageProcessor.java
@@ -126,23 +126,25 @@
         return new ImageData(width, height, bpp, palette, 1, data);
     }
 
-    static public float[] ReceiveData(int target, final ByteString data) {
-        ByteBuffer buffer = data.asReadOnlyByteBuffer();
-        GLEnum type = GLEnum.valueOf(target);
-        if (type == GLEnum.GL_ARRAY_BUFFER) {
+    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];
-            buffer.asFloatBuffer().get(elements);
+            for (int i = 0; i < elements.length; i++)
+                elements[i] = buffer.getFloat();
             return elements;
-        } else if (type == GLEnum.GL_ELEMENT_ARRAY_BUFFER) {
-            // usually unsigned short
+        } else if (type == GLEnum.GL_UNSIGNED_SHORT) {
             float[] elements = new float[buffer.remaining() / 2];
-            for (int i = 0; i < elements.length; i++) {
-                int bits = Short.reverseBytes(buffer.getShort()) & 0xffff;
-                elements[i] = bits;
-            }
+            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
-            return null;
-
+            assert false;
+        return null;
     }
 }
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/MessageQueue.java b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageQueue.java
index 9f91d40..afa7e91 100644
--- a/tools/glesv2debugger/src/com/android/glesv2debugger/MessageQueue.java
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageQueue.java
@@ -16,13 +16,12 @@
 
 package com.android.glesv2debugger;
 
-import com.android.glesv2debugger.DebuggerMessage.Message.Function;
 import com.android.glesv2debugger.DebuggerMessage.Message.Type;
 
-import java.io.EOFException;
-import java.io.IOException;
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
 import java.net.Socket;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -35,6 +34,8 @@
     ArrayList<DebuggerMessage.Message> commands = new ArrayList<DebuggerMessage.Message>();
     SampleView sampleView;
 
+    HashMap<Integer, GLServerVertex> serversVertex = new HashMap<Integer, GLServerVertex>();
+
     public MessageQueue(SampleView sampleView) {
         this.sampleView = sampleView;
     }
@@ -95,19 +96,20 @@
             Error(e);
         }
 
-        try {
-            while (running) {
-                DebuggerMessage.Message msg = null;
-                if (incoming.size() > 0) { // find queued incoming
-                    for (ArrayList<DebuggerMessage.Message> messages : incoming
+        // try {
+        while (running) {
+            DebuggerMessage.Message msg = null;
+            if (incoming.size() > 0) { // find queued incoming
+                for (ArrayList<DebuggerMessage.Message> messages : incoming
                             .values())
-                        if (messages.size() > 0) {
-                            msg = messages.get(0);
-                            messages.remove(0);
-                            break;
-                        }
-                }
-                if (null == msg) { // get incoming from network
+                    if (messages.size() > 0) {
+                        msg = messages.get(0);
+                        messages.remove(0);
+                        break;
+                    }
+            }
+            if (null == msg) { // get incoming from network
+                try {
                     msg = ReadMessage(dis);
                     if (msg.getExpectResponse()) {
                         if (msg.getType() == Type.BeforeCall)
@@ -119,28 +121,34 @@
                         // SendResponse(dos, msg.getContextId(),
                         // DebuggerMessage.Message.Function.SKIP);
                         else if (msg.getType() == Type.Response)
-                            ;
+                            assert true;
                         else
                             assert false;
                     }
+                } catch (IOException e) {
+                    Error(e);
+                    running = false;
+                    break;
                 }
+            }
 
-                int contextId = msg.getContextId();
-                if (!incoming.containsKey(contextId))
-                    incoming.put(contextId,
+            int contextId = msg.getContextId();
+            if (!incoming.containsKey(contextId))
+                incoming.put(contextId,
                             new ArrayList<DebuggerMessage.Message>());
 
-                // FIXME: the expected sequence will change for interactive mode
-                while (msg.getType() == Type.BeforeCall) {
-                    DebuggerMessage.Message next = null;
-                    // get existing message part for this context
-                    ArrayList<DebuggerMessage.Message> messages = incoming
+            // FIXME: the expected sequence will change for interactive mode
+            while (msg.getType() == Type.BeforeCall) {
+                DebuggerMessage.Message next = null;
+                // get existing message part for this context
+                ArrayList<DebuggerMessage.Message> messages = incoming
                             .get(contextId);
-                    if (messages.size() > 0) {
-                        next = messages.get(0);
-                        messages.remove(0);
-                    }
-                    if (null == next) { // read new part for message
+                if (messages.size() > 0) {
+                    next = messages.get(0);
+                    messages.remove(0);
+                }
+                if (null == next) { // read new part for message
+                    try {
                         next = ReadMessage(dis);
 
                         if (next.getExpectResponse()) {
@@ -150,45 +158,123 @@
                                         next.getContextId(),
                                         DebuggerMessage.Message.Function.CONTINUE);
                             else if (next.getType() == Type.AfterCall)
-                                SendCommands(dos, 0); // FIXME: proper context id
+                                SendCommands(dos, 0); // FIXME: proper context
+                                                      // id
                             else if (msg.getType() == Type.Response)
-                                ;
+                                assert true;
                             else
                                 assert false;
                         }
-
-                        if (next.getContextId() != contextId) {
-                            // message part not for this context
-                            if (!incoming.containsKey(next.getContextId()))
-                                incoming.put(
-                                        next.getContextId(),
-                                        new ArrayList<DebuggerMessage.Message>());
-                            incoming.get(next.getContextId()).add(next);
-                            continue;
-                        }
+                    } catch (IOException e) {
+                        Error(e);
+                        running = false;
+                        break;
                     }
 
-                    DebuggerMessage.Message.Builder builder = msg.toBuilder();
-                    // builder.mergeFrom(next); seems to merge incorrectly
-                    if (next.hasRet())
-                        builder.setRet(next.getRet());
-                    if (next.hasTime())
-                        builder.setTime(next.getTime());
-                    if (next.hasData())
-                        builder.setData(next.getData());
-                    builder.setType(next.getType());
-                    msg = builder.build();
+                    if (next.getContextId() != contextId) {
+                        // message part not for this context
+                        if (!incoming.containsKey(next.getContextId()))
+                            incoming.put(
+                                        next.getContextId(),
+                                        new ArrayList<DebuggerMessage.Message>());
+                        incoming.get(next.getContextId()).add(next);
+                        continue;
+                    }
                 }
 
-                synchronized (complete) {
-                    complete.add(msg);
-                }
+                DebuggerMessage.Message.Builder builder = msg.toBuilder();
+                // builder.mergeFrom(next); seems to merge incorrectly
+                if (next.hasRet())
+                    builder.setRet(next.getRet());
+                if (next.hasTime())
+                    builder.setTime(next.getTime());
+                if (next.hasData())
+                    builder.setData(next.getData());
+                builder.setType(next.getType());
+                msg = builder.build();
             }
+
+            GLServerVertex serverVertex = serversVertex.get(msg.getContextId());
+            if (null == serverVertex) {
+                serverVertex = new GLServerVertex();
+                serversVertex.put(msg.getContextId(), serverVertex);
+            }
+
+            // forward message to synchronize state
+            switch (msg.getFunction()) {
+                case glBindBuffer:
+                    serverVertex.glBindBuffer(msg);
+                    break;
+                case glBufferData:
+                    serverVertex.glBufferData(msg);
+                    break;
+                case glBufferSubData:
+                    serverVertex.glBufferSubData(msg);
+                    break;
+                case glDeleteBuffers:
+                    serverVertex.glDeleteBuffers(msg);
+                    break;
+                case glDrawArrays:
+                    if (msg.hasArg7())
+                        msg = serverVertex.glDrawArrays(msg);
+                    break;
+                case glDrawElements:
+                    if (msg.hasArg7())
+                        msg = serverVertex.glDrawElements(msg);
+                    break;
+                case glDisableVertexAttribArray:
+                    serverVertex.glDisableVertexAttribArray(msg);
+                    break;
+                case glEnableVertexAttribArray:
+                    serverVertex.glEnableVertexAttribArray(msg);
+                    break;
+                case glGenBuffers:
+                    serverVertex.glGenBuffers(msg);
+                    break;
+                case glVertexAttribPointer:
+                    serverVertex.glVertexAttribPointer(msg);
+                    break;
+                case glVertexAttrib1f:
+                    serverVertex.glVertexAttrib1f(msg);
+                    break;
+                case glVertexAttrib1fv:
+                    serverVertex.glVertexAttrib1fv(msg);
+                    break;
+                case glVertexAttrib2f:
+                    serverVertex.glVertexAttrib2f(msg);
+                    break;
+                case glVertexAttrib2fv:
+                    serverVertex.glVertexAttrib2fv(msg);
+                    break;
+                case glVertexAttrib3f:
+                    serverVertex.glVertexAttrib3f(msg);
+                    break;
+                case glVertexAttrib3fv:
+                    serverVertex.glVertexAttrib3fv(msg);
+                    break;
+                case glVertexAttrib4f:
+                    serverVertex.glVertexAttrib4f(msg);
+                    break;
+                case glVertexAttrib4fv:
+                    serverVertex.glVertexAttrib4fv(msg);
+                    break;
+            }
+
+            synchronized (complete) {
+                complete.add(msg);
+            }
+        }
+
+        try {
             socket.close();
-        } catch (Exception e) {
+        } catch (IOException e) {
             Error(e);
             running = false;
         }
+        // } catch (Exception e) {
+        // Error(e);
+        // running = false;
+        // }
     }
 
     public DebuggerMessage.Message RemoveMessage(int contextId) {
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/SampleView.java b/tools/glesv2debugger/src/com/android/glesv2debugger/SampleView.java
index 05fe51b..41a0bb2 100755
--- a/tools/glesv2debugger/src/com/android/glesv2debugger/SampleView.java
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/SampleView.java
@@ -42,13 +42,16 @@
 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.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.FillLayout;
 import org.eclipse.swt.widgets.Canvas;
 import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Event;
 import org.eclipse.swt.widgets.Listener;
 import org.eclipse.swt.widgets.Menu;
@@ -56,6 +59,7 @@
 import org.eclipse.swt.widgets.Shell;
 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;
@@ -92,6 +96,7 @@
 
     TableViewer viewer;
     org.eclipse.swt.widgets.Canvas canvas;
+    Text text;
     Action actionConnect; // connect / disconnect
     Action doubleClickAction;
     Action actionAutoScroll;
@@ -131,7 +136,6 @@
 
         @Override
         public void inputChanged(Viewer v, Object oldInput, Object newInput) {
-            // showMessage("ViewContentProvider::inputChanged");
         }
 
         @Override
@@ -251,8 +255,35 @@
         hookSelectionChanged();
         contributeToActionBars();
 
-        canvas = new Canvas(parent, SWT.NO_BACKGROUND | SWT.NO_REDRAW_RESIZE
+        class LayoutComposite extends Composite {
+            public LayoutComposite(Composite parent, int style) {
+                super(parent, style);
+            }
+
+            @Override
+            public Control[] getChildren() {
+                Control[] children = super.getChildren();
+                ArrayList<Control> controls = new ArrayList<Control>();
+                for (int i = 0; i < children.length; i++)
+                    if (children[i].isVisible())
+                        controls.add(children[i]);
+                children = new Control[controls.size()];
+                return controls.toArray(children);
+            }
+
+        }
+
+        LayoutComposite layoutComposite = new LayoutComposite(parent, 0);
+        layoutComposite.setLayout(new FillLayout());
+
+        text = new Text(layoutComposite, SWT.NO_BACKGROUND | SWT.READ_ONLY
                 | SWT.V_SCROLL | SWT.H_SCROLL);
+        text.setVisible(false);
+
+        canvas = new Canvas(layoutComposite, SWT.NO_BACKGROUND | SWT.NO_REDRAW_RESIZE
+                | SWT.V_SCROLL | SWT.H_SCROLL);
+        canvas.setVisible(false);
+
         final ScrollBar hBar = canvas.getHorizontalBar();
         hBar.addListener(SWT.Selection, new Listener() {
             @Override
@@ -383,7 +414,7 @@
                         shell, "Contains Filter",
                         "case sensitive substring or *",
                         actionFilter.getText(), null);
-                if (dialog.OK == dialog.open()) {
+                if (Window.OK == dialog.open()) {
                     actionFilter.setText(dialog.getValue());
                     manager.update(true);
                     filters = dialog.getValue().split("\\|");
@@ -485,9 +516,13 @@
                     String str = "";
                     for (int i = 0; i < msgData.data.length; i++)
                     {
-                        str += str.format("%.2f", msgData.data[i]);
+                        str += String.format("%f", msgData.data[i]);
+                        if (i % (4 * msgData.maxAttrib) == (4 * msgData.maxAttrib - 1))
+                            str += '\n';
+                        else if (i % 4 == 3)
+                            str += " -";
                         if (i < msgData.data.length - 1)
-                            str += ", ";
+                            str += ' ';
                     }
                     showMessage(str);
                 }
@@ -515,12 +550,57 @@
                 if (null == selection)
                     return;
                 if (1 != selection.size())
+                {
+                    Object[] objects = selection.toArray();
+                    float totalTime = 0;
+                    for (int i = 0; i < objects.length; i++)
+                    {
+                        MessageData msgData = (MessageData) objects[i];
+                        if (null == msgData)
+                            continue;
+                        totalTime += Float.parseFloat(msgData.columns[1]);
+                    }
+                    viewer.getTable().getColumn(1).setText(Float.toString(totalTime));
                     return;
+                }
+                else
+                    viewer.getTable().getColumn(1).setText("Elapsed (ms)");
                 MessageData msgData = (MessageData) selection.getFirstElement();
                 if (null == msgData)
                     return;
                 if (null != msgData.image)
+                {
+                    text.setVisible(false);
+                    canvas.setVisible(true);
                     canvas.setBackgroundImage(msgData.image);
+                    canvas.getParent().layout();
+                }
+                else if (null != msgData.shader)
+                {
+                    text.setText(msgData.shader);
+                    text.setVisible(true);
+                    canvas.setVisible(false);
+                    text.getParent().layout();
+                }
+                else if (null != msgData.data)
+                {
+                    String str = "";
+                    for (int i = 0; i < msgData.data.length; i++)
+                    {
+                        str += String.format("%.3g", msgData.data[i]);
+                        if (i % (4 * msgData.maxAttrib) == (4 * msgData.maxAttrib - 1))
+                            str += '\n';
+                        else if (i % 4 == 3)
+                            str += " -";
+                        if (i < msgData.data.length - 1)
+                            str += ' ';
+                    }
+
+                    text.setText(str);
+                    text.setVisible(true);
+                    canvas.setVisible(false);
+                    text.getParent().layout();
+                }
             }
 
         });