GLES2Dbg: cache Messages to RandomAccessFile

-Load and format Message when the frame is selected to save memory.

Change-Id: I4ff9edf049dc724a73d6643bde1d53ec8b625114
Signed-off-by: David Li <davidxli@google.com>
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/CodeGen.java b/tools/glesv2debugger/src/com/android/glesv2debugger/CodeGen.java
index a8503cd..7f4bcef 100644
--- a/tools/glesv2debugger/src/com/android/glesv2debugger/CodeGen.java
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/CodeGen.java
@@ -20,13 +20,19 @@
 import com.android.glesv2debugger.DebuggerMessage.Message.Function;
 import com.android.sdklib.util.SparseIntArray;
 
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.dialogs.ProgressMonitorDialog;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.swt.widgets.Shell;
+
 import java.io.FileOutputStream;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.lang.reflect.InvocationTargetException;
 import java.nio.ByteBuffer;
 
-public class CodeGen {
+public class CodeGen implements IRunnableWithProgress {
     private FileWriter codeFile, makeFile, namesHeaderFile, namesSourceFile;
     private PrintWriter code, make, namesHeader, namesSource;
     private FileOutputStream dataOut;
@@ -65,7 +71,7 @@
                     code.write(";CHKERR;\n");
                     return true;
                 }
-                assert msg.getArg2() == msg.getPixelFormat(); // TODO
+                // FIXME: check the texture format & type, and convert
                 s = "//" + MessageFormatter.Format(msg, true) + "\n";
                 s += String.format(
                         "glTexSubImage2D(%s, %d, %d, %d, %d, %d, %s, %s, texData);CHKERR;",
@@ -710,7 +716,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");
@@ -767,9 +773,16 @@
         renderbufferNames = null;
     }
 
-    void CodeGenFrames(final DebugContext dbgCtx, int count) {
-        Context ctx = dbgCtx.frames.get(0).startContext.clone();
+    private DebugContext dbgCtx;
+    private int count;
+    private IProgressMonitor progress;
+    @Override
+    public void run(IProgressMonitor monitor) throws InvocationTargetException,
+            InterruptedException {
+        progress.beginTask("CodeGenFrames", count + 2);
+        Context ctx = dbgCtx.GetFrame(0).startContext.clone();
         CodeGenSetup(ctx);
+        progress.worked(1);
         for (int i = 0; i < count; i++) {
             try {
                 codeFile = new FileWriter("frame" + i + ".cpp", false);
@@ -782,9 +795,9 @@
 
             code.write("#include \"frame_names.h\"\n");
             code.format("void Frame%d(){\n", i);
-            final Frame frame = dbgCtx.frames.get(i);
-            for (int j = 0; j < frame.calls.size(); j++) {
-                final MessageData msgData = frame.calls.get(j);
+            final Frame frame = dbgCtx.GetFrame(i);
+            for (int j = 0; j < frame.Size(); j++) {
+                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);
@@ -802,6 +815,7 @@
                 e.printStackTrace();
                 assert false;
             }
+            progress.worked(1);
         }
         for (int i = 0; i < count; i++)
             namesHeader.format("void Frame%d();\n", i);
@@ -813,6 +827,25 @@
         namesSource.write("};\n");
         namesSource.format("const unsigned int FrameCount = %d;\n", count);
         CodeGenCleanup();
+        progress.worked(1);
+    }
+
+    void CodeGenFrames(final DebugContext dbgCtx, int count, final Shell shell) {
+        this.dbgCtx = dbgCtx;
+        this.count = count;
+        ProgressMonitorDialog dialog = new ProgressMonitorDialog(shell);
+        this.progress = dialog.getProgressMonitor();
+        try {
+            dialog.run(false, true, this);
+        } catch (InvocationTargetException e) {
+            e.printStackTrace();
+            assert false;
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        this.dbgCtx = null;
+        this.count = 0;
+        progress = null;
     }
 
     void CodeGenFrame(final Frame frame) {
@@ -828,8 +861,8 @@
         make.format("    frame0.cpp \\\n");
         code.write("#include \"frame_names.h\"\n");
         code.format("void Frame0(){\n");
-        for (int i = 0; i < frame.calls.size(); i++) {
-            final MessageData msgData = frame.calls.get(i);
+        for (int i = 0; i < frame.Size(); i++) {
+            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);
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/Context.java b/tools/glesv2debugger/src/com/android/glesv2debugger/Context.java
index 15b2da6..23160c4 100644
--- a/tools/glesv2debugger/src/com/android/glesv2debugger/Context.java
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/Context.java
@@ -30,59 +30,188 @@
 import org.eclipse.swt.graphics.Image;
 import org.eclipse.swt.widgets.Display;
 
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
 import java.lang.reflect.Array;
 import java.lang.reflect.Field;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Map;
 import java.util.Set;
 
 class Frame {
-    final Context startContext;
-    ArrayList<MessageData> calls = new ArrayList<MessageData>();
+    public final long filePosition;
+    private int callsCount;
 
-    Frame(final Context context) {
+    final Context startContext;
+    private ArrayList<MessageData> calls = new ArrayList<MessageData>();
+
+    Frame(final Context context, final long filePosition) {
         this.startContext = context.clone();
+        this.filePosition = filePosition;
+    }
+
+    void Add(final MessageData msgData) {
+        calls.add(msgData);
+    }
+
+    void IncreaseCallsCount() {
+        callsCount++;
+    }
+
+    Context ComputeContext(final MessageData call) {
+        Context ctx = startContext.clone();
+        for (int i = 0; i < calls.size(); i++)
+            if (call == calls.get(i))
+                return ctx;
+            else
+                ctx.ProcessMessage(calls.get(i).oriMsg);
+        assert false;
+        return ctx;
+    }
+
+    int Size() {
+        return callsCount;
+    }
+
+    MessageData Get(final int i) {
+        return calls.get(i);
+    }
+
+    ArrayList<MessageData> Get() {
+        return calls;
+    }
+
+    void Unload() {
+        if (calls == null)
+            return;
+        calls.clear();
+        calls = null;
+    }
+
+    void Load(final RandomAccessFile file) {
+        if (calls != null && calls.size() == callsCount)
+            return;
+        try {
+            Context ctx = startContext.clone();
+            calls = new ArrayList<MessageData>(callsCount);
+            final long oriPosition = file.getFilePointer();
+            file.seek(filePosition);
+            for (int i = 0; i < callsCount; i++) {
+                int len = file.readInt();
+                if (SampleView.targetByteOrder == ByteOrder.LITTLE_ENDIAN)
+                    len = Integer.reverseBytes(len);
+                final byte[] data = new byte[len];
+                file.read(data);
+                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;
+                calls.add(msgData);
+            }
+            file.seek(oriPosition);
+        } catch (IOException e) {
+            e.printStackTrace();
+            assert false;
+        }
     }
 }
 
 class DebugContext {
+    boolean uiUpdate = false;
     final int contextId;
     Context currentContext;
-    ArrayList<Frame> frames = new ArrayList<Frame>(128);
-    private Frame currentFrame;
+    private ArrayList<Frame> frames = new ArrayList<Frame>(128);
+    private Frame lastFrame;
+    private Frame loadedFrame;
+    private RandomAccessFile file;
 
     DebugContext(final int contextId) {
         this.contextId = contextId;
         currentContext = new Context(contextId);
-        frames.add(new Frame(currentContext));
-        currentFrame = frames.get(0);
+        try {
+            file = new RandomAccessFile(Integer.toHexString(contextId) + ".gles2dbg",
+                    "rw");
+            frames.add(new Frame(currentContext, file.getFilePointer()));
+
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+            assert false;
+        } catch (IOException e) {
+            e.printStackTrace();
+            assert false;
+        }
+        lastFrame = frames.get(0);
+        loadedFrame = lastFrame;
     }
 
-    MessageData ProcessMessage(final Message oriMsg) {
-        currentContext.ProcessMessage(oriMsg);
-        Message msg = oriMsg;
-        if (currentContext.processed != null)
-            msg = currentContext.processed;
-        currentContext.processed = null;
-        MessageData msgData = new MessageData(Display.getCurrent(), msg, oriMsg, currentContext);
-        msgData.attribs = currentContext.serverVertex.fetchedAttribs;
-        currentFrame.calls.add(msgData);
+    /** Writes oriMsg to file, and formats into MessageData for current frame */
+    void ProcessMessage(final Message oriMsg) {
+        synchronized (file) {
+            final byte[] data = oriMsg.toByteArray();
+            final ByteBuffer len = ByteBuffer.allocate(4);
+            len.order(SampleView.targetByteOrder);
+            len.putInt(data.length);
+            try {
+                if (SampleView.targetByteOrder == ByteOrder.BIG_ENDIAN)
+                    file.writeInt(data.length);
+                else
+                    file.writeInt(Integer.reverseBytes(data.length));
+                file.write(data);
+            } catch (IOException e) {
+                e.printStackTrace();
+                assert false;
+            }
+        }
+
+        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;
+            lastFrame.Add(msgData);
+            uiUpdate = true;
+        }
         if (msg.getFunction() != Function.eglSwapBuffers)
-            return msgData;
-        frames.add(currentFrame = new Frame(currentContext));
-        return msgData;
+            return;
+        synchronized (frames) {
+            if (loadedFrame != lastFrame)
+                lastFrame.Unload();
+            try {
+                frames.add(lastFrame = new Frame(currentContext, file.getFilePointer()));
+                // file.getChannel().force(false);
+                uiUpdate = true;
+            } catch (IOException e) {
+                e.printStackTrace();
+                assert false;
+            }
+        }
+        return;
     }
 
-    Context ComputeContext(final Frame frame, final MessageData call) {
-        Context ctx = frame.startContext.clone();
-        for (int i = 0; i < frame.calls.size(); i++)
-            if (call == frame.calls.get(i))
-                return ctx;
-            else
-                ctx.ProcessMessage(frame.calls.get(i).oriMsg);
-        assert false;
-        return ctx;
+    Frame GetFrame(int index) {
+        synchronized (frames) {
+            Frame newFrame = frames.get(index);
+            if (loadedFrame != null && loadedFrame != lastFrame && newFrame != loadedFrame) {
+                loadedFrame.Unload();
+                uiUpdate = true;
+            }
+            loadedFrame = newFrame;
+            synchronized (file) {
+                loadedFrame.Load(file);
+            }
+            return loadedFrame;
+        }
+    }
+
+    int FrameCount() {
+        synchronized (frames) {
+            return frames.size();
+        }
     }
 }
 
@@ -97,8 +226,6 @@
 
     byte[] readPixelRef = new byte[0];
 
-    Message processed = null; // return; processed Message
-
     public Context(int contextId) {
         this.contextId = contextId;
         shares.add(this);
@@ -115,6 +242,7 @@
             copy.serverShader = serverShader.clone(copy);
             copy.serverState = serverState.clone();
             copy.serverTexture = serverTexture.clone(copy);
+            copy.readPixelRef = readPixelRef.clone();
             return copy;
         } catch (CloneNotSupportedException e) {
             e.printStackTrace();
@@ -123,17 +251,21 @@
         }
     }
 
-    public void ProcessMessage(Message msg) {
+    /** returns processed Message, which could be a new Message */
+    public Message ProcessMessage(Message msg) {
         if (serverVertex.Process(msg)) {
-            processed = serverVertex.processed;
-            return;
+            if (serverVertex.processed != null)
+                return serverVertex.processed;
+            else
+                return msg;
         }
         if (serverShader.ProcessMessage(msg))
-            return;
+            return msg;
         if (serverState.ProcessMessage(msg))
-            return;
+            return msg;
         if (serverTexture.ProcessMessage(msg))
-            return;
+            return msg;
+        return msg;
     }
 }
 
@@ -175,15 +307,16 @@
         if (!(entry.obj instanceof Message))
             return null;
         final Message msg = (Message) entry.obj;
-        for (int i = 0; i <= sampleView.frameNum.getSelection(); i++) {
-            if (i == sampleView.current.frames.size())
+        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
+            default:
                 return null;
-            final Frame frame = sampleView.current.frames.get(i);
-            for (final MessageData msgData : frame.calls)
-                if (msgData.oriMsg == msg)
-                    return entry.image = msgData.image;
         }
-        return null;
     }
 
     @Override
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/DebuggerMessage.java b/tools/glesv2debugger/src/com/android/glesv2debugger/DebuggerMessage.java
index 9b8c610..d871afb 100644
--- a/tools/glesv2debugger/src/com/android/glesv2debugger/DebuggerMessage.java
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/DebuggerMessage.java
@@ -448,6 +448,7 @@
       AfterCall(1, 1),
       AfterGeneratedCall(2, 2),
       Response(3, 3),
+      CompleteCall(4, 4),
       ;
       
       
@@ -459,6 +460,7 @@
           case 1: return AfterCall;
           case 2: return AfterGeneratedCall;
           case 3: return Response;
+          case 4: return CompleteCall;
           default: return null;
         }
       }
@@ -490,10 +492,10 @@
       ReferencedImage(0, 0),
       NonreferencedImage(1, 1),
       ;
-
-
+      
+      
       public final int getNumber() { return value; }
-
+      
       public static DataType valueOf(int value) {
         switch (value) {
           case 0: return ReferencedImage;
@@ -501,7 +503,7 @@
           default: return null;
         }
       }
-
+      
       public static com.google.protobuf.Internal.EnumLiteMap<DataType>
           internalGetValueMap() {
         return internalValueMap;
@@ -513,17 +515,17 @@
                 return DataType.valueOf(number)
       ;        }
             };
-
+      
       private final int index;
       private final int value;
       private DataType(int index, int value) {
         this.index = index;
         this.value = value;
       }
-
+      
       // @@protoc_insertion_point(enum_scope:com.android.glesv2debugger.Message.DataType)
     }
-
+    
     public enum Prop
         implements com.google.protobuf.Internal.EnumLite {
       Capture(0, 0),
@@ -676,21 +678,21 @@
     private com.android.glesv2debugger.DebuggerMessage.Message.DataType dataType_;
     public boolean hasDataType() { return hasDataType; }
     public com.android.glesv2debugger.DebuggerMessage.Message.DataType getDataType() { return dataType_; }
-
+    
     // optional int32 pixel_format = 24;
     public static final int PIXEL_FORMAT_FIELD_NUMBER = 24;
     private boolean hasPixelFormat;
     private int pixelFormat_ = 0;
     public boolean hasPixelFormat() { return hasPixelFormat; }
     public int getPixelFormat() { return pixelFormat_; }
-
+    
     // optional int32 pixel_type = 25;
     public static final int PIXEL_TYPE_FIELD_NUMBER = 25;
     private boolean hasPixelType;
     private int pixelType_ = 0;
     public boolean hasPixelType() { return hasPixelType; }
     public int getPixelType() { return pixelType_; }
-
+    
     // optional float time = 11;
     public static final int TIME_FIELD_NUMBER = 11;
     private boolean hasTime;
@@ -1513,7 +1515,7 @@
         result.dataType_ = com.android.glesv2debugger.DebuggerMessage.Message.DataType.ReferencedImage;
         return this;
       }
-
+      
       // optional int32 pixel_format = 24;
       public boolean hasPixelFormat() {
         return result.hasPixelFormat();
@@ -1531,7 +1533,7 @@
         result.pixelFormat_ = 0;
         return this;
       }
-
+      
       // optional int32 pixel_type = 25;
       public boolean hasPixelType() {
         return result.hasPixelType();
@@ -1549,7 +1551,7 @@
         result.pixelType_ = 0;
         return this;
       }
-
+      
       // optional float time = 11;
       public boolean hasTime() {
         return result.hasTime();
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/MessageData.java b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageData.java
index d118f6b..1cd14d8 100644
--- a/tools/glesv2debugger/src/com/android/glesv2debugger/MessageData.java
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageData.java
@@ -29,14 +29,13 @@
 
 public class MessageData {
     public final Message msg, oriMsg;
-    public Image image; // texture
-    public String shader; // shader source
+    public Image image = null; // texture
+    public String shader = null; // shader source
     public String text;
     public String[] columns = new String[3];
-    public float[] data;
+    public float[] data = null;
     public int maxAttrib; // used for formatting data
     public GLEnum dataType; // could be float, int; mainly for formatting use
-    Context context; // the context before this call
 
     ByteBuffer[] attribs = null;
 
@@ -44,14 +43,10 @@
             final Context context) {
         this.msg = msg;
         this.oriMsg = oriMsg;
-        this.context = context;
-        image = null;
-        shader = null;
-        data = null;
         StringBuilder builder = new StringBuilder();
         final Function function = msg.getFunction();
         ImageData imageData = null;
-        if (function != Message.Function.ACK)
+        if (function != Message.Function.ACK && msg.getType() != Type.BeforeCall)
             assert msg.hasTime();
         builder.append(columns[0] = function.name());
         while (builder.length() < 30)
@@ -70,7 +65,7 @@
         else if (msg.getType() == Type.AfterGeneratedCall)
             columns[2] = "[AfterGeneratedCall] ";
         else
-            assert msg.getType() == Type.AfterCall;
+            assert msg.getType() == Type.CompleteCall;
         columns[2] += MessageFormatter.Format(msg, false);
         builder.append(columns[2]);
         switch (function) {
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/MessageQueue.java b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageQueue.java
index 09dbc5d..fcd9371 100644
--- a/tools/glesv2debugger/src/com/android/glesv2debugger/MessageQueue.java
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/MessageQueue.java
@@ -19,6 +19,7 @@
 import com.android.glesv2debugger.DebuggerMessage.Message;
 import com.android.glesv2debugger.DebuggerMessage.Message.Function;
 import com.android.glesv2debugger.DebuggerMessage.Message.Type;
+import com.android.sdklib.util.SparseArray;
 
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
@@ -28,17 +29,16 @@
 import java.net.Socket;
 import java.nio.ByteOrder;
 import java.util.ArrayList;
-import java.util.HashMap;
 
 public class MessageQueue implements Runnable {
 
-    boolean running = false;
+    private boolean running = false;
     private ByteOrder byteOrder;
     private FileInputStream file; // if null, create and use socket
-    Thread thread = null;
-    ArrayList<Message> complete = new ArrayList<Message>(); // need synchronized
-    ArrayList<Message> commands = new ArrayList<Message>(); // need synchronized
-    SampleView sampleView;
+    private Thread thread = null;
+    private ArrayList<Message> complete = new ArrayList<Message>(); // synchronized
+    private ArrayList<Message> commands = new ArrayList<Message>(); // synchronized
+    private SampleView sampleView;
 
     public MessageQueue(SampleView sampleView) {
         this.sampleView = sampleView;
@@ -64,7 +64,7 @@
         return running;
     }
 
-    void SendCommands(final int contextId) throws IOException {
+    private void SendCommands(final int contextId) throws IOException {
         synchronized (commands) {
             for (int i = 0; i < commands.size(); i++) {
                 Message command = commands.get(i);
@@ -86,7 +86,7 @@
     // access call chain starts with run()
     private DataInputStream dis = null;
     private DataOutputStream dos = null;
-    private HashMap<Integer, ArrayList<Message>> incoming = new HashMap<Integer, ArrayList<Message>>();
+    private SparseArray<ArrayList<Message>> incoming = new SparseArray<ArrayList<Message>>();
 
     @Override
     public void run() {
@@ -118,11 +118,13 @@
 
             Message msg = null;
             if (incoming.size() > 0) { // find queued incoming
-                for (ArrayList<Message> messages : incoming.values())
+                for (int i = 0; i < incoming.size(); i++) {
+                    final ArrayList<Message> messages = incoming.valueAt(i);
                     if (messages.size() > 0) {
                         msg = messages.remove(0);
                         break;
                     }
+                }
             }
             try {
                 if (null == msg) // get incoming from network
@@ -167,8 +169,8 @@
         SendMessage(dos, msg);
     }
 
-    // should only used by DefaultProcessMessage
-    private HashMap<Integer, Message> partials = new HashMap<Integer, Message>();
+    // should only be used by DefaultProcessMessage
+    private SparseArray<Message> partials = new SparseArray<Message>();
 
     Message GetPartialMessage(final int contextId) {
         return partials.get(contextId);
@@ -176,7 +178,8 @@
 
     // used to add BeforeCall to complete if it was skipped
     void CompletePartialMessage(final int contextId) {
-        final Message msg = partials.remove(contextId);
+        final Message msg = partials.get(contextId);
+        partials.remove(contextId);
         assert msg != null;
         assert msg.getType() == Type.BeforeCall;
         synchronized (complete) {
@@ -189,29 +192,44 @@
             boolean sendResponse)
             throws IOException {
         final int contextId = msg.getContextId();
-        final Message.Builder builder = Message.newBuilder();
-        builder.setContextId(contextId);
-        builder.setType(Type.Response);
-        builder.setExpectResponse(expectResponse);
         if (msg.getType() == Type.BeforeCall) {
             if (sendResponse) {
+                final Message.Builder builder = Message.newBuilder();
+                builder.setContextId(contextId);
+                builder.setType(Type.Response);
+                builder.setExpectResponse(expectResponse);
                 builder.setFunction(Function.CONTINUE);
                 SendMessage(dos, builder.build());
             }
-            assert !partials.containsKey(contextId);
+            assert partials.indexOfKey(contextId) < 0;
             partials.put(contextId, msg);
         } else if (msg.getType() == Type.AfterCall) {
             if (sendResponse) {
-                builder.setFunction(Function.CONTINUE);
+                final Message.Builder builder = Message.newBuilder();
+                builder.setContextId(contextId);
+                builder.setType(Type.Response);
+                builder.setExpectResponse(expectResponse);
+                builder.setFunction(Function.SKIP);
                 SendMessage(dos, builder.build());
             }
-            assert partials.containsKey(contextId);
-            final Message before = partials.remove(contextId);
+            assert partials.indexOfKey(contextId) >= 0;
+            final Message before = partials.get(contextId);
+            partials.remove(contextId);
             assert before.getFunction() == msg.getFunction();
-            final Message completed = before.toBuilder().mergeFrom(msg).build();
+            final Message completed = before.toBuilder().mergeFrom(msg)
+                    .setType(Type.CompleteCall).build();
             synchronized (complete) {
                 complete.add(completed);
             }
+        } else if (msg.getType() == Type.CompleteCall) {
+            // this type should only be encountered on client after processing
+            assert file != null;
+            assert !msg.getExpectResponse();
+            assert !sendResponse;
+            assert partials.indexOfKey(contextId) < 0;
+            synchronized (complete) {
+                complete.add(msg);
+            }
         } else
             assert false;
     }
@@ -265,6 +283,8 @@
 
     private void SendMessage(final DataOutputStream dos, final Message message)
             throws IOException {
+        if (dos == null)
+            return;
         assert message.getFunction() != Function.NEG;
         final byte[] data = message.toByteArray();
         if (byteOrder == ByteOrder.BIG_ENDIAN)
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/SampleView.java b/tools/glesv2debugger/src/com/android/glesv2debugger/SampleView.java
index 66a7dd3..3b267ff 100644
--- a/tools/glesv2debugger/src/com/android/glesv2debugger/SampleView.java
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/SampleView.java
@@ -75,12 +75,7 @@
 
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.PrintWriter;
 import java.nio.ByteOrder;
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
 
 /**
  * This sample class demonstrates how to plug-in a new workbench view. The view
@@ -148,7 +143,7 @@
 
         @Override
         public Object[] getElements(Object parent) {
-            return frame.calls.toArray();
+            return frame.Get().toArray();
         }
 
         @Override
@@ -562,7 +557,11 @@
             public void run()
             {
                 if (current != null)
+                {
                     new CodeGen().CodeGenFrame((Frame) viewer.getInput());
+                    // need to reload current frame
+                    viewer.setInput(current.GetFrame(frameNum.getSelection()));
+                }
             }
         });
 
@@ -572,7 +571,12 @@
             public void run()
             {
                 if (current != null)
-                    new CodeGen().CodeGenFrames(current, frameNum.getSelection() + 1);
+                {
+                    new CodeGen().CodeGenFrames(current, frameNum.getSelection() + 1,
+                            getSite().getShell());
+                    // need to reload current frame
+                    viewer.setInput(current.GetFrame(frameNum.getSelection()));
+                }
             }
         });
     }
@@ -626,8 +630,8 @@
             return;
         if (frameNum.getSelection() == frameNum.getMaximum())
             return; // scale max cannot overlap min, so max is array size
-        final Frame frame = current.frames.get(frameNum.getSelection());
-        final Context context = current.ComputeContext(frame, msgData);
+        final Frame frame = current.GetFrame(frameNum.getSelection());
+        final Context context = frame.ComputeContext(msgData);
         contextViewer.setInput(context);
         if (null != msgData.image) {
             canvas.setBackgroundImage(msgData.image);
@@ -686,46 +690,32 @@
 
     @Override
     public void run() {
-        FileWriter file = null;
-        PrintWriter writer = null;
-        try {
-            file = new FileWriter("GLES2Debugger.log", true);
-            writer = new PrintWriter(file);
-            writer.write("\n\n");
-            writer.write(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Calendar.getInstance()
-                    .getTime()));
-            writer.write("\n\n");
-        } catch (IOException e1) {
-            showError(e1);
-        }
-
         int newMessages = 0;
 
-        boolean shaderEditorUpdate = false, currentUpdate = false;
+        boolean shaderEditorUpdate = false;
         while (running) {
             final Message oriMsg = messageQueue.RemoveCompleteMessage(0);
             if (oriMsg == null && !messageQueue.IsRunning())
                 break;
             if (newMessages > 60 || (newMessages > 0 && null == oriMsg)) {
                 newMessages = 0;
-
-                if (currentUpdate || current == null)
+                if (current == null || current.uiUpdate)
                     getSite().getShell().getDisplay().syncExec(new Runnable() {
                         @Override
                         public void run() {
                             if (current == null)
                                 ChangeContext(debugContexts.valueAt(0));
-                            else if (frameNum.getSelection() == current.frames.size() - 1)
+                            else if (frameNum.getSelection() == current.FrameCount() - 1)
                             {
                                 viewer.refresh(false);
                                 if (actionAutoScroll.isChecked())
                                     viewer.getList().setSelection(
                                             viewer.getList().getItemCount() - 1);
                             }
-                            frameNum.setMaximum(current.frames.size());
+                            frameNum.setMaximum(current.FrameCount());
                         }
                     });
-                currentUpdate = false;
+                current.uiUpdate = false;
 
                 if (shaderEditorUpdate)
                     this.getSite().getShell().getDisplay().syncExec(new Runnable() {
@@ -751,29 +741,15 @@
                 debugContexts.put(oriMsg.getContextId(), debugContext);
             }
 
-            final MessageData msgData = debugContext.ProcessMessage(oriMsg);
-            if (current == debugContext) {
-                currentUpdate = true;
-            }
+            debugContext.ProcessMessage(oriMsg);
 
             shaderEditorUpdate |= debugContext.currentContext.serverShader.uiUpdate;
             debugContext.currentContext.serverShader.uiUpdate = false;
 
-            if (null != writer) {
-                writer.write(msgData.text + "\n");
-                if (msgData.msg.getFunction() == Function.eglSwapBuffers) {
-                    writer.write("\n-------\n");
-                    writer.flush();
-                }
-            }
             newMessages++;
         }
         if (running)
             ConnectDisconnect(); // error occurred, disconnect
-        if (null != writer) {
-            writer.flush();
-            writer.close();
-        }
     }
 
     /** can be called from non-UI thread */
@@ -782,9 +758,9 @@
             @Override
             public void run() {
                 current = newContext;
-                frameNum.setMaximum(current.frames.size());
+                frameNum.setMaximum(current.FrameCount());
                 frameNum.setSelection(0);
-                viewer.setInput(current.frames.get(frameNum.getSelection()));
+                viewer.setInput(current.GetFrame(frameNum.getSelection()));
                 shaderEditor.Update();
                 actContext.setText("Context: 0x" + Integer.toHexString(current.contextId));
                 getViewSite().getActionBars().getToolBarManager().update(true);
@@ -798,9 +774,9 @@
             assert false;
         if (current == null)
             return;
-        if (frameNum.getSelection() == current.frames.size())
+        if (frameNum.getSelection() == current.FrameCount())
             return; // scale maximum cannot overlap minimum
-        Frame frame = current.frames.get(frameNum.getSelection());
+        Frame frame = current.GetFrame(frameNum.getSelection());
         viewer.setInput(frame);
     }
 
diff --git a/tools/glesv2debugger/src/com/android/glesv2debugger/ShaderEditor.java b/tools/glesv2debugger/src/com/android/glesv2debugger/ShaderEditor.java
index 56676e3..dd982f0 100644
--- a/tools/glesv2debugger/src/com/android/glesv2debugger/ShaderEditor.java
+++ b/tools/glesv2debugger/src/com/android/glesv2debugger/ShaderEditor.java
@@ -95,6 +95,7 @@
         gridData.grabExcessHorizontalSpace = true;
         gridData.verticalAlignment = SWT.FILL;
         gridData.grabExcessVerticalSpace = true;
+        gridData.verticalSpan = 2;
         styledText.setLayoutData(gridData);
         styledText.addExtendedModifyListener(this);
     }