gltrace: manage texture image state changes

Texture Data is provided via glTexImage2D. Parts of a created
texture may then be updated using glTexSubImage2D. This patch
adds a GL state variable that maintains a path to the current
texture image for each texture.

This patch also includes a few other misc. changes:
 - Duration minimap: do not create back buffer image of size 0.
   In such a case, just don't draw anything.
 - In the function trace view, selecting a particular item in
   the table doesn't also scroll it into view on Mac. Add
   a setTopIndex to fix this.
 - add Guava is a dependency
 - Apply the state transformations in a separate Eclipse job.

Change-Id: I53f5a0438217d9d086b844f7d333306f7c9fbccd
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/.classpath b/eclipse/plugins/com.android.ide.eclipse.gldebugger/.classpath
index 06be95e..39c4185 100755
--- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/.classpath
+++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/.classpath
@@ -7,5 +7,6 @@
 	<classpathentry kind="lib" path="libs/host-libprotobuf-java-2.3.0-lite.jar"/>
 	<classpathentry kind="lib" path="libs/liblzf.jar"/>
 	<classpathentry kind="lib" path="libs/sdklib.jar"/>
+	<classpathentry kind="lib" path="libs/guava-10.0.1.jar"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/META-INF/MANIFEST.MF b/eclipse/plugins/com.android.ide.eclipse.gldebugger/META-INF/MANIFEST.MF
index feb16bb..fcaceb7 100644
--- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/META-INF/MANIFEST.MF
+++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/META-INF/MANIFEST.MF
@@ -15,6 +15,7 @@
  libs/liblzf.jar,
  libs/sdklib.jar,
  libs/ddmlib.jar,
+ libs/guava-10.0.1.jar,
  .
 Bundle-Vendor: The Android Open Source Project
 Export-Package: com.android.ide.eclipse.gldebugger;x-friends:="com.android.ide.eclipse.gldebugger.tests",
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/build.properties b/eclipse/plugins/com.android.ide.eclipse.gldebugger/build.properties
index 91e1d10..3b4a698 100644
--- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/build.properties
+++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/build.properties
@@ -7,6 +7,5 @@
                lib/host-libprotobuf-java-2.3.0-lite.jar,\

                lib/liblzf.jar,\

                lib/sdklib.jar,\

-               libs/,\

-               libs/ddmlib.jar,\

-               entries.in

+               entries.in,\

+               libs/

diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/FileUtils.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/FileUtils.java
new file mode 100644
index 0000000..ab06f67
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/FileUtils.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2012 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.ide.eclipse.gltrace;
+
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+
+public class FileUtils {
+    private static final File sCacheDir;
+
+    static {
+        sCacheDir = Files.createTempDir();
+        sCacheDir.deleteOnExit();
+    }
+
+    public static File createTempFile(String prefix, String suffix) {
+        File f;
+        try {
+            f = File.createTempFile(prefix, suffix, sCacheDir);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        f.deleteOnExit();
+        return f;
+    }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/TraceFileParserTask.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/TraceFileParserTask.java
index 381d8ce..b3ca918 100644
--- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/TraceFileParserTask.java
+++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/TraceFileParserTask.java
@@ -130,17 +130,12 @@
 
             // counters that maintain some statistics about the trace messages
             long minTraceStartTime = Long.MAX_VALUE;
-            int maxContextId = -1;
 
-            while ((msg = sReader.getMessageAtOffset(mFile, 0)) != null) {
+            while ((msg = sReader.getMessageAtOffset(mFile, -1)) != null) {
                 if (minTraceStartTime > msg.getStartTime()) {
                     minTraceStartTime = msg.getStartTime();
                 }
 
-                if (maxContextId < msg.getContextId()) {
-                    maxContextId = msg.getContextId();
-                }
-
                 addMessage(msgCount, filePointer, msg, msg.getStartTime() - minTraceStartTime);
 
                 filePointer = mFile.getFilePointer();
@@ -151,7 +146,7 @@
                 }
             }
 
-            if (maxContextId > 0) {
+            if (mGLContextIds.size() > 1) {
                 // if there are multiple contexts, then the calls may arrive at the
                 // host out of order. So we perform a sort based on the invocation time.
                 Collections.sort(mGLCalls, new Comparator<GLCall>() {
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/TraceFileReader.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/TraceFileReader.java
index e5121bf..ba7bf67 100644
--- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/TraceFileReader.java
+++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/TraceFileReader.java
@@ -43,7 +43,7 @@
         int len;
         byte[] b;
         try {
-            if (offset != 0) {
+            if (offset != -1) {
                 file.seek(offset);
             }
 
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/DurationMinimap.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/DurationMinimap.java
index 0fd7c42..88a8e31 100644
--- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/DurationMinimap.java
+++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/DurationMinimap.java
@@ -196,6 +196,12 @@
     private void initializeBackBuffer() {
         Rectangle clientArea = getClientArea();
 
+        if (clientArea.width == 0 || clientArea.height == 0) {
+            mBackBufferImage = null;
+            mBackBufferGC = null;
+            return;
+        }
+
         mBackBufferImage = new Image(getDisplay(),
                 clientArea.width,
                 clientArea.height);
@@ -232,6 +238,10 @@
             initializeBackBuffer();
         }
 
+        if (mBackBufferImage == null) {
+            return;
+        }
+
         // draw contents onto the back buffer
         drawBackground(mBackBufferGC, mBackBufferImage.getBounds());
         drawContextHeaders(mBackBufferGC);
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/GLFunctionTraceViewer.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/GLFunctionTraceViewer.java
index d69aeda..bbb668f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/GLFunctionTraceViewer.java
+++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/GLFunctionTraceViewer.java
@@ -346,6 +346,7 @@
             @Override
             public void callSelected(int selectedCallIndex) {
                 table.select(selectedCallIndex);
+                table.setTopIndex(selectedCallIndex);
             }
         });
 
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/StateViewPage.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/StateViewPage.java
index 4a9ad31..40e0a2a 100644
--- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/StateViewPage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/StateViewPage.java
@@ -16,12 +16,17 @@
 
 package com.android.ide.eclipse.gltrace.editors;
 
+import com.android.ide.eclipse.gldebugger.Activator;
 import com.android.ide.eclipse.gltrace.model.GLCall;
 import com.android.ide.eclipse.gltrace.model.GLTrace;
 import com.android.ide.eclipse.gltrace.state.GLState;
 import com.android.ide.eclipse.gltrace.state.IGLProperty;
 import com.android.ide.eclipse.gltrace.state.transforms.IStateTransform;
 
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
 import org.eclipse.jface.viewers.ISelection;
 import org.eclipse.jface.viewers.IStructuredSelection;
 import org.eclipse.jface.viewers.TreeViewer;
@@ -132,20 +137,41 @@
             return;
         }
 
+        final int selectedCallIndex = selectedCall.getIndex();
         if (selectedCall.getIndex() != mCurrentStateIndex) {
-            final Set<IGLProperty> changedProperties = updateState(mCurrentStateIndex,
-                    selectedCall.getIndex());
-            mCurrentStateIndex = selectedCall.getIndex();
-
-            mLabelProvider.setChangedProperties(changedProperties);
-            Display.getDefault().syncExec(new Runnable() {
+            // Creation of texture images takes a few seconds on the first run. So run
+            // the update task as an Eclipse job.
+            Job job = new Job("Updating GL State") {
                 @Override
-                public void run() {
-                    if (!mTreeViewer.getTree().isDisposed()) {
-                        mTreeViewer.refresh();
+                protected IStatus run(IProgressMonitor monitor) {
+                    Set<IGLProperty> changedProperties = null;
+
+                    try {
+                        changedProperties = updateState(mCurrentStateIndex,
+                            selectedCallIndex);
+                    } catch (Exception e) {
+                        return new Status(Status.ERROR,
+                                Activator.PLUGIN_ID,
+                                "Unexpected error while updating GL State.",
+                                e);
                     }
+                    mCurrentStateIndex = selectedCallIndex;
+
+                    mLabelProvider.setChangedProperties(changedProperties);
+                    Display.getDefault().syncExec(new Runnable() {
+                        @Override
+                        public void run() {
+                            if (!mTreeViewer.getTree().isDisposed()) {
+                                mTreeViewer.refresh();
+                            }
+                        }
+                    });
+
+                    return Status.OK_STATUS;
                 }
-            });
+            };
+            job.setPriority(Job.SHORT);
+            job.schedule();
         }
     }
 
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/GLState.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/GLState.java
index 25878e9..d8a3e34 100644
--- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/GLState.java
+++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/GLState.java
@@ -230,8 +230,9 @@
                 GLEnum.GL_INVALID_VALUE);
         IGLProperty imageType = new GLEnumProperty(GLStateType.TEXTURE_IMAGE_TYPE,
                 GLEnum.GL_UNSIGNED_BYTE);
+        IGLProperty image = new GLStringProperty(GLStateType.TEXTURE_IMAGE, null);
         IGLProperty textureDefaultState = new GLCompositeProperty(GLStateType.PER_TEXTURE_STATE,
-                minFilter, magFilter, wrapS, wrapT, format, width, height, imageType);
+                minFilter, magFilter, wrapS, wrapT, format, width, height, imageType, image);
         GLSparseArrayProperty textures = new GLSparseArrayProperty(GLStateType.TEXTURES,
                 textureDefaultState);
         textures.add(0);
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/GLStateType.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/GLStateType.java
index da49a0c..e2c1edf 100644
--- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/GLStateType.java
+++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/GLStateType.java
@@ -112,6 +112,7 @@
                 TEXTURE_WIDTH("Width"),
                 TEXTURE_HEIGHT("Height"),
                 TEXTURE_IMAGE_TYPE("Image Type"),
+                TEXTURE_IMAGE("Image"),
 
     FRAMEBUFFER_STATE("Framebuffer State"),
         FRAMEBUFFER_BINDING("Framebuffer Binding"),
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/GLStringProperty.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/GLStringProperty.java
new file mode 100644
index 0000000..af435af
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/GLStringProperty.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2012 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.ide.eclipse.gltrace.state;
+
+public class GLStringProperty extends GLAbstractAtomicProperty {
+    private final String mDefaultValue;
+    private String mCurrentValue;
+
+    public GLStringProperty(GLStateType type, String defaultValue) {
+        super(type);
+
+        mDefaultValue = defaultValue;
+    }
+
+    @Override
+    public boolean isDefault() {
+        return mDefaultValue.equalsIgnoreCase(mCurrentValue);
+    }
+
+    @Override
+    public void setValue(Object value) {
+        if (value instanceof String) {
+            mCurrentValue = (String) value;
+        } else {
+            throw new IllegalArgumentException("Attempt to set non-string value for " //$NON-NLS-1$
+                    + getType());
+        }
+    }
+
+    public void setValue(String value) {
+        mCurrentValue = value;
+    }
+
+    @Override
+    public Object getValue() {
+        return mCurrentValue;
+    }
+
+    @Override
+    public String getStringValue() {
+        return mCurrentValue;
+    }
+
+    @Override
+    public String toString() {
+        return mCurrentValue;
+    };
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/StateTransformFactory.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/StateTransformFactory.java
index fb19ed8..0c282c4 100644
--- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/StateTransformFactory.java
+++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/StateTransformFactory.java
@@ -17,17 +17,24 @@
 package com.android.ide.eclipse.gltrace.state.transforms;
 
 import com.android.ide.eclipse.gldebugger.GLEnum;
+import com.android.ide.eclipse.gltrace.FileUtils;
 import com.android.ide.eclipse.gltrace.GLProtoBuf.GLMessage;
 import com.android.ide.eclipse.gltrace.state.GLState;
 import com.android.ide.eclipse.gltrace.state.GLStateType;
 import com.android.ide.eclipse.gltrace.state.IGLProperty;
+import com.google.common.io.Files;
+import com.google.protobuf.ByteString;
 
+import java.io.File;
+import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 
 public class StateTransformFactory {
+    private static final String TEXTURE_DATA_FILE_PREFIX = "tex";   //$NON-NLS-1$
+    private static final String TEXTURE_DATA_FILE_SUFFIX = ".dat";  //$NON-NLS-1$
+
     /** Construct a list of transformations to be applied for the provided OpenGL call. */
     public static List<IStateTransform> getTransformsFor(GLMessage msg) {
         switch (msg.getFunction()) {
@@ -694,7 +701,7 @@
      * {@link #transformsForGlTexSubImage2D(GLMessage)}.
      */
     private static List<IStateTransform> transformsForGlTexImage(GLMessage msg, int widthArgIndex,
-            int heightArgIndex) {
+            int heightArgIndex, int xOffsetIndex, int yOffsetIndex) {
         GLEnum target = GLEnum.valueOf(msg.getArgs(0).getIntValue(0));
         Integer width = Integer.valueOf(msg.getArgs(widthArgIndex).getIntValue(0));
         Integer height = Integer.valueOf(msg.getArgs(heightArgIndex).getIntValue(0));
@@ -722,19 +729,49 @@
                                             getTextureUnitTargetName(target),
                                             GLStateType.TEXTURE_IMAGE_TYPE),
                 type));
+
+        // if texture data is available, extract and store it in the cache folder
+        File f = null;
+        if (msg.getArgs(8).getIsArray()) {
+            ByteString data = msg.getArgs(8).getRawBytes(0);
+            f = FileUtils.createTempFile(TEXTURE_DATA_FILE_PREFIX, TEXTURE_DATA_FILE_SUFFIX);
+            try {
+                Files.write(data.toByteArray(), f);
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        int xOffset = 0;
+        int yOffset = 0;
+
+        if (xOffsetIndex >= 0) {
+            xOffset = msg.getArgs(xOffsetIndex).getIntValue(0);
+        }
+
+        if (yOffsetIndex >= 0) {
+            yOffset = msg.getArgs(yOffsetIndex).getIntValue(0);
+        }
+
+        transforms.add(new TexImageTransform(
+                new TexturePropertyAccessor(msg.getContextId(),
+                        getTextureUnitTargetName(target),
+                        GLStateType.TEXTURE_IMAGE),
+                f, format, xOffset, yOffset, width, height));
+
         return transforms;
     }
 
     private static List<IStateTransform> transformsForGlTexImage2D(GLMessage msg) {
         // void glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width,
-        //          GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid * data);
-        return transformsForGlTexImage(msg, 3, 4);
+        //          GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *data);
+        return transformsForGlTexImage(msg, 3, 4, -1, -1);
     }
 
     private static List<IStateTransform> transformsForGlTexSubImage2D(GLMessage msg) {
         // void glTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
-        //          GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid * data);
-        return transformsForGlTexImage(msg, 4, 5);
+        //          GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *data);
+        return transformsForGlTexImage(msg, 4, 5, 2, 3);
     }
 
     private static List<IStateTransform> transformsForGlTexParameter(GLMessage msg) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/TexImageTransform.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/TexImageTransform.java
new file mode 100644
index 0000000..0d4ee30
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/TexImageTransform.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2012 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.ide.eclipse.gltrace.state.transforms;
+
+import com.android.ide.eclipse.gldebugger.GLEnum;
+import com.android.ide.eclipse.gltrace.FileUtils;
+import com.android.ide.eclipse.gltrace.state.GLStringProperty;
+import com.android.ide.eclipse.gltrace.state.IGLProperty;
+import com.google.common.io.Files;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+
+import javax.imageio.ImageIO;
+
+/**
+ * {@link TexImageTransform} transforms the state to reflect the effect of a
+ * glTexImage2D or glTexSubImage2D GL call.
+ */
+public class TexImageTransform implements IStateTransform {
+    private static final String PNG_IMAGE_FORMAT = "PNG";
+    private static final String TEXTURE_FILE_PREFIX = "tex";
+    private static final String TEXTURE_FILE_SUFFIX = ".png";
+
+    private final IGLPropertyAccessor mAccessor;
+    private final File mTextureDataFile;
+
+    private final int mxOffset;
+    private final int myOffset;
+    private final int mWidth;
+    private final int mHeight;
+
+    private String mOldValue;
+    private String mNewValue;
+    private GLEnum mFormat;
+
+    /**
+     * Construct a texture image transformation.
+     * @param accessor accessor to obtain the GL state variable to modify
+     * @param textureData texture data passed in by the call. Could be null.
+     * @param format format of the source texture data
+     * @param xOffset x offset for the source data (used only in glTexSubImage2D)
+     * @param yOffset y offset for the source data (used only in glTexSubImage2D)
+     * @param width width of the texture
+     * @param height height of the texture
+     */
+    public TexImageTransform(IGLPropertyAccessor accessor, File textureData, GLEnum format,
+            int xOffset, int yOffset, int width, int height) {
+        mAccessor = accessor;
+        mTextureDataFile = textureData;
+        mFormat = format;
+
+        mxOffset = xOffset;
+        myOffset = yOffset;
+        mWidth = width;
+        mHeight = height;
+    }
+
+    @Override
+    public void apply(IGLProperty currentState) {
+        assert mOldValue == null : "Transform cannot be applied multiple times"; //$NON-NLS-1$
+
+        IGLProperty property = mAccessor.getProperty(currentState);
+        if (!(property instanceof GLStringProperty)) {
+            return;
+        }
+
+        GLStringProperty prop = (GLStringProperty) property;
+        mOldValue = prop.getStringValue();
+
+        // Applying texture transformations is a heavy weight process. So we perform
+        // it only once and save the result in a temporary file. The property is actually
+        // the path to the file.
+        if (mNewValue == null) {
+            try {
+                if (mOldValue == null) {
+                    mNewValue = createTexture(mTextureDataFile, mWidth, mHeight);
+                } else {
+                    mNewValue = updateTextureData(mOldValue, mTextureDataFile, mxOffset, myOffset,
+                            mWidth, mHeight);
+                }
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        prop.setValue(mNewValue);
+    }
+
+    @Override
+    public void revert(IGLProperty state) {
+        if (mOldValue != null) {
+            IGLProperty property = mAccessor.getProperty(state);
+            property.setValue(mOldValue);
+            mOldValue = null;
+        }
+    }
+
+    @Override
+    public IGLProperty getChangedProperty(IGLProperty state) {
+        return mAccessor.getProperty(state);
+    }
+
+    /**
+     * Creates a texture of provided width and height. If the texture data file is provided,
+     * then the texture is initialized with the contents of that file, otherwise an empty
+     * image is created.
+     * @param textureDataFile path to texture data, could be null.
+     * @param width width of texture
+     * @param height height of texture
+     * @return path to cached texture
+     */
+    private String createTexture(File textureDataFile, int width, int height) throws IOException {
+        File f = FileUtils.createTempFile(TEXTURE_FILE_PREFIX, TEXTURE_FILE_SUFFIX);
+
+        BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
+
+        if (textureDataFile != null) {
+            byte[] initialData = Files.toByteArray(textureDataFile);
+            img.getRaster().setDataElements(0, 0, width, height,
+                    formatSourceData(initialData, width, height));
+        }
+
+        ImageIO.write(img, PNG_IMAGE_FORMAT, f);
+
+        return f.getAbsolutePath();
+    }
+
+    /**
+     * Update part of an existing texture.
+     * @param currentImagePath current texture image.
+     * @param textureDataFile new data to update the current texture with
+     * @param xOffset x offset for the update region
+     * @param yOffset y offset for the update region
+     * @param width width of the update region
+     * @param height height of the update region
+     * @return path to the updated texture
+     */
+    private String updateTextureData(String currentImagePath, File textureDataFile,
+            int xOffset, int yOffset, int width, int height) throws IOException {
+        assert currentImagePath != null : "Attempt to update a null texture";
+
+        if (textureDataFile == null) {
+            // Do not perform any updates if we don't have the actual data.
+            return currentImagePath;
+        }
+
+        File f = FileUtils.createTempFile(TEXTURE_FILE_PREFIX, TEXTURE_FILE_SUFFIX);
+        BufferedImage image = null;
+        image = ImageIO.read(new File(currentImagePath));
+
+        byte[] subImageData = Files.toByteArray(textureDataFile);
+        image.getRaster().setDataElements(xOffset, yOffset, width, height,
+                formatSourceData(subImageData, width, height));
+
+        ImageIO.write(image, PNG_IMAGE_FORMAT, f);
+
+        return f.getAbsolutePath();
+    }
+
+    private byte[] formatSourceData(byte[] subImageData, int width, int height) {
+        switch (mFormat) {
+            case GL_RGBA:
+                // no conversions necessary
+                return subImageData;
+            case GL_RGB:
+                return addAlphaChannel(subImageData, width, height);
+            case GL_ALPHA:
+                return addRGBChannels(subImageData, width, height);
+            case GL_LUMINANCE:
+                return createRGBAFromLuminance(subImageData, width, height);
+            case GL_LUMINANCE_ALPHA:
+                return createRGBAFromLuminanceAlpha(subImageData, width, height);
+            default:
+                throw new RuntimeException();
+        }
+    }
+
+    private byte[] addAlphaChannel(byte[] sourceData, int width, int height) {
+        assert sourceData.length == 3 * width * height; // should have R, G & B channels
+
+        byte[] data = new byte[4 * width * height];
+
+        for (int src = 0, dst = 0; src < sourceData.length; src += 3, dst += 4) {
+            data[dst + 0] = sourceData[src + 0]; // copy R byte
+            data[dst + 1] = sourceData[src + 1]; // copy G byte
+            data[dst + 2] = sourceData[src + 2]; // copy B byte
+            data[dst + 3] = 1; // add alpha = 1
+        }
+
+        return data;
+    }
+
+    private byte[] addRGBChannels(byte[] sourceData, int width, int height) {
+        assert sourceData.length == width * height; // should have a single alpha channel
+
+        byte[] data = new byte[4 * width * height];
+
+        for (int src = 0, dst = 0; src < sourceData.length; src++, dst += 4) {
+            data[dst + 0] = data[dst + 1] = data[dst + 2] = 0; // set R = G = B = 0
+            data[dst + 3] = sourceData[src];                 // copy over alpha
+        }
+
+        return data;
+    }
+
+    private byte[] createRGBAFromLuminance(byte[] sourceData, int width, int height) {
+        assert sourceData.length == width * height; // should have a single luminance channel
+
+        byte[] data = new byte[4 * width * height];
+
+        for (int src = 0, dst = 0; src < sourceData.length; src++, dst += 4) {
+            int l = sourceData[src] * 3;
+            if (l > 255) { // clamp to 255
+                l = 255;
+            }
+
+            data[dst + 0] = data[dst + 1] = data[dst + 2] = (byte) l; // set R = G = B = L * 3
+            data[dst + 3] = 1;                                        // set alpha = 1
+        }
+
+        return data;
+    }
+
+    private byte[] createRGBAFromLuminanceAlpha(byte[] sourceData, int width, int height) {
+        assert sourceData.length == 2 * width * height; // should have luminance & alpha channels
+
+        byte[] data = new byte[4 * width * height];
+
+        for (int src = 0, dst = 0; src < sourceData.length; src += 2, dst += 4) {
+            int l = sourceData[src] * 3;
+            if (l > 255) { // clamp to 255
+                l = 255;
+            }
+
+            data[dst + 0] = data[dst + 1] = data[dst + 2] = (byte) l; // set R = G = B = L * 3
+            data[dst + 3] = sourceData[src + 1];                    // copy over alpha
+        }
+
+        return data;
+    }
+}
diff --git a/eclipse/scripts/create_all_symlinks.sh b/eclipse/scripts/create_all_symlinks.sh
index b23a3b5..1e68571 100755
--- a/eclipse/scripts/create_all_symlinks.sh
+++ b/eclipse/scripts/create_all_symlinks.sh
@@ -162,9 +162,10 @@
   
   GLD_DEST="sdk/eclipse/plugins/com.android.ide.eclipse.gldebugger/libs"
   GLD_LIBS="host-libprotobuf-java-2.3.0-lite liblzf sdklib ddmlib"
+  GLD_PREBUILTS="prebuilts/tools/common/guava-tools/guava-10.0.1.jar"
 
   LIBS="$LIBS $GLD_LIBS"
-  CP_FILES="$CP_FILES @:$GLD_DEST $GLD_LIBS"
+  CP_FILES="$CP_FILES @:$GLD_DEST $GLD_LIBS $GLD_PREBUILTS"
 fi
 
 # Make sure we have lunch sdk-<something>