Bug 723036 - Port the Java compositor to OpenGL ES 2.0
authorPatrick Walton <pwalton@mozilla.com>
Wed, 08 Feb 2012 21:13:08 -0800
changeset 89107 6cf9f5c757ae462fc246b8379ccf281d2e4ceedc
parent 89106 a3da29165fc211efe092fb145254606a723485a5
child 89108 23107130ea2279672b2fc923e62700fa9b141ae7
push id22242
push userkgupta@mozilla.com
push dateWed, 14 Mar 2012 15:19:09 +0000
treeherdermozilla-central@936ef50fa498 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs723036
milestone13.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 723036 - Port the Java compositor to OpenGL ES 2.0
mobile/android/base/AndroidManifest.xml.in
mobile/android/base/gfx/GeckoGLLayerClient.java
mobile/android/base/gfx/Layer.java
mobile/android/base/gfx/LayerRenderer.java
mobile/android/base/gfx/LayerView.java
mobile/android/base/gfx/MultiTileLayer.java
mobile/android/base/gfx/NinePatchTileLayer.java
mobile/android/base/gfx/ScrollbarLayer.java
mobile/android/base/gfx/SingleTileLayer.java
mobile/android/base/gfx/SurfaceTextureLayer.java
mobile/android/base/gfx/TextureGenerator.java
mobile/android/base/gfx/TextureReaper.java
mobile/android/base/gfx/TileLayer.java
mobile/android/base/gfx/VirtualLayer.java
mobile/android/base/gfx/WidgetTileLayer.java
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -37,16 +37,19 @@
 
     <uses-feature android:name="android.hardware.location" android:required="false"/>
     <uses-feature android:name="android.hardware.location.gps" android:required="false"/>
     <uses-feature android:name="android.hardware.touchscreen"/>
 
     <uses-permission android:name="android.permission.CAMERA" />
     <uses-feature android:name="android.hardware.camera" android:required="false"/>
     <uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
+
+    <!-- App requires OpenGL ES 2.0 -->
+    <uses-feature android:glEsVersion="0x00020000" android:required="true" />
  
     <application android:label="@MOZ_APP_DISPLAYNAME@"
 		 android:icon="@drawable/icon"
 #if MOZILLA_OFFICIAL
 		 android:debuggable="false">
 #else
 		 android:debuggable="true">
 #endif
--- a/mobile/android/base/gfx/GeckoGLLayerClient.java
+++ b/mobile/android/base/gfx/GeckoGLLayerClient.java
@@ -123,17 +123,17 @@ public class GeckoGLLayerClient extends 
     protected IntSize getTileSize() {
         Log.e(LOGTAG, "### getTileSize " + getBufferSize());
         return getBufferSize();
     }
 
     @Override
     protected void tileLayerUpdated() {
         // Set the new origin and resolution instantly.
-        mTileLayer.performUpdates(null, null);
+        mTileLayer.performUpdates(null);
     }
 
     @Override
     public Bitmap getBitmap() {
         Log.e(LOGTAG, "### getBitmap");
         IntSize size = getBufferSize();
         return Bitmap.createBitmap(size.width, size.height, Bitmap.Config.RGB_565);
     }
--- a/mobile/android/base/gfx/Layer.java
+++ b/mobile/android/base/gfx/Layer.java
@@ -16,16 +16,17 @@
  *
  * The Initial Developer of the Original Code is Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2009-2010
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Patrick Walton <pcwalton@mozilla.com>
  *   Chris Lord <chrislord.net@gmail.com>
+ *   Arkady Blyakher <rkadyb@mit.edu>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -38,18 +39,18 @@
 
 package org.mozilla.gecko.gfx;
 
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.RectF;
 import android.graphics.Region;
 import android.util.Log;
+import java.nio.FloatBuffer;
 import java.util.concurrent.locks.ReentrantLock;
-import javax.microedition.khronos.opengles.GL10;
 import org.mozilla.gecko.FloatUtils;
 
 public abstract class Layer {
     private final ReentrantLock mTransactionLock;
     private boolean mInTransaction;
     private Point mNewOrigin;
     private float mNewResolution;
     private LayerView mView;
@@ -62,25 +63,25 @@ public abstract class Layer {
         mOrigin = new Point(0, 0);
         mResolution = 1.0f;
     }
 
     /**
      * Updates the layer. This returns false if there is still work to be done
      * after this update.
      */
-    public final boolean update(GL10 gl, RenderContext context) {
+    public final boolean update(RenderContext context) {
         if (mTransactionLock.isHeldByCurrentThread()) {
             throw new RuntimeException("draw() called while transaction lock held by this " +
                                        "thread?!");
         }
 
         if (mTransactionLock.tryLock()) {
             try {
-                return performUpdates(gl, context);
+                return performUpdates(context);
             } finally {
                 mTransactionLock.unlock();
             }
         }
 
         return false;
     }
 
@@ -172,17 +173,17 @@ public abstract class Layer {
     }
 
     /**
      * Subclasses may override this method to perform custom layer updates. This will be called
      * with the transaction lock held. Subclass implementations of this method must call the
      * superclass implementation. Returns false if there is still work to be done after this
      * update is complete.
      */
-    protected boolean performUpdates(GL10 gl, RenderContext context) {
+    protected boolean performUpdates(RenderContext context) {
         if (mNewOrigin != null) {
             mOrigin = mNewOrigin;
             mNewOrigin = null;
         }
         if (mNewResolution != 0.0f) {
             mResolution = mNewResolution;
             mNewResolution = 0.0f;
         }
@@ -193,21 +194,28 @@ public abstract class Layer {
     protected boolean dimensionChangesPending() {
         return (mNewOrigin != null) || (mNewResolution != 0.0f);
     }
 
     public static class RenderContext {
         public final RectF viewport;
         public final FloatSize pageSize;
         public final float zoomFactor;
+        public final int positionHandle;
+        public final int textureHandle;
+        public final FloatBuffer coordBuffer;
 
-        public RenderContext(RectF aViewport, FloatSize aPageSize, float aZoomFactor) {
+        public RenderContext(RectF aViewport, FloatSize aPageSize, float aZoomFactor,
+                             int aPositionHandle, int aTextureHandle, FloatBuffer aCoordBuffer) {
             viewport = aViewport;
             pageSize = aPageSize;
             zoomFactor = aZoomFactor;
+            positionHandle = aPositionHandle;
+            textureHandle = aTextureHandle;
+            coordBuffer = aCoordBuffer;
         }
 
         public boolean fuzzyEquals(RenderContext other) {
             if (other == null) {
                 return false;
             }
             return RectUtils.fuzzyEquals(viewport, other.viewport)
                 && pageSize.fuzzyEquals(other.pageSize)
--- a/mobile/android/base/gfx/LayerRenderer.java
+++ b/mobile/android/base/gfx/LayerRenderer.java
@@ -16,16 +16,17 @@
  *
  * The Initial Developer of the Original Code is Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2009-2010
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Patrick Walton <pcwalton@mozilla.com>
  *   Chris Lord <chrislord.net@gmail.com>
+ *   Arkady Blyakher <rkadyb@mit.edu>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -43,31 +44,36 @@ import org.mozilla.gecko.gfx.IntSize;
 import org.mozilla.gecko.gfx.Layer.RenderContext;
 import org.mozilla.gecko.gfx.LayerController;
 import org.mozilla.gecko.gfx.NinePatchTileLayer;
 import org.mozilla.gecko.gfx.SingleTileLayer;
 import org.mozilla.gecko.gfx.TextureReaper;
 import org.mozilla.gecko.gfx.TextureGenerator;
 import org.mozilla.gecko.gfx.TextLayer;
 import org.mozilla.gecko.gfx.TileLayer;
+import org.mozilla.gecko.GeckoAppShell;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
 import android.graphics.RegionIterator;
+import android.opengl.GLES20;
 import android.opengl.GLSurfaceView;
 import android.os.SystemClock;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.WindowManager;
 import javax.microedition.khronos.egl.EGLConfig;
 import javax.microedition.khronos.opengles.GL10;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
 import java.nio.IntBuffer;
 import java.util.ArrayList;
 
 /**
  * The layer renderer implements the rendering logic for a layer view.
  */
 public class LayerRenderer implements GLSurfaceView.Renderer {
     private static final String LOGTAG = "GeckoLayerRenderer";
@@ -86,16 +92,17 @@ public class LayerRenderer implements GL
     private final SingleTileLayer mBackgroundLayer;
     private final CheckerboardImage mCheckerboardImage;
     private final SingleTileLayer mCheckerboardLayer;
     private final NinePatchTileLayer mShadowLayer;
     private final TextLayer mFrameRateLayer;
     private final ScrollbarLayer mHorizScrollLayer;
     private final ScrollbarLayer mVertScrollLayer;
     private final FadeRunnable mFadeRunnable;
+    private final FloatBuffer mCoordBuffer;
     private RenderContext mLastPageContext;
     private int mMaxTextureSize;
 
     private ArrayList<Layer> mExtraLayers = new ArrayList<Layer>();
 
     // Dropped frames display
     private int[] mFrameTimings;
     private int mCurrentFrame, mFrameTimingsSum, mDroppedFrames;
@@ -105,16 +112,58 @@ public class LayerRenderer implements GL
     private int mFramesRendered;
     private float mCompleteFramesRendered;
     private boolean mProfileRender;
     private long mProfileOutputTime;
 
     /* Used by robocop for testing purposes */
     private IntBuffer mPixelBuffer;
 
+    // Used by GLES 2.0
+    private int mProgram;
+    private int mPositionHandle;
+    private int mTextureHandle;
+    private int mSampleHandle;
+    private int mTMatrixHandle;
+
+    // column-major matrix applied to each vertex to shift the viewport from
+    // one ranging from (-1, -1),(1,1) to (0,0),(1,1) and to scale all sizes by
+    // a factor of 2 to fill up the screen
+    private static final float[] TEXTURE_MATRIX = {
+        2.0f, 0.0f, 0.0f, 0.0f,
+        0.0f, 2.0f, 0.0f, 0.0f,
+        0.0f, 0.0f, 2.0f, 0.0f,
+        -1.0f, -1.0f, 0.0f, 1.0f
+    };
+
+    private static final int COORD_BUFFER_SIZE = 20;
+
+    // The shaders run on the GPU directly, the vertex shader is only applying the
+    // matrix transform detailed above
+    private static final String VERTEX_SHADER =
+        "uniform mat4 uTMatrix;\n" +
+        "attribute vec4 vPosition;\n" +
+        "attribute vec2 aTexCoord;\n" +
+        "varying vec2 vTexCoord;\n" +
+        "void main() {\n" +
+        "    gl_Position = uTMatrix * vPosition;\n" +
+        "    vTexCoord = aTexCoord;\n" +
+        "}\n";
+
+    // Note we flip the y-coordinate in the fragment shader from a
+    // coordinate system with (0,0) in the top left to one with (0,0) in
+    // the bottom left.
+    private static final String FRAGMENT_SHADER =
+        "precision mediump float;\n" +
+        "varying vec2 vTexCoord;\n" +
+        "uniform sampler2D sTexture;\n" +
+        "void main() {\n" +
+        "    gl_FragColor = texture2D(sTexture, vec2(vTexCoord.x, 1.0 - vTexCoord.y));\n" +
+        "}\n";
+
     public LayerRenderer(LayerView view) {
         mView = view;
 
         LayerController controller = view.getController();
 
         CairoImage backgroundImage = new BufferedCairoImage(controller.getBackgroundPattern());
         mBackgroundLayer = new SingleTileLayer(true, backgroundImage);
 
@@ -129,29 +178,57 @@ public class LayerRenderer implements GL
 
         mHorizScrollLayer = ScrollbarLayer.create(false);
         mVertScrollLayer = ScrollbarLayer.create(true);
         mFadeRunnable = new FadeRunnable();
 
         mFrameTimings = new int[60];
         mCurrentFrame = mFrameTimingsSum = mDroppedFrames = 0;
         mShowFrameRate = false;
+
+        // Initialize the FloatBuffer that will be used to store all vertices and texture
+        // coordinates in draw() commands.
+        ByteBuffer byteBuffer = GeckoAppShell.allocateDirectBuffer(COORD_BUFFER_SIZE * 4);
+        byteBuffer.order(ByteOrder.nativeOrder());
+        mCoordBuffer = byteBuffer.asFloatBuffer();
     }
 
     public void onSurfaceCreated(GL10 gl, EGLConfig config) {
         checkMonitoringEnabled();
 
-        gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);
-        gl.glDisable(GL10.GL_DITHER);
-        gl.glEnable(GL10.GL_TEXTURE_2D);
+        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER);
+        int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER);
+
+        mProgram = GLES20.glCreateProgram();
+        GLES20.glAttachShader(mProgram, vertexShader);   // add the vertex shader to program
+        GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program
+        GLES20.glLinkProgram(mProgram);                  // creates OpenGL program executables
+
+        // Get handles to the vertex shader's vPosition, aTexCoord, sTexture, and uTMatrix members.
+        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
+        mTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTexCoord");
+        mSampleHandle = GLES20.glGetUniformLocation(mProgram, "sTexture");
+        mTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uTMatrix");
 
         int maxTextureSizeResult[] = new int[1];
-        gl.glGetIntegerv(GL10.GL_MAX_TEXTURE_SIZE, maxTextureSizeResult, 0);
+        GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxTextureSizeResult, 0);
         mMaxTextureSize = maxTextureSizeResult[0];
 
+        // Add the program to the OpenGL environment
+        GLES20.glUseProgram(mProgram);
+
+        // Set the transformation matrix
+        GLES20.glUniformMatrix4fv(mTMatrixHandle, 1, false, TEXTURE_MATRIX, 0);
+
+        // Enable the arrays from which we get the vertex and texture coordinates
+        GLES20.glEnableVertexAttribArray(mPositionHandle);
+        GLES20.glEnableVertexAttribArray(mTextureHandle);
+
+        GLES20.glUniform1i(mSampleHandle, 0);
+
         TextureGenerator.get().fill();
     }
 
     public int getMaxTextureSize() {
         return mMaxTextureSize;
     }
 
     public void addLayer(Layer layer) {
@@ -175,17 +252,17 @@ public class LayerRenderer implements GL
     }
 
     /**
      * Called whenever a new frame is about to be drawn.
      */
     public void onDrawFrame(GL10 gl) {
         long frameStartTime = SystemClock.uptimeMillis();
 
-        TextureReaper.get().reap(gl);
+        TextureReaper.get().reap();
         TextureGenerator.get().fill();
 
         LayerController controller = mView.getController();
         RenderContext screenContext = createScreenContext();
 
         boolean updated = true;
 
         synchronized (controller) {
@@ -202,50 +279,50 @@ public class LayerRenderer implements GL
                 boolean stillFading = mVertScrollLayer.fade() | mHorizScrollLayer.fade();
                 if (stillFading) {
                     mFadeRunnable.scheduleNextFadeFrame();
                 }
             }
             mLastPageContext = pageContext;
 
             /* Update layers. */
-            if (rootLayer != null) updated &= rootLayer.update(gl, pageContext);
-            updated &= mBackgroundLayer.update(gl, screenContext);
-            updated &= mShadowLayer.update(gl, pageContext);
-            updateCheckerboardLayer(gl, screenContext);
-            updated &= mFrameRateLayer.update(gl, screenContext);
-            updated &= mVertScrollLayer.update(gl, pageContext);
-            updated &= mHorizScrollLayer.update(gl, pageContext);
+            if (rootLayer != null) updated &= rootLayer.update(pageContext);
+            updated &= mBackgroundLayer.update(screenContext);
+            updated &= mShadowLayer.update(pageContext);
+            updateCheckerboardLayer(screenContext);
+            updated &= mFrameRateLayer.update(screenContext);
+            updated &= mVertScrollLayer.update(pageContext);
+            updated &= mHorizScrollLayer.update(pageContext);
 
             for (Layer layer : mExtraLayers)
-                updated &= layer.update(gl, pageContext);
+                updated &= layer.update(pageContext);
 
             /* Draw the background. */
             mBackgroundLayer.draw(screenContext);
 
             /* Draw the drop shadow, if we need to. */
             Rect pageRect = getPageRect();
             RectF untransformedPageRect = new RectF(0.0f, 0.0f, pageRect.width(),
                                                     pageRect.height());
             if (!untransformedPageRect.contains(controller.getViewport()))
                 mShadowLayer.draw(pageContext);
 
             /* Draw the checkerboard. */
             Rect scissorRect = transformToScissorRect(pageRect);
-            gl.glEnable(GL10.GL_SCISSOR_TEST);
-            gl.glScissor(scissorRect.left, scissorRect.top,
-                         scissorRect.width(), scissorRect.height());
+            GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
+            GLES20.glScissor(scissorRect.left, scissorRect.top,
+                             scissorRect.width(), scissorRect.height());
 
             mCheckerboardLayer.draw(screenContext);
 
             /* Draw the layer the client added to us. */
             if (rootLayer != null)
                 rootLayer.draw(pageContext);
 
-            gl.glDisable(GL10.GL_SCISSOR_TEST);
+            GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
 
             /* Draw any extra layers that were added (likely plugins) */
             for (Layer layer : mExtraLayers)
                 layer.draw(pageContext);
 
             /* Draw the vertical scrollbar. */
             IntSize screenSize = new IntSize(controller.getViewportSize());
             if (pageRect.height() > screenSize.height)
@@ -294,21 +371,21 @@ public class LayerRenderer implements GL
             }
         }
 
         /* Draw the FPS. */
         if (mShowFrameRate) {
             updateDroppedFrames(frameStartTime);
 
             try {
-                gl.glEnable(GL10.GL_BLEND);
-                gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
+                GLES20.glEnable(GLES20.GL_BLEND);
+                GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
                 mFrameRateLayer.draw(screenContext);
             } finally {
-                gl.glDisable(GL10.GL_BLEND);
+                GLES20.glDisable(GLES20.GL_BLEND);
             }
         }
 
         // If a layer update requires further work, schedule another redraw
         if (!updated)
             mView.requestRender();
 
         PanningPerfAPI.recordFrameTime();
@@ -345,28 +422,30 @@ public class LayerRenderer implements GL
         return pixelBuffer;
     }
 
     private RenderContext createScreenContext() {
         LayerController layerController = mView.getController();
         IntSize viewportSize = new IntSize(layerController.getViewportSize());
         RectF viewport = new RectF(0.0f, 0.0f, viewportSize.width, viewportSize.height);
         FloatSize pageSize = new FloatSize(layerController.getPageSize());
-        return new RenderContext(viewport, pageSize, 1.0f);
+        return new RenderContext(viewport, pageSize, 1.0f, mPositionHandle, mTextureHandle,
+                                 mCoordBuffer);
     }
 
     private RenderContext createPageContext() {
         LayerController layerController = mView.getController();
 
         Rect viewport = new Rect();
         layerController.getViewport().round(viewport);
 
         FloatSize pageSize = new FloatSize(layerController.getPageSize());
         float zoomFactor = layerController.getZoomFactor();
-        return new RenderContext(new RectF(viewport), pageSize, zoomFactor);
+        return new RenderContext(new RectF(viewport), pageSize, zoomFactor, mPositionHandle,
+                                 mTextureHandle, mCoordBuffer);
     }
 
     private Rect getPageRect() {
         LayerController controller = mView.getController();
 
         Point origin = PointUtils.round(controller.getOrigin());
         IntSize pageSize = new IntSize(controller.getPageSize());
 
@@ -385,17 +464,17 @@ public class LayerRenderer implements GL
         int right = Math.min(screenSize.width, rect.right);
         int bottom = Math.min(screenSize.height, rect.bottom);
 
         return new Rect(left, screenSize.height - bottom, right,
                         (screenSize.height - bottom) + (bottom - top));
     }
 
     public void onSurfaceChanged(GL10 gl, final int width, final int height) {
-        gl.glViewport(0, 0, width, height);
+        GLES20.glViewport(0, 0, width, height);
 
         // updating the state in the view/controller/client should be
         // done on the main UI thread, not the GL renderer thread
         mView.post(new Runnable() {
             public void run() {
                 mView.setViewportSize(new IntSize(width, height));
                 moveFrameRateLayer(width, height);
             }
@@ -445,33 +524,44 @@ public class LayerRenderer implements GL
                 Context context = mView.getContext();
                 SharedPreferences preferences = context.getSharedPreferences("GeckoApp", 0);
                 mShowFrameRate = preferences.getBoolean("showFrameRate", false);
                 mProfileRender = Log.isLoggable(PROFTAG, Log.DEBUG);
             }
         }).start();
     }
 
-    private void updateCheckerboardLayer(GL10 gl, RenderContext renderContext) {
+    private void updateCheckerboardLayer(RenderContext renderContext) {
         int checkerboardColor = mView.getController().getCheckerboardColor();
         boolean showChecks = mView.getController().checkerboardShouldShowChecks();
         if (checkerboardColor == mCheckerboardImage.getColor() &&
             showChecks == mCheckerboardImage.getShowChecks()) {
             return;
         }
 
         mCheckerboardLayer.beginTransaction();
         try {
             mCheckerboardImage.update(showChecks, checkerboardColor);
             mCheckerboardLayer.invalidate();
         } finally {
             mCheckerboardLayer.endTransaction();
         }
 
-        mCheckerboardLayer.update(gl, renderContext);
+        mCheckerboardLayer.update(renderContext);
+    }
+
+    /*
+     * create a vertex shader type (GLES20.GL_VERTEX_SHADER)
+     * or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
+     */
+    private int loadShader(int type, String shaderCode) {
+        int shader = GLES20.glCreateShader(type);
+        GLES20.glShaderSource(shader, shaderCode);
+        GLES20.glCompileShader(shader);
+        return shader;
     }
 
     class FadeRunnable implements Runnable {
         private boolean mStarted;
         private long mRunAt;
 
         void scheduleStartFade(long delay) {
             mRunAt = SystemClock.elapsedRealtime() + delay;
--- a/mobile/android/base/gfx/LayerView.java
+++ b/mobile/android/base/gfx/LayerView.java
@@ -15,16 +15,17 @@
  * The Original Code is Mozilla Android code.
  *
  * The Initial Developer of the Original Code is Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2009-2010
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Patrick Walton <pcwalton@mozilla.com>
+ *   Arkady Blyakher <rkadyb@mit.edu>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
--- a/mobile/android/base/gfx/MultiTileLayer.java
+++ b/mobile/android/base/gfx/MultiTileLayer.java
@@ -1,9 +1,9 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (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.mozilla.org/MPL/
  *
@@ -15,16 +15,17 @@
  * The Original Code is Mozilla Android code.
  *
  * The Initial Developer of the Original Code is Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2011-2012
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Chris Lord <chrislord.net@gmail.com>
+ *   Arkady Blyakher <rkadyb@mit.edu>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -41,18 +42,18 @@ import org.mozilla.gecko.gfx.CairoImage;
 import org.mozilla.gecko.gfx.IntSize;
 import org.mozilla.gecko.gfx.SingleTileLayer;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
 import android.util.Log;
 import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
 import java.util.ArrayList;
-import javax.microedition.khronos.opengles.GL10;
 
 /**
  * Encapsulates the logic needed to draw a layer made of multiple tiles.
  *
  * TODO: Support repeating.
  */
 public class MultiTileLayer extends Layer {
     private static final String LOGTAG = "GeckoMultiTileLayer";
@@ -152,47 +153,47 @@ public class MultiTileLayer extends Laye
 
         // Set tile origins and resolution
         refreshTileMetrics(getOrigin(), getResolution(), false);
 
         mBufferSize = size;
     }
 
     @Override
-    protected boolean performUpdates(GL10 gl, RenderContext context) {
-        super.performUpdates(gl, context);
+    protected boolean performUpdates(RenderContext context) {
+        super.performUpdates(context);
 
         validateTiles();
 
         // Iterate over the tiles and decide which ones we'll be drawing
         int dirtyTiles = 0;
         boolean screenUpdateDone = false;
         SubTile firstDirtyTile = null;
         for (SubTile layer : mTiles) {
             // First do a non-texture update to make sure coordinates are
             // up-to-date.
             boolean invalid = layer.getSkipTextureUpdate();
             layer.setSkipTextureUpdate(true);
-            layer.performUpdates(gl, context);
+            layer.performUpdates(context);
 
             RectF layerBounds = layer.getBounds(context, new FloatSize(layer.getSize()));
             boolean isDirty = layer.isDirty();
 
             if (isDirty) {
                 if (!RectF.intersects(layerBounds, context.viewport)) {
                     if (firstDirtyTile == null)
                         firstDirtyTile = layer;
                     dirtyTiles ++;
                     invalid = true;
                 } else {
                     // This tile intersects with the screen and is dirty,
                     // update it immediately.
                     layer.setSkipTextureUpdate(false);
                     screenUpdateDone = true;
-                    layer.performUpdates(gl, context);
+                    layer.performUpdates(context);
                     invalid = false;
                 }
             }
 
             // We use the SkipTextureUpdate flag as a marker of a tile's
             // validity. This is required, as sometimes layers are drawn
             // without updating first, and we mustn't draw tiles that have
             // been marked as invalid that we haven't updated.
@@ -200,17 +201,17 @@ public class MultiTileLayer extends Laye
         }
 
         // Now if no tiles that intersect with the screen were updated, update
         // a single tile that doesn't (if there are any). This has the effect
         // of spreading out non-critical texture upload over time, and smoothing
         // upload-related hitches.
         if (!screenUpdateDone && firstDirtyTile != null) {
             firstDirtyTile.setSkipTextureUpdate(false);
-            firstDirtyTile.performUpdates(gl, context);
+            firstDirtyTile.performUpdates(context);
             dirtyTiles --;
         }
 
         return (dirtyTiles == 0);
     }
 
     private void refreshTileMetrics(Point origin, float resolution, boolean inTransaction) {
         IntSize size = getSize();
--- a/mobile/android/base/gfx/NinePatchTileLayer.java
+++ b/mobile/android/base/gfx/NinePatchTileLayer.java
@@ -15,16 +15,17 @@
  * The Original Code is Mozilla Android code.
  *
  * The Initial Developer of the Original Code is Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2009-2010
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Patrick Walton <pcwalton@mozilla.com>
+ *   Arkady Blyakher <rkadyb@mit.edu>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -35,48 +36,48 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 package org.mozilla.gecko.gfx;
 
 import org.mozilla.gecko.gfx.FloatSize;
 import android.graphics.PointF;
 import android.graphics.RectF;
-import android.opengl.GLES11;
-import android.opengl.GLES11Ext;
 import android.util.Log;
 import javax.microedition.khronos.opengles.GL10;
 import java.nio.FloatBuffer;
+import android.opengl.GLES20;
 
 /**
  * Encapsulates the logic needed to draw a nine-patch bitmap using OpenGL ES.
  *
  * For more information on nine-patch bitmaps, see the following document:
  *   http://developer.android.com/guide/topics/graphics/2d-graphics.html#nine-patch
  */
 public class NinePatchTileLayer extends TileLayer {
     private static final int PATCH_SIZE = 16;
-    private static final int TEXTURE_SIZE = 48;
+    private static final int TEXTURE_SIZE = 64;
 
     public NinePatchTileLayer(CairoImage image) {
         super(false, image);
     }
 
     @Override
     public void draw(RenderContext context) {
         if (!initialized())
             return;
 
-        GLES11.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
-        GLES11.glEnable(GL10.GL_BLEND);
+        GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
+        GLES20.glEnable(GLES20.GL_BLEND);
+
         try {
-            GLES11.glBindTexture(GL10.GL_TEXTURE_2D, getTextureID());
+            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTextureID());
             drawPatches(context);
         } finally {
-            GLES11.glDisable(GL10.GL_BLEND);
+            GLES20.glDisable(GLES20.GL_BLEND);
         }
     }
 
     private void drawPatches(RenderContext context) {
         /*
          * We divide the nine-patch bitmap up as follows:
          *
          *    +---+---+---+
@@ -86,39 +87,82 @@ public class NinePatchTileLayer extends 
          *    +---+---+---+
          *    | 5 | 6 | 7 |
          *    +---+---+---+
          */
 
         FloatSize size = context.pageSize;
         float width = size.width, height = size.height;
 
-        drawPatch(context, 0, 0,                                                    /* 0 */
+        drawPatch(context, 0, PATCH_SIZE * 3,                                              /* 0 */
                   0.0f, 0.0f, PATCH_SIZE, PATCH_SIZE);
-        drawPatch(context, PATCH_SIZE, 0,                                           /* 1 */
+        drawPatch(context, PATCH_SIZE, PATCH_SIZE*3,                                       /* 1 */
                   PATCH_SIZE, 0.0f, width, PATCH_SIZE);
-        drawPatch(context, PATCH_SIZE * 2, 0,                                       /* 2 */
+        drawPatch(context, PATCH_SIZE * 2, PATCH_SIZE*3,                                   /* 2 */
                   PATCH_SIZE + width, 0.0f, PATCH_SIZE, PATCH_SIZE);
-        drawPatch(context, 0, PATCH_SIZE,                                           /* 3 */
+        drawPatch(context, 0, PATCH_SIZE * 2,                                              /* 3 */
                   0.0f, PATCH_SIZE, PATCH_SIZE, height);
-        drawPatch(context, PATCH_SIZE * 2, PATCH_SIZE,                              /* 4 */
+        drawPatch(context, PATCH_SIZE * 2, PATCH_SIZE * 2,                                 /* 4 */
                   PATCH_SIZE + width, PATCH_SIZE, PATCH_SIZE, height);
-        drawPatch(context, 0, PATCH_SIZE * 2,                                       /* 5 */
+        drawPatch(context, 0, PATCH_SIZE,                                                  /* 5 */
                   0.0f, PATCH_SIZE + height, PATCH_SIZE, PATCH_SIZE);
-        drawPatch(context, PATCH_SIZE, PATCH_SIZE * 2,                              /* 6 */
+        drawPatch(context, PATCH_SIZE, PATCH_SIZE,                                         /* 6 */
                   PATCH_SIZE, PATCH_SIZE + height, width, PATCH_SIZE);
-        drawPatch(context, PATCH_SIZE * 2, PATCH_SIZE * 2,                          /* 7 */
+        drawPatch(context, PATCH_SIZE * 2, PATCH_SIZE,                                     /* 7 */
                   PATCH_SIZE + width, PATCH_SIZE + height, PATCH_SIZE, PATCH_SIZE);
     }
 
-    private void drawPatch(RenderContext context, int textureX, int textureY, float tileX,
-                           float tileY, float tileWidth, float tileHeight) {
-        int[] cropRect = { textureX, textureY + PATCH_SIZE, PATCH_SIZE, -PATCH_SIZE };
-        GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, cropRect,
-                                0);
-
+    private void drawPatch(RenderContext context, int textureX, int textureY,
+                           float tileX, float tileY, float tileWidth, float tileHeight) {
         RectF viewport = context.viewport;
         float viewportHeight = viewport.height();
         float drawX = tileX - viewport.left - PATCH_SIZE;
         float drawY = viewportHeight - (tileY + tileHeight - viewport.top - PATCH_SIZE);
-        GLES11Ext.glDrawTexfOES(drawX, drawY, 0.0f, tileWidth, tileHeight);
+
+        float[] coords = {
+            //x, y, z, texture_x, texture_y
+            drawX/viewport.width(), drawY/viewport.height(), 0,
+            textureX/(float)TEXTURE_SIZE, textureY/(float)TEXTURE_SIZE,
+
+            drawX/viewport.width(), (drawY+tileHeight)/viewport.height(), 0,
+            textureX/(float)TEXTURE_SIZE, (textureY+PATCH_SIZE)/(float)TEXTURE_SIZE,
+
+            (drawX+tileWidth)/viewport.width(), drawY/viewport.height(), 0,
+            (textureX+PATCH_SIZE)/(float)TEXTURE_SIZE, textureY/(float)TEXTURE_SIZE,
+
+            (drawX+tileWidth)/viewport.width(), (drawY+tileHeight)/viewport.height(), 0,
+            (textureX+PATCH_SIZE)/(float)TEXTURE_SIZE, (textureY+PATCH_SIZE)/(float)TEXTURE_SIZE
+
+        };
+
+        // Get the buffer and handles from the context
+        FloatBuffer coordBuffer = context.coordBuffer;
+        int positionHandle = context.positionHandle;
+        int textureHandle = context.textureHandle;
+
+        // Make sure we are at position zero in the buffer in case other draw methods did not clean
+        // up after themselves
+        coordBuffer.position(0);
+        coordBuffer.put(coords);
+
+        // Vertex coordinates are x,y,z starting at position 0 into the buffer.
+        coordBuffer.position(0);
+        GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20, coordBuffer);
+
+        // Texture coordinates are texture_x, texture_y starting at position 3 into the buffer.
+        coordBuffer.position(3);
+        GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20, coordBuffer);
+
+        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
+                               GLES20.GL_CLAMP_TO_EDGE);
+        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
+                               GLES20.GL_CLAMP_TO_EDGE);
+
+        // Use bilinear filtering for both magnification and minimization of the texture. This
+        // applies only to the shadow layer so we do not incur a high overhead.
+        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
+                               GLES20.GL_LINEAR);
+        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
+                               GLES20.GL_LINEAR);
+
+        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
     }
 }
--- a/mobile/android/base/gfx/ScrollbarLayer.java
+++ b/mobile/android/base/gfx/ScrollbarLayer.java
@@ -15,16 +15,17 @@
  * The Original Code is Mozilla Android code.
  *
  * The Initial Developer of the Original Code is Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2011
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Kartikaya Gupta <kgupta@mozilla.com>
+ *   Arkady Blyakher <rkadyb@mit.edu>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -40,48 +41,91 @@ package org.mozilla.gecko.gfx;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.PointF;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.RectF;
-import android.opengl.GLES11;
-import android.opengl.GLES11Ext;
+import android.opengl.GLES20;
 import android.util.Log;
 import java.nio.ByteBuffer;
-import javax.microedition.khronos.opengles.GL10;
+import java.nio.FloatBuffer;
 import org.mozilla.gecko.FloatUtils;
 import org.mozilla.gecko.GeckoAppShell;
 
 /**
  * Draws a small rect. This is scaled to become a scrollbar.
  */
 public class ScrollbarLayer extends TileLayer {
     public static final long FADE_DELAY = 500; // milliseconds before fade-out starts
     private static final float FADE_AMOUNT = 0.03f; // how much (as a percent) the scrollbar should fade per frame
 
     private static final int PADDING = 1;   // gap between scrollbar and edge of viewport
     private static final int BAR_SIZE = 6;
     private static final int CAP_RADIUS = (BAR_SIZE / 2);
 
-    private static final int[] CROPRECT_MIDPIXEL = new int[] { CAP_RADIUS, CAP_RADIUS, 1, 1 };
-    private static final int[] CROPRECT_TOPCAP = new int[] { 0, CAP_RADIUS, BAR_SIZE, -CAP_RADIUS };
-    private static final int[] CROPRECT_BOTTOMCAP = new int[] { 0, BAR_SIZE, BAR_SIZE, -CAP_RADIUS };
-    private static final int[] CROPRECT_LEFTCAP = new int[] { 0, BAR_SIZE, CAP_RADIUS, -BAR_SIZE };
-    private static final int[] CROPRECT_RIGHTCAP = new int[] { CAP_RADIUS, BAR_SIZE, CAP_RADIUS, -BAR_SIZE };
-
     private final boolean mVertical;
     private final ByteBuffer mBuffer;
     private final Bitmap mBitmap;
     private final Canvas mCanvas;
     private float mOpacity;
     private boolean mFinalized = false;
 
+    // Dimensions of the texture image
+    private static final float TEX_HEIGHT = 8.0f;
+    private static final float TEX_WIDTH = 8.0f;
+
+    // Texture coordinates for the scrollbar's body
+    // We take a 1x1 pixel from the center of the image and scale it to become the bar
+    private static final float[] BODY_TEX_COORDS = {
+        // x, y
+        CAP_RADIUS/TEX_WIDTH, CAP_RADIUS/TEX_HEIGHT,
+        CAP_RADIUS/TEX_WIDTH, (CAP_RADIUS+1)/TEX_HEIGHT,
+        (CAP_RADIUS+1)/TEX_WIDTH, CAP_RADIUS/TEX_HEIGHT,
+        (CAP_RADIUS+1)/TEX_WIDTH, (CAP_RADIUS+1)/TEX_HEIGHT
+    };
+
+    // Texture coordinates for the top cap of the scrollbar
+    private static final float[] TOP_CAP_TEX_COORDS = {
+        // x, y
+        0                 , 1.0f - CAP_RADIUS/TEX_HEIGHT,
+        0                 , 1.0f,
+        BAR_SIZE/TEX_WIDTH, 1.0f - CAP_RADIUS/TEX_HEIGHT,
+        BAR_SIZE/TEX_WIDTH, 1.0f
+    };
+
+    // Texture coordinates for the bottom cap of the scrollbar
+    private static final float[] BOT_CAP_TEX_COORDS = {
+        // x, y
+        0                 , 1.0f - BAR_SIZE/TEX_HEIGHT,
+        0                 , 1.0f - CAP_RADIUS/TEX_HEIGHT,
+        BAR_SIZE/TEX_WIDTH, 1.0f - BAR_SIZE/TEX_HEIGHT,
+        BAR_SIZE/TEX_WIDTH, 1.0f - CAP_RADIUS/TEX_HEIGHT
+    };
+
+    // Texture coordinates for the left cap of the scrollbar
+    private static final float[] LEFT_CAP_TEX_COORDS = {
+        // x, y
+        0                   , 1.0f - BAR_SIZE/TEX_HEIGHT,
+        0                   , 1.0f,
+        CAP_RADIUS/TEX_WIDTH, 1.0f - BAR_SIZE/TEX_HEIGHT,
+        CAP_RADIUS/TEX_WIDTH, 1.0f
+    };
+
+    // Texture coordinates for the right cap of the scrollbar
+    private static final float[] RIGHT_CAP_TEX_COORDS = {
+        // x, y
+        CAP_RADIUS/TEX_WIDTH, 1.0f - BAR_SIZE/TEX_HEIGHT,
+        CAP_RADIUS/TEX_WIDTH, 1.0f,
+        BAR_SIZE/TEX_WIDTH  , 1.0f - BAR_SIZE/TEX_HEIGHT,
+        BAR_SIZE/TEX_WIDTH  , 1.0f
+    };
+
     private ScrollbarLayer(CairoImage image, boolean vertical, ByteBuffer buffer) {
         super(false, image);
         mVertical = vertical;
         mBuffer = buffer;
 
         IntSize size = image.getSize();
         mBitmap = Bitmap.createBitmap(size.width, size.height, Bitmap.Config.ARGB_8888);
         mCanvas = new Canvas(mBitmap);
@@ -92,23 +136,23 @@ public class ScrollbarLayer extends Tile
             if (!mFinalized && mBuffer != null)
                 GeckoAppShell.freeDirectBuffer(mBuffer);
             mFinalized = true;
         } finally {
             super.finalize();
         }
     }
 
-
     public static ScrollbarLayer create(boolean vertical) {
         // just create an empty image for now, it will get drawn
         // on demand anyway
         int imageSize = IntSize.nextPowerOfTwo(BAR_SIZE);
         ByteBuffer buffer = GeckoAppShell.allocateDirectBuffer(imageSize * imageSize * 4);
-        CairoImage image = new BufferedCairoImage(buffer, imageSize, imageSize, CairoImage.FORMAT_ARGB32);
+        CairoImage image = new BufferedCairoImage(buffer, imageSize, imageSize,
+                                                  CairoImage.FORMAT_ARGB32);
         return new ScrollbarLayer(image, vertical, buffer);
     }
 
     /**
      * Decrease the opacity of the scrollbar by one frame's worth.
      * Return true if the opacity was decreased, or false if the scrollbars
      * are already fully faded out.
      */
@@ -161,46 +205,207 @@ public class ScrollbarLayer extends Tile
     }
 
     @Override
     public void draw(RenderContext context) {
         if (!initialized())
             return;
 
         try {
-            GLES11.glEnable(GL10.GL_BLEND);
-            GLES11.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
+            GLES20.glEnable(GLES20.GL_BLEND);
+            GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
 
-            Rect rect = RectUtils.round(mVertical ? getVerticalRect(context) : getHorizontalRect(context));
-            GLES11.glBindTexture(GL10.GL_TEXTURE_2D, getTextureID());
+            Rect rect = RectUtils.round(mVertical
+                                        ? getVerticalRect(context)
+                                        : getHorizontalRect(context));
+            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTextureID());
 
+            float viewWidth = context.viewport.width();
             float viewHeight = context.viewport.height();
 
-            // for the body of the scrollbar, we take a 1x1 pixel from the center of the image
-            // and scale it to become the bar
-            GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, CROPRECT_MIDPIXEL, 0);
-            GLES11Ext.glDrawTexfOES(rect.left, viewHeight - rect.bottom, 0.0f, rect.width(), rect.height());
+            float top = viewHeight - rect.top;
+            float bot = viewHeight - rect.bottom;
+
+            // Coordinates for the scrollbar's body combined with the texture coordinates
+            float[] bodyCoords = {
+                // x, y, z, texture_x, texture_y
+                rect.left/viewWidth, bot/viewHeight, 0,
+                BODY_TEX_COORDS[0], BODY_TEX_COORDS[1],
+
+                rect.left/viewWidth, (bot+rect.height())/viewHeight, 0,
+                BODY_TEX_COORDS[2], BODY_TEX_COORDS[3],
+
+                (rect.left+rect.width())/viewWidth, bot/viewHeight, 0,
+                BODY_TEX_COORDS[4], BODY_TEX_COORDS[5],
+
+                (rect.left+rect.width())/viewWidth, (bot+rect.height())/viewHeight, 0,
+                BODY_TEX_COORDS[6], BODY_TEX_COORDS[7]
+            };
+
+            // Get the buffer and handles from the context
+            FloatBuffer coordBuffer = context.coordBuffer;
+            int positionHandle = context.positionHandle;
+            int textureHandle = context.textureHandle;
+
+            // Make sure we are at position zero in the buffer in case other draw methods did not
+            // clean up after themselves
+            coordBuffer.position(0);
+            coordBuffer.put(bodyCoords);
+
+            // Vertex coordinates are x,y,z starting at position 0 into the buffer.
+            coordBuffer.position(0);
+            GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20,
+                                         coordBuffer);
+
+            // Texture coordinates are texture_x, texture_y starting at position 3 into the buffer.
+            coordBuffer.position(3);
+            GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20,
+                                         coordBuffer);
+
+            GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+
+            // Reset the position in the buffer for the next set of vertex and texture coordinates.
+            coordBuffer.position(0);
 
             if (mVertical) {
                 // top endcap
-                GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, CROPRECT_TOPCAP, 0);
-                GLES11Ext.glDrawTexfOES(rect.left, viewHeight - rect.top, 0.0f, BAR_SIZE, CAP_RADIUS);
+                float[] topCap = {
+                    // x, y, z, texture_x, texture_y
+                    rect.left/viewWidth, top/viewHeight, 0,
+                    TOP_CAP_TEX_COORDS[0], TOP_CAP_TEX_COORDS[1],
+
+                    rect.left/viewWidth, (top+CAP_RADIUS)/viewHeight, 0,
+                    TOP_CAP_TEX_COORDS[2], TOP_CAP_TEX_COORDS[3],
+
+                    (rect.left+BAR_SIZE)/viewWidth, top/viewHeight, 0,
+                    TOP_CAP_TEX_COORDS[4], TOP_CAP_TEX_COORDS[5],
+
+                    (rect.left+BAR_SIZE)/viewWidth, (top+CAP_RADIUS)/viewHeight, 0,
+                    TOP_CAP_TEX_COORDS[6], TOP_CAP_TEX_COORDS[7]
+                };
+
+                coordBuffer.put(topCap);
+
+                // Vertex coordinates are x,y,z starting at position 0 into the buffer.
+                coordBuffer.position(0);
+                GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20,
+                                             coordBuffer);
+
+                // Texture coordinates are texture_x, texture_y starting at position 3 into the
+                // buffer.
+                coordBuffer.position(3);
+                GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20,
+                                             coordBuffer);
+
+                GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+
+                // Reset the position in the buffer for the next set of vertex and texture
+                // coordinates.
+                coordBuffer.position(0);
+
                 // bottom endcap
-                GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, CROPRECT_BOTTOMCAP, 0);
-                GLES11Ext.glDrawTexfOES(rect.left, viewHeight - (rect.bottom + CAP_RADIUS), 0.0f, BAR_SIZE, CAP_RADIUS);
+                float[] botCap = {
+                    // x, y, z, texture_x, texture_y
+                    rect.left/viewWidth, (bot-CAP_RADIUS)/viewHeight, 0,
+                    BOT_CAP_TEX_COORDS[0], BOT_CAP_TEX_COORDS[1],
+
+                    rect.left/viewWidth, (bot)/viewHeight, 0,
+                    BOT_CAP_TEX_COORDS[2], BOT_CAP_TEX_COORDS[3],
+
+                    (rect.left+BAR_SIZE)/viewWidth, (bot-CAP_RADIUS)/viewHeight, 0,
+                    BOT_CAP_TEX_COORDS[4], BOT_CAP_TEX_COORDS[5],
+
+                    (rect.left+BAR_SIZE)/viewWidth, (bot)/viewHeight, 0,
+                    BOT_CAP_TEX_COORDS[6], BOT_CAP_TEX_COORDS[7]
+                };
+
+                coordBuffer.put(botCap);
+
+                // Vertex coordinates are x,y,z starting at position 0 into the buffer.
+                coordBuffer.position(0);
+                GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20,
+                                             coordBuffer);
+
+                // Texture coordinates are texture_x, texture_y starting at position 3 into the
+                // buffer.
+                coordBuffer.position(3);
+                GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20,
+                                             coordBuffer);
+
+                GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+
+                // Reset the position in the buffer for the next set of vertex and texture
+                // coordinates.
+                coordBuffer.position(0);
             } else {
                 // left endcap
-                GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, CROPRECT_LEFTCAP, 0);
-                GLES11Ext.glDrawTexfOES(rect.left - CAP_RADIUS, viewHeight - rect.bottom, 0.0f, CAP_RADIUS, BAR_SIZE);
+                float[] leftCap = {
+                    // x, y, z, texture_x, texture_y
+                    (rect.left-CAP_RADIUS)/viewWidth, bot/viewHeight, 0,
+                        LEFT_CAP_TEX_COORDS[0], LEFT_CAP_TEX_COORDS[1],
+                    (rect.left-CAP_RADIUS)/viewWidth, (bot+BAR_SIZE)/viewHeight, 0,
+                        LEFT_CAP_TEX_COORDS[2], LEFT_CAP_TEX_COORDS[3],
+                    (rect.left)/viewWidth, bot/viewHeight, 0, LEFT_CAP_TEX_COORDS[4],
+                        LEFT_CAP_TEX_COORDS[5],
+                    (rect.left)/viewWidth, (bot+BAR_SIZE)/viewHeight, 0,
+                        LEFT_CAP_TEX_COORDS[6], LEFT_CAP_TEX_COORDS[7]
+                };
+
+                coordBuffer.put(leftCap);
+
+                // Vertex coordinates are x,y,z starting at position 0 into the buffer.
+                coordBuffer.position(0);
+                GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20,
+                                             coordBuffer);
+
+                // Texture coordinates are texture_x, texture_y starting at position 3 into the
+                // buffer.
+                coordBuffer.position(3);
+                GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20,
+                                             coordBuffer);
+
+                GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+
+                // Reset the position in the buffer for the next set of vertex and texture
+                // coordinates.
+                coordBuffer.position(0);
+
                 // right endcap
-                GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, CROPRECT_RIGHTCAP, 0);
-                GLES11Ext.glDrawTexfOES(rect.right, viewHeight - rect.bottom, 0.0f, CAP_RADIUS, BAR_SIZE);
+                float[] rightCap = {
+                    // x, y, z, texture_x, texture_y
+                    rect.right/viewWidth, (bot)/viewHeight, 0,
+                    RIGHT_CAP_TEX_COORDS[0], RIGHT_CAP_TEX_COORDS[1],
+
+                    rect.right/viewWidth, (bot+BAR_SIZE)/viewHeight, 0,
+                    RIGHT_CAP_TEX_COORDS[2], RIGHT_CAP_TEX_COORDS[3],
+
+                    (rect.right+CAP_RADIUS)/viewWidth, (bot)/viewHeight, 0,
+                    RIGHT_CAP_TEX_COORDS[4], RIGHT_CAP_TEX_COORDS[5],
+
+                    (rect.right+CAP_RADIUS)/viewWidth, (bot+BAR_SIZE)/viewHeight, 0,
+                    RIGHT_CAP_TEX_COORDS[6], RIGHT_CAP_TEX_COORDS[7]
+                };
+
+                coordBuffer.put(rightCap);
+
+                // Vertex coordinates are x,y,z starting at position 0 into the buffer.
+                coordBuffer.position(0);
+                GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20,
+                                             coordBuffer);
+
+                // Texture coordinates are texture_x, texture_y starting at position 3 into the
+                // buffer.
+                coordBuffer.position(3);
+                GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20,
+                                             coordBuffer);
+
+                GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
             }
         } finally {
-            GLES11.glDisable(GL10.GL_BLEND);
+            GLES20.glDisable(GLES20.GL_BLEND);
         }
     }
 
     private RectF getVerticalRect(RenderContext context) {
         RectF viewport = context.viewport;
         FloatSize pageSize = context.pageSize;
         float barStart = (viewport.height() * viewport.top / pageSize.height) + CAP_RADIUS;
         float barEnd = (viewport.height() * viewport.bottom / pageSize.height) - CAP_RADIUS;
--- a/mobile/android/base/gfx/SingleTileLayer.java
+++ b/mobile/android/base/gfx/SingleTileLayer.java
@@ -15,16 +15,17 @@
  * The Original Code is Mozilla Android code.
  *
  * The Initial Developer of the Original Code is Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2009-2010
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Patrick Walton <pcwalton@mozilla.com>
+ *   Arkady Blyakher <rkadyb@mit.edu>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -39,21 +40,18 @@ package org.mozilla.gecko.gfx;
 
 import org.mozilla.gecko.gfx.CairoImage;
 import org.mozilla.gecko.gfx.CairoUtils;
 import org.mozilla.gecko.gfx.IntSize;
 import org.mozilla.gecko.gfx.LayerController;
 import org.mozilla.gecko.gfx.TileLayer;
 import android.graphics.PointF;
 import android.graphics.RectF;
-import android.opengl.GLES11;
-import android.opengl.GLES11Ext;
+import android.opengl.GLES20;
 import android.util.Log;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
 import java.nio.FloatBuffer;
 import javax.microedition.khronos.opengles.GL10;
 
 /**
  * Encapsulates the logic needed to draw a single textured tile.
  *
  * TODO: Repeating textures really should be their own type of layer.
  */
@@ -66,36 +64,64 @@ public class SingleTileLayer extends Til
 
     @Override
     public void draw(RenderContext context) {
         // mTextureIDs may be null here during startup if Layer.java's draw method
         // failed to acquire the transaction lock and call performUpdates.
         if (!initialized())
             return;
 
-        GLES11.glBindTexture(GL10.GL_TEXTURE_2D, getTextureID());
+        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTextureID());
 
         RectF bounds;
         int[] cropRect;
         IntSize size = getSize();
         RectF viewport = context.viewport;
 
         if (repeats()) {
             bounds = new RectF(0.0f, 0.0f, viewport.width(), viewport.height());
             int width = Math.round(viewport.width());
-            int height = Math.round(-viewport.height());
-            cropRect = new int[] { 0, size.height, width, height };
+            int height = Math.round(viewport.height());
+            cropRect = new int[] { 0, 0, width, height };
         } else {
             bounds = getBounds(context, new FloatSize(size));
-            cropRect = new int[] { 0, size.height, size.width, -size.height };
+            cropRect = new int[] { 0, 0, size.width, size.height };
         }
 
-        GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, cropRect,
-                                0);
-
         float height = bounds.height();
         float left = bounds.left - viewport.left;
         float top = viewport.height() - (bounds.top + height - viewport.top);
 
-        GLES11Ext.glDrawTexfOES(left, top, 0.0f, bounds.width(), height);
+        float[] coords = {
+            //x, y, z, texture_x, texture_y
+            left/viewport.width(), top/viewport.height(), 0,
+            cropRect[0]/(float)size.width, cropRect[1]/(float)size.height,
+
+            left/viewport.width(), (top+height)/viewport.height(), 0,
+            cropRect[0]/(float)size.width, cropRect[3]/(float)size.height,
+
+            (left+bounds.width())/viewport.width(), top/viewport.height(), 0,
+            cropRect[2]/(float)size.width, cropRect[1]/(float)size.height,
+
+            (left+bounds.width())/viewport.width(), (top+height)/viewport.height(), 0,
+            cropRect[2]/(float)size.width, cropRect[3]/(float)size.height
+        };
+
+        FloatBuffer coordBuffer = context.coordBuffer;
+        int positionHandle = context.positionHandle;
+        int textureHandle = context.textureHandle;
+
+        // Make sure we are at position zero in the buffer in case other draw methods did not clean
+        // up after themselves
+        coordBuffer.position(0);
+        coordBuffer.put(coords);
+
+        // Vertex coordinates are x,y,z starting at position 0 into the buffer.
+        coordBuffer.position(0);
+        GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20, coordBuffer);
+
+        // Texture coordinates are texture_x, texture_y starting at position 3 into the buffer.
+        coordBuffer.position(3);
+        GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20, coordBuffer);
+        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
     }
 }
 
--- a/mobile/android/base/gfx/SurfaceTextureLayer.java
+++ b/mobile/android/base/gfx/SurfaceTextureLayer.java
@@ -51,16 +51,17 @@ import android.view.Surface;
 import javax.microedition.khronos.opengles.GL10;
 import javax.microedition.khronos.opengles.GL11Ext;
 import java.nio.Buffer;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.FloatBuffer;
 import android.hardware.Camera;
 
+// TODO: Port this to GLES 2.0.
 public class SurfaceTextureLayer extends Layer implements SurfaceTexture.OnFrameAvailableListener {
     private static final String LOGTAG = "SurfaceTextureLayer";
     private static final int LOCAL_GL_TEXTURE_EXTERNAL_OES = 0x00008d65; // This is only defined in API level 15 for some reason (Android 4.0.3)
 
     private final SurfaceTexture mSurfaceTexture;
     private final Surface mSurface;
     private int mTextureId;
     private boolean mHaveFrame;
@@ -168,31 +169,31 @@ public class SurfaceTextureLayer extends
                 Log.e(LOGTAG, "some other exception while invoking release method on mSurfaceTexture", e);
             }
         }
         if (mTextureId > 0)
             TextureReaper.get().add(mTextureId);
     }
 
     @Override
-    protected boolean performUpdates(GL10 gl, RenderContext context) {
-        super.performUpdates(gl, context);
+    protected boolean performUpdates(RenderContext context) {
+        super.performUpdates(context);
 
         if (mNewSize != null) {
             mSize = mNewSize;
             mNewSize = null;
         }
 
         mInverted = mNewInverted;
         mBlend = mNewBlend;
 
-        gl.glEnable(LOCAL_GL_TEXTURE_EXTERNAL_OES);
-        gl.glBindTexture(LOCAL_GL_TEXTURE_EXTERNAL_OES, mTextureId);
+        GLES11.glEnable(LOCAL_GL_TEXTURE_EXTERNAL_OES);
+        GLES11.glBindTexture(LOCAL_GL_TEXTURE_EXTERNAL_OES, mTextureId);
         mSurfaceTexture.updateTexImage();
-        gl.glDisable(LOCAL_GL_TEXTURE_EXTERNAL_OES);
+        GLES11.glDisable(LOCAL_GL_TEXTURE_EXTERNAL_OES);
 
         // FIXME: we should return true and rely on onFrameAvailable, but
         // that isn't working for some reason
         return false;
     }
 
     private float mapToGLCoords(float input, float viewport, boolean flip) {
         if (flip) input = viewport - input;
--- a/mobile/android/base/gfx/TextureGenerator.java
+++ b/mobile/android/base/gfx/TextureGenerator.java
@@ -32,17 +32,17 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 package org.mozilla.gecko.gfx;
 
-import android.opengl.GLES10;
+import android.opengl.GLES20;
 import java.util.Stack;
 
 public class TextureGenerator {
     private static final int MIN_TEXTURES = 5;
 
     private static TextureGenerator sSharedInstance;
     private Stack<Integer> mTextureIds;
 
@@ -59,15 +59,15 @@ public class TextureGenerator {
             return 0;
 
         return (int)mTextureIds.pop();
     }
 
     public synchronized void fill() {
         int[] textures = new int[1];
         while (mTextureIds.size() < MIN_TEXTURES) {
-            GLES10.glGenTextures(1, textures, 0);
+            GLES20.glGenTextures(1, textures, 0);
             mTextureIds.push(textures[0]);
         }
     }
 }
 
 
--- a/mobile/android/base/gfx/TextureReaper.java
+++ b/mobile/android/base/gfx/TextureReaper.java
@@ -15,16 +15,17 @@
  * The Original Code is Mozilla Android code.
  *
  * The Initial Developer of the Original Code is Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2009-2010
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Patrick Walton <pcwalton@mozilla.com>
+ *   Arkady Blyakher <rkadyb@mit.edu>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -32,17 +33,17 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 package org.mozilla.gecko.gfx;
 
-import javax.microedition.khronos.opengles.GL10;
+import android.opengl.GLES20;
 import java.util.ArrayList;
 
 /** Manages a list of dead tiles, so we don't leak resources. */
 public class TextureReaper {
     private static TextureReaper sSharedInstance;
     private ArrayList<Integer> mDeadTextureIDs;
 
     private TextureReaper() { mDeadTextureIDs = new ArrayList<Integer>(); }
@@ -57,19 +58,19 @@ public class TextureReaper {
         for (int textureID : textureIDs)
             add(textureID);
     }
 
     public void add(int textureID) {
         mDeadTextureIDs.add(textureID);
     }
 
-    public void reap(GL10 gl) {
+    public void reap() {
         int[] deadTextureIDs = new int[mDeadTextureIDs.size()];
         for (int i = 0; i < deadTextureIDs.length; i++)
             deadTextureIDs[i] = mDeadTextureIDs.get(i);
         mDeadTextureIDs.clear();
 
-        gl.glDeleteTextures(deadTextureIDs.length, deadTextureIDs, 0);
+        GLES20.glDeleteTextures(deadTextureIDs.length, deadTextureIDs, 0);
     }
 }
 
 
--- a/mobile/android/base/gfx/TileLayer.java
+++ b/mobile/android/base/gfx/TileLayer.java
@@ -15,16 +15,17 @@
  * The Original Code is Mozilla Android code.
  *
  * The Initial Developer of the Original Code is Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2009-2010
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Patrick Walton <pcwalton@mozilla.com>
+ *   Arkady Blyakher <rkadyb@mit.edu>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -38,18 +39,16 @@
 package org.mozilla.gecko.gfx;
 
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
 import android.opengl.GLES20;
 import android.util.Log;
-import javax.microedition.khronos.opengles.GL10;
-import javax.microedition.khronos.opengles.GL11Ext;
 import java.nio.Buffer;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.FloatBuffer;
 
 /**
  * Base class for tile layers, which encapsulate the logic needed to draw textured tiles in OpenGL
  * ES.
@@ -101,17 +100,17 @@ public abstract class TileLayer extends 
         IntSize bufferSize = mImage.getSize();
         invalidate(new Rect(0, 0, bufferSize.width, bufferSize.height));
     }
 
     public boolean isDirty() {
         return mImage.getSize().isPositive() && (mTextureIDs == null || !mDirtyRect.isEmpty());
     }
 
-    private void validateTexture(GL10 gl) {
+    private void validateTexture() {
         /* Calculate the ideal texture size. This must be a power of two if
          * the texture is repeated or OpenGL ES 2.0 isn't supported, as
          * OpenGL ES 2.0 is required for NPOT texture support (without
          * extensions), but doesn't support repeating NPOT textures.
          *
          * XXX Currently, we don't pick a GLES 2.0 context, so always round.
          */
         IntSize bufferSize = mImage.getSize();
@@ -124,98 +123,98 @@ public abstract class TileLayer extends 
 
             // Delete the old texture
             if (mTextureIDs != null) {
                 TextureReaper.get().add(mTextureIDs);
                 mTextureIDs = null;
 
                 // Free the texture immediately, so we don't incur a
                 // temporarily increased memory usage.
-                TextureReaper.get().reap(gl);
+                TextureReaper.get().reap();
             }
         }
     }
 
     /** Tells the tile not to update the texture on the next update. */
     public void setSkipTextureUpdate(boolean skip) {
         mSkipTextureUpdate = skip;
     }
 
     public boolean getSkipTextureUpdate() {
         return mSkipTextureUpdate;
     }
 
     @Override
-    protected boolean performUpdates(GL10 gl, RenderContext context) {
-        super.performUpdates(gl, context);
+    protected boolean performUpdates(RenderContext context) {
+        super.performUpdates(context);
 
         if (mSkipTextureUpdate) {
             return false;
         }
 
         // Reallocate the texture if the size has changed
-        validateTexture(gl);
+        validateTexture();
 
         // Don't do any work if the image has an invalid size.
         if (!mImage.getSize().isPositive())
             return true;
 
         // If we haven't allocated a texture, assume the whole region is dirty
         if (mTextureIDs == null) {
-            uploadFullTexture(gl);
+            uploadFullTexture();
         } else {
-            uploadDirtyRect(gl, mDirtyRect);
+            uploadDirtyRect(mDirtyRect);
         }
 
         mDirtyRect.setEmpty();
 
         return true;
     }
 
-    private void uploadFullTexture(GL10 gl) {
+    private void uploadFullTexture() {
         IntSize bufferSize = mImage.getSize();
-        uploadDirtyRect(gl, new Rect(0, 0, bufferSize.width, bufferSize.height));
+        uploadDirtyRect(new Rect(0, 0, bufferSize.width, bufferSize.height));
     }
 
-    private void uploadDirtyRect(GL10 gl, Rect dirtyRect) {
+    private void uploadDirtyRect(Rect dirtyRect) {
         // If we have nothing to upload, just return for now
         if (dirtyRect.isEmpty())
             return;
 
         // It's possible that the buffer will be null, check for that and return
         ByteBuffer imageBuffer = mImage.getBuffer();
         if (imageBuffer == null)
             return;
 
         boolean newlyCreated = false;
 
         if (mTextureIDs == null) {
             mTextureIDs = new int[1];
-            gl.glGenTextures(mTextureIDs.length, mTextureIDs, 0);
+            GLES20.glGenTextures(mTextureIDs.length, mTextureIDs, 0);
             newlyCreated = true;
         }
 
         IntSize bufferSize = mImage.getSize();
         Rect bufferRect = new Rect(0, 0, bufferSize.width, bufferSize.height);
 
         int cairoFormat = mImage.getFormat();
         CairoGLInfo glInfo = new CairoGLInfo(cairoFormat);
 
-        bindAndSetGLParameters(gl);
+        bindAndSetGLParameters();
 
         if (newlyCreated || dirtyRect.contains(bufferRect)) {
             if (mSize.equals(bufferSize)) {
-                gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, glInfo.internalFormat, mSize.width, mSize.height,
-                                0, glInfo.format, glInfo.type, imageBuffer);
+                GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, glInfo.internalFormat, mSize.width,
+                                    mSize.height, 0, glInfo.format, glInfo.type, imageBuffer);
                 return;
             } else {
-                gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, glInfo.internalFormat, mSize.width, mSize.height,
-                                0, glInfo.format, glInfo.type, null);
-                gl.glTexSubImage2D(gl.GL_TEXTURE_2D, 0, 0, 0, bufferSize.width, bufferSize.height,
-                                   glInfo.format, glInfo.type, imageBuffer);
+                GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, glInfo.internalFormat, mSize.width,
+                                    mSize.height, 0, glInfo.format, glInfo.type, null);
+                GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, bufferSize.width,
+                                       bufferSize.height, glInfo.format, glInfo.type, imageBuffer);
                 return;
             }
         }
 
         // Make sure that the dirty region intersects with the buffer rect,
         // otherwise we'll end up with an invalid buffer pointer.
         if (!Rect.intersects(dirtyRect, bufferRect)) {
             return;
@@ -232,24 +231,26 @@ public abstract class TileLayer extends 
         int bpp = CairoUtils.bitsPerPixelForCairoFormat(cairoFormat) / 8;
         int position = dirtyRect.top * bufferSize.width * bpp;
         if (position > viewBuffer.limit()) {
             Log.e(LOGTAG, "### Position outside tile! " + dirtyRect.top);
             return;
         }
 
         viewBuffer.position(position);
-        gl.glTexSubImage2D(gl.GL_TEXTURE_2D, 0, 0, dirtyRect.top, bufferSize.width,
-                           Math.min(bufferSize.height - dirtyRect.top, dirtyRect.height()),
-                           glInfo.format, glInfo.type, viewBuffer);
+        GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, dirtyRect.top, bufferSize.width,
+                               Math.min(bufferSize.height - dirtyRect.top, dirtyRect.height()),
+                               glInfo.format, glInfo.type, viewBuffer);
     }
 
-    private void bindAndSetGLParameters(GL10 gl) {
-        gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIDs[0]);
-        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
-        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
+    private void bindAndSetGLParameters() {
+        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIDs[0]);
+        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
+                               GLES20.GL_NEAREST);
+        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
+                               GLES20.GL_LINEAR);
 
-        int repeatMode = mRepeat ? GL10.GL_REPEAT : GL10.GL_CLAMP_TO_EDGE;
-        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, repeatMode);
-        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, repeatMode);
+        int repeatMode = mRepeat ? GLES20.GL_REPEAT : GLES20.GL_CLAMP_TO_EDGE;
+        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, repeatMode);
+        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, repeatMode);
     }
 }
 
--- a/mobile/android/base/gfx/VirtualLayer.java
+++ b/mobile/android/base/gfx/VirtualLayer.java
@@ -34,17 +34,16 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 package org.mozilla.gecko.gfx;
 
 import android.graphics.Point;
-import javax.microedition.khronos.opengles.GL10;
 
 public class VirtualLayer extends Layer {
     private Listener mListener;
     private IntSize mSize;
 
     public void setListener(Listener listener) {
         mListener = listener;
     }
@@ -59,19 +58,19 @@ public class VirtualLayer extends Layer 
         return mSize;
     }
 
     public void setSize(IntSize size) {
         mSize = size;
     }
 
     @Override
-    protected boolean performUpdates(GL10 gl, RenderContext context) {
+    protected boolean performUpdates(RenderContext context) {
         boolean dimensionsChanged = dimensionChangesPending();
-        boolean result = super.performUpdates(gl, context);
+        boolean result = super.performUpdates(context);
         if (dimensionsChanged && mListener != null) {
             mListener.dimensionsChanged(getOrigin(), getResolution());
         }
 
         return result;
     }
 
     public interface Listener {
--- a/mobile/android/base/gfx/WidgetTileLayer.java
+++ b/mobile/android/base/gfx/WidgetTileLayer.java
@@ -15,16 +15,17 @@
  * The Original Code is Mozilla Android code.
  *
  * The Initial Developer of the Original Code is Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2009-2010
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   James Willcox <jwillcox@mozilla.com>
+ *   Arkady Blyakher <rkadyb@mit.edu>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -35,21 +36,20 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 package org.mozilla.gecko.gfx;
 
 import org.mozilla.gecko.gfx.LayerController;
 import org.mozilla.gecko.gfx.SingleTileLayer;
 import org.mozilla.gecko.GeckoAppShell;
-import android.opengl.GLES11;
-import android.opengl.GLES11Ext;
 import android.graphics.RectF;
 import android.util.Log;
-import javax.microedition.khronos.opengles.GL10;
+import android.opengl.GLES20;
+import java.nio.FloatBuffer;
 
 /**
  * Encapsulates the logic needed to draw the single-tiled Gecko texture
  */
 public class WidgetTileLayer extends Layer {
     private static final String LOGTAG = "WidgetTileLayer";
 
     private int[] mTextureIDs;
@@ -60,69 +60,100 @@ public class WidgetTileLayer extends Lay
     }
 
     protected boolean initialized() { return mTextureIDs != null; }
 
     @Override
     public IntSize getSize() { return mImage.getSize(); }
 
     protected void bindAndSetGLParameters() {
-        GLES11.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIDs[0]);
-        GLES11.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
-        GLES11.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
+        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIDs[0]);
+        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
+        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
     }
 
     @Override
     protected void finalize() throws Throwable {
         if (mTextureIDs != null)
             TextureReaper.get().add(mTextureIDs);
     }
 
     @Override
-    protected boolean performUpdates(GL10 gl, RenderContext context) {
-        super.performUpdates(gl, context);
+    protected boolean performUpdates(RenderContext context) {
+        super.performUpdates(context);
 
         if (mTextureIDs == null) {
             mTextureIDs = new int[1];
-            GLES11.glGenTextures(1, mTextureIDs, 0);
+            GLES20.glGenTextures(1, mTextureIDs, 0);
         }
 
         bindAndSetGLParameters();
         GeckoAppShell.bindWidgetTexture();
 
         return true;
     }
 
     @Override
     public void draw(RenderContext context) {
         // mTextureIDs may be null here during startup if Layer.java's draw method
         // failed to acquire the transaction lock and call performUpdates.
         if (!initialized())
             return;
 
-        GLES11.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIDs[0]);
+        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIDs[0]);
 
         RectF bounds;
         int[] cropRect;
         IntSize size = getSize();
         RectF viewport = context.viewport;
 
         bounds = getBounds(context, new FloatSize(size));
-        cropRect = new int[] { 0, size.height, size.width, -size.height };
+        cropRect = new int[] { 0, 0, size.width, size.height };
         bounds.offset(-viewport.left, -viewport.top);
 
-        GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, cropRect,
-                                0);
-
         float top = viewport.height() - (bounds.top + bounds.height());
 
         // There may be errors from a previous GL call, so clear them first because
         // we want to check for one below
-        while (GLES11.glGetError() != GLES11.GL_NO_ERROR);
+        while (GLES20.glGetError() != GLES20.GL_NO_ERROR);
+
+        float[] coords = {
+            //x, y, z, texture_x, texture_y
+            bounds.left/viewport.width(), top/viewport.height(), 0,
+            cropRect[0]/size.width, cropRect[1]/size.height,
+
+            bounds.left/viewport.width(), (top+bounds.height())/viewport.height(), 0,
+            cropRect[0]/size.width, cropRect[3]/size.height,
+
+            (bounds.left+bounds.width())/viewport.width(), top/viewport.height(), 0,
+            cropRect[2]/size.width, cropRect[1]/size.height,
+
+            (bounds.left+bounds.width())/viewport.width(), (top+bounds.height())/viewport.height(),
+                0,
+            cropRect[2]/size.width, cropRect[3]/size.height
+        };
 
-        GLES11Ext.glDrawTexfOES(bounds.left, top, 0.0f, bounds.width(), bounds.height());
-        int error = GLES11.glGetError();
-        if (error != GLES11.GL_NO_ERROR) {
+        // Get the buffer and handles from the context
+        FloatBuffer coordBuffer = context.coordBuffer;
+        int positionHandle = context.positionHandle;
+        int textureHandle = context.textureHandle;
+
+        // Make sure we are at position zero in the buffer in case other draw methods did not clean
+        // up after themselves
+        coordBuffer.position(0);
+        coordBuffer.put(coords);
+
+        // Vertex coordinates are x,y,z starting at position 0 into the buffer.
+        coordBuffer.position(0);
+        GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20, coordBuffer);
+
+        // Texture coordinates are texture_x, texture_y starting at position 3 into the buffer.
+        coordBuffer.position(3);
+        GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20, coordBuffer);
+        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+
+        int error = GLES20.glGetError();
+        if (error != GLES20.GL_NO_ERROR) {
             Log.i(LOGTAG, "Failed to draw texture: " + error);
         }
     }
 }