Use NEAREST filtering for layers whenever possible.

Change-Id: Id5bee1bd4a322cf93e8000b08e18f1e1b058648e
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index b6c5522..4987e2f 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -246,8 +246,19 @@
         return nIsBackBufferPreserved();
     }
 
-    private static native boolean nIsBackBufferPreserved();    
-    
+    private static native boolean nIsBackBufferPreserved();
+
+    /**
+     * Disables v-sync. For performance testing only.
+     * 
+     * @hide
+     */
+    public static void disableVsync() {
+        nDisableVsync();
+    }
+
+    private static native void nDisableVsync();
+
     @Override
     void onPreDraw(Rect dirty) {
         if (dirty != null) {
@@ -265,7 +276,7 @@
     void onPostDraw() {
         nFinish(mRenderer);
     }
-    
+
     private static native void nFinish(int renderer);
 
     @Override
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index bbfb4c1..188970c 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -57,6 +57,16 @@
      * "false", to disable partial invalidates
      */
     static final String RENDER_DIRTY_REGIONS_PROPERTY = "hwui.render_dirty_regions";
+    
+    /**
+     * System property used to enable or disable vsync.
+     * The default value of this property is assumed to be false.
+     * 
+     * Possible values:
+     * "true", to disable vsync
+     * "false", to enable vsync
+     */
+    static final String DISABLE_VSYNC_PROPERTY = "hwui.disable_vsync";
 
     /**
      * Turn on to draw dirty regions every other frame.
@@ -303,6 +313,7 @@
 
         boolean mDirtyRegions;
         final boolean mDirtyRegionsRequested;
+        final boolean mVsyncDisabled;
 
         final int mGlVersion;
         final boolean mTranslucent;
@@ -314,10 +325,17 @@
         GlRenderer(int glVersion, boolean translucent) {
             mGlVersion = glVersion;
             mTranslucent = translucent;
+
             final String dirtyProperty = SystemProperties.get(RENDER_DIRTY_REGIONS_PROPERTY, "true");
             //noinspection PointlessBooleanExpression,ConstantConditions
             mDirtyRegions = RENDER_DIRTY_REGIONS && "true".equalsIgnoreCase(dirtyProperty);
             mDirtyRegionsRequested = mDirtyRegions;
+
+            final String vsyncProperty = SystemProperties.get(DISABLE_VSYNC_PROPERTY, "false");
+            mVsyncDisabled = "true".equalsIgnoreCase(vsyncProperty);
+            if (mVsyncDisabled) {
+                Log.d(LOG_TAG, "Disabling v-sync");
+            }
         }
 
         /**
@@ -765,6 +783,14 @@
         }
 
         @Override
+        void setup(int width, int height) {
+            super.setup(width, height);
+            if (mVsyncDisabled) {
+                GLES20Canvas.disableVsync();
+            }
+        }
+
+        @Override
         DisplayList createDisplayList(View v) {
             return new GLES20DisplayList(v);
         }
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index 2106eb4..681f43f 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -115,6 +115,18 @@
     return error == EGL_SUCCESS && value == EGL_BUFFER_PRESERVED;
 }
 
+static void android_view_GLES20Canvas_disableVsync(JNIEnv* env, jobject clazz) {
+    EGLDisplay display = eglGetCurrentDisplay();
+
+    eglGetError();
+    eglSwapInterval(display, 0);
+
+    EGLint error = eglGetError();
+    if (error != EGL_SUCCESS) {
+        RENDERER_LOGD("Could not disable v-sync (%x)", error);
+    }
+}
+
 // ----------------------------------------------------------------------------
 // Constructors
 // ----------------------------------------------------------------------------
@@ -621,7 +633,7 @@
 
     if (layer) {
         jint* storage = env->GetIntArrayElements(layerInfo, NULL);
-        storage[0] = layer->texture;
+        storage[0] = layer->getTexture();
         env->ReleaseIntArrayElements(layerInfo, storage, 0);
     }
 
@@ -634,8 +646,8 @@
 
     if (layer) {
         jint* storage = env->GetIntArrayElements(layerInfo, NULL);
-        storage[0] = layer->width;
-        storage[1] = layer->height;
+        storage[0] = layer->getWidth();
+        storage[1] = layer->getHeight();
         env->ReleaseIntArrayElements(layerInfo, storage, 0);
     }
 
@@ -647,8 +659,8 @@
     LayerRenderer::resizeLayer(layer, width, height);
 
     jint* storage = env->GetIntArrayElements(layerInfo, NULL);
-    storage[0] = layer->width;
-    storage[1] = layer->height;
+    storage[0] = layer->getWidth();
+    storage[1] = layer->getHeight();
     env->ReleaseIntArrayElements(layerInfo, storage, 0);
 }
 
@@ -722,6 +734,7 @@
 #ifdef USE_OPENGL_RENDERER
     { "nIsBackBufferPreserved", "()Z",         (void*) android_view_GLES20Canvas_isBackBufferPreserved },
     { "nPreserveBackBuffer",    "()Z",         (void*) android_view_GLES20Canvas_preserveBackBuffer },
+    { "nDisableVsync",          "()V",         (void*) android_view_GLES20Canvas_disableVsync },
 
     { "nCreateRenderer",    "()I",             (void*) android_view_GLES20Canvas_createRenderer },
     { "nDestroyRenderer",   "(I)V",            (void*) android_view_GLES20Canvas_destroyRenderer },
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index 0310bc3..3c2d80d 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -27,6 +27,7 @@
 
 #include "Rect.h"
 #include "SkiaColorFilter.h"
+#include "Texture.h"
 #include "Vertex.h"
 
 namespace android {
@@ -40,14 +41,18 @@
  * A layer has dimensions and is backed by an OpenGL texture or FBO.
  */
 struct Layer {
-    Layer(const uint32_t layerWidth, const uint32_t layerHeight):
-            width(layerWidth), height(layerHeight) {
+    Layer(const uint32_t layerWidth, const uint32_t layerHeight) {
         mesh = NULL;
         meshIndices = NULL;
         meshElementCount = 0;
-        isCacheable = true;
-        isTextureLayer = false;
+        cacheable = true;
+        textureLayer = false;
         renderTarget = GL_TEXTURE_2D;
+        texture.width = layerWidth;
+        texture.height = layerHeight;
+        colorFilter = NULL;
+        firstFilter = true;
+        firstWrap = true;
     }
 
     ~Layer() {
@@ -64,12 +69,152 @@
         regionRect.set(bounds.leftTop().x, bounds.leftTop().y,
                bounds.rightBottom().x, bounds.rightBottom().y);
 
-        const float texX = 1.0f / float(width);
-        const float texY = 1.0f / float(height);
+        const float texX = 1.0f / float(texture.width);
+        const float texY = 1.0f / float(texture.height);
         const float height = layer.getHeight();
         texCoords.set(
                regionRect.left * texX, (height - regionRect.top) * texY,
                regionRect.right * texX, (height - regionRect.bottom) * texY);
+
+        regionRect.translate(layer.left, layer.top);
+    }
+
+    inline uint32_t getWidth() {
+        return texture.width;
+    }
+
+    inline uint32_t getHeight() {
+        return texture.height;
+    }
+
+    void setSize(uint32_t width, uint32_t height) {
+        texture.width = width;
+        texture.height = height;
+    }
+
+    inline void setBlend(bool blend) {
+        texture.blend = blend;
+    }
+
+    inline bool isBlend() {
+        return texture.blend;
+    }
+
+    inline void setAlpha(int alpha) {
+        this->alpha = alpha;
+    }
+
+    inline void setAlpha(int alpha, SkXfermode::Mode mode) {
+        this->alpha = alpha;
+        this->mode = mode;
+    }
+
+    inline int getAlpha() {
+        return alpha;
+    }
+
+    inline SkXfermode::Mode getMode() {
+        return mode;
+    }
+
+    inline void setEmpty(bool empty) {
+        this->empty = empty;
+    }
+
+    inline bool isEmpty() {
+        return empty;
+    }
+
+    inline void setFbo(GLuint fbo) {
+        this->fbo = fbo;
+    }
+
+    inline GLuint getFbo() {
+        return fbo;
+    }
+
+    inline GLuint* getTexturePointer() {
+        return &texture.id;
+    }
+
+    inline GLuint getTexture() {
+        return texture.id;
+    }
+
+    inline GLenum getRenderTarget() {
+        return renderTarget;
+    }
+
+    inline void setRenderTarget(GLenum renderTarget) {
+        this->renderTarget = renderTarget;
+    }
+
+    void setWrap(GLenum wrapS, GLenum wrapT, bool bindTexture = false, bool force = false) {
+        if (firstWrap || force || wrapS != texture.wrapS || wrapT != texture.wrapT) {
+            firstWrap = true;
+            texture.setWrap(wrapS, wrapT);
+            if (bindTexture) {
+                glBindTexture(renderTarget, texture.id);
+            }
+            glTexParameteri(renderTarget, GL_TEXTURE_WRAP_S, wrapS);
+            glTexParameteri(renderTarget, GL_TEXTURE_WRAP_T, wrapT);
+        }
+    }
+
+    void setFilter(GLenum min, GLenum mag, bool bindTexture = false, bool force = false) {
+        if (firstFilter || force || min != texture.minFilter || mag != texture.magFilter) {
+            firstFilter = false;
+            texture.setFilter(min, mag);
+            if (bindTexture) {
+                glBindTexture(renderTarget, texture.id);
+            }
+            glTexParameteri(renderTarget, GL_TEXTURE_MIN_FILTER, min);
+            glTexParameteri(renderTarget, GL_TEXTURE_MAG_FILTER, mag);
+        }
+    }
+
+    inline bool isCacheable() {
+        return cacheable;
+    }
+
+    inline void setCacheable(bool cacheable) {
+        this->cacheable = cacheable;
+    }
+
+    inline bool isTextureLayer() {
+        return textureLayer;
+    }
+
+    inline void setTextureLayer(bool textureLayer) {
+        this->textureLayer = textureLayer;
+    }
+
+    inline SkiaColorFilter* getColorFilter() {
+        return colorFilter;
+    }
+
+    inline void setColorFilter(SkiaColorFilter* filter) {
+        colorFilter = filter;
+    }
+
+    inline void bindTexture() {
+        glBindTexture(renderTarget, texture.id);
+    }
+
+    inline void generateTexture() {
+        glGenTextures(1, &texture.id);
+    }
+
+    inline void deleteTexture() {
+        if (texture.id) glDeleteTextures(1, &texture.id);
+    }
+
+    inline void allocateTexture(GLenum format, GLenum storage) {
+        glTexImage2D(renderTarget, 0, format, getWidth(), getHeight(), 0, format, storage, NULL);
+    }
+
+    inline mat4& getTexTransform() {
+        return texTransform;
     }
 
     /**
@@ -82,43 +227,6 @@
     Rect texCoords;
 
     /**
-     * Name of the FBO used to render the layer. If the name is 0
-     * this layer is not backed by an FBO, but a simple texture.
-     */
-    GLuint fbo;
-
-    /**
-     * Opacity of the layer.
-     */
-    int alpha;
-    /**
-     * Blending mode of the layer.
-     */
-    SkXfermode::Mode mode;
-    /**
-     * Indicates whether this layer should be blended.
-     */
-    bool blend;
-
-    /**
-     * Indicates whether this layer has been used already.
-     */
-    bool empty;
-
-    /**
-     * Name of the texture used to render the layer.
-     */
-    GLuint texture;
-    /**
-     * Width of the layer texture.
-     */
-    uint32_t width;
-    /**
-     * Height of the layer texture.
-     */
-    uint32_t height;
-
-    /**
      * Dirty region indicating what parts of the layer
      * have been drawn.
      */
@@ -130,37 +238,66 @@
     Rect regionRect;
 
     /**
-     * Color filter used to draw this layer. Optional.
-     */
-    SkiaColorFilter* colorFilter;
-
-    /**
      * If the layer can be rendered as a mesh, this is non-null.
      */
     TextureVertex* mesh;
     uint16_t* meshIndices;
     GLsizei meshElementCount;
 
+private:
+    /**
+     * Name of the FBO used to render the layer. If the name is 0
+     * this layer is not backed by an FBO, but a simple texture.
+     */
+    GLuint fbo;
+
+    /**
+     * Indicates whether this layer has been used already.
+     */
+    bool empty;
+
+    /**
+     * The texture backing this layer.
+     */
+    Texture texture;
+
     /**
      * If set to true (by default), the layer can be reused.
      */
-    bool isCacheable;
+    bool cacheable;
 
     /**
      * When set to true, this layer must be treated as a texture
      * layer.
      */
-    bool isTextureLayer;
+    bool textureLayer;
+
+    /**
+     * Indicates the render target.
+     */
+    GLenum renderTarget;
+
+    /**
+     * Color filter used to draw this layer. Optional.
+     */
+    SkiaColorFilter* colorFilter;
+
+    /**
+     * Opacity of the layer.
+     */
+    int alpha;
+    /**
+     * Blending mode of the layer.
+     */
+    SkXfermode::Mode mode;
 
     /**
      * Optional texture coordinates transform.
      */
     mat4 texTransform;
 
-    /**
-     * Indicates the render target.
-     */
-    GLenum renderTarget;
+    bool firstFilter;
+    bool firstWrap;
 }; // struct Layer
 
 }; // namespace uirenderer
diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp
index b2d795f..1a15e87 100644
--- a/libs/hwui/LayerCache.cpp
+++ b/libs/hwui/LayerCache.cpp
@@ -68,8 +68,8 @@
 
 void LayerCache::deleteLayer(Layer* layer) {
     if (layer) {
-        mSize -= layer->width * layer->height * 4;
-        glDeleteTextures(1, &layer->texture);
+        mSize -= layer->getWidth() * layer->getHeight() * 4;
+        layer->deleteTexture();
         delete layer;
     }
 }
@@ -93,29 +93,23 @@
         mCache.removeAt(index);
 
         layer = entry.mLayer;
-        mSize -= layer->width * layer->height * 4;
+        mSize -= layer->getWidth() * layer->getHeight() * 4;
 
-        LAYER_LOGD("Reusing layer %dx%d", layer->width, layer->height);
+        LAYER_LOGD("Reusing layer %dx%d", layer->getWidth(), layer->getHeight());
     } else {
         LAYER_LOGD("Creating new layer %dx%d", entry.mWidth, entry.mHeight);
 
         layer = new Layer(entry.mWidth, entry.mHeight);
-        layer->blend = true;
-        layer->empty = true;
-        layer->fbo = 0;
-        layer->colorFilter = NULL;
+        layer->setBlend(true);
+        layer->setEmpty(true);
+        layer->setFbo(0);
 
-        glGenTextures(1, &layer->texture);
-        glBindTexture(GL_TEXTURE_2D, layer->texture);
-
+        layer->generateTexture();
+        layer->bindTexture();
+        layer->setFilter(GL_NEAREST, GL_NEAREST);
+        layer->setWrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE);
         glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
 
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-
 #if DEBUG_LAYERS
         size_t size = mCache.size();
         for (size_t i = 0; i < size; i++) {
@@ -133,30 +127,30 @@
     //       size already in the cache, and reuse it instead of creating a new one
 
     LayerEntry entry(width, height);
-    if (entry.mWidth <= layer->width && entry.mHeight <= layer->height) {
+    if (entry.mWidth <= layer->getWidth() && entry.mHeight <= layer->getHeight()) {
         return true;
     }
 
-    glActiveTexture(GL_TEXTURE0);
-    glBindTexture(GL_TEXTURE_2D, layer->texture);
+    uint32_t oldWidth = layer->getWidth();
+    uint32_t oldHeight = layer->getHeight();
 
-    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, entry.mWidth, entry.mHeight, 0,
-            GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+    glActiveTexture(GL_TEXTURE0);
+    layer->bindTexture();
+    layer->setSize(entry.mWidth, entry.mHeight);
+    layer->allocateTexture(GL_RGBA, GL_UNSIGNED_BYTE);
 
     if (glGetError() != GL_NO_ERROR) {
+        layer->setSize(oldWidth, oldHeight);
         return false;
     }
 
-    layer->width = entry.mWidth;
-    layer->height = entry.mHeight;
-
     return true;
 }
 
 bool LayerCache::put(Layer* layer) {
-    if (!layer->isCacheable) return false;
+    if (!layer->isCacheable()) return false;
 
-    const uint32_t size = layer->width * layer->height * 4;
+    const uint32_t size = layer->getWidth() * layer->getHeight() * 4;
     // Don't even try to cache a layer that's bigger than the cache
     if (size < mMaxSize) {
         // TODO: Use an LRU
diff --git a/libs/hwui/LayerCache.h b/libs/hwui/LayerCache.h
index d2d5f39..81b8bf3 100644
--- a/libs/hwui/LayerCache.h
+++ b/libs/hwui/LayerCache.h
@@ -119,7 +119,7 @@
         }
 
         LayerEntry(Layer* layer):
-            mLayer(layer), mWidth(layer->width), mHeight(layer->height) {
+            mLayer(layer), mWidth(layer->getWidth()), mHeight(layer->getHeight()) {
         }
 
         bool operator<(const LayerEntry& rhs) const {
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index e034a86..44f2a40 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -32,9 +32,9 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 void LayerRenderer::prepareDirty(float left, float top, float right, float bottom, bool opaque) {
-    LAYER_RENDERER_LOGD("Rendering into layer, fbo = %d", mLayer->fbo);
+    LAYER_RENDERER_LOGD("Rendering into layer, fbo = %d", mLayer->getFbo());
 
-    glBindFramebuffer(GL_FRAMEBUFFER, mLayer->fbo);
+    glBindFramebuffer(GL_FRAMEBUFFER, mLayer->getFbo());
 
     const float width = mLayer->layer.getWidth();
     const float height = mLayer->layer.getHeight();
@@ -62,14 +62,14 @@
 
     generateMesh();
 
-    LAYER_RENDERER_LOGD("Finished rendering into layer, fbo = %d", mLayer->fbo);
+    LAYER_RENDERER_LOGD("Finished rendering into layer, fbo = %d", mLayer->getFbo());
 
     // No need to unbind our FBO, this will be taken care of by the caller
     // who will invoke OpenGLRenderer::resume()
 }
 
 GLint LayerRenderer::getTargetFbo() {
-    return mLayer->fbo;
+    return mLayer->getFbo();
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -128,8 +128,8 @@
     }
     mLayer->meshElementCount = elementCount;
 
-    const float texX = 1.0f / float(mLayer->width);
-    const float texY = 1.0f / float(mLayer->height);
+    const float texX = 1.0f / float(mLayer->getWidth());
+    const float texY = 1.0f / float(mLayer->getHeight());
     const float height = mLayer->layer.getHeight();
 
     TextureVertex* mesh = mLayer->mesh;
@@ -182,40 +182,41 @@
         return NULL;
     }
 
-    layer->fbo = fbo;
+    layer->setFbo(fbo);
     layer->layer.set(0.0f, 0.0f, width, height);
-    layer->texCoords.set(0.0f, height / float(layer->height),
-            width / float(layer->width), 0.0f);
-    layer->alpha = 255;
-    layer->mode = SkXfermode::kSrcOver_Mode;
-    layer->blend = !isOpaque;
-    layer->colorFilter = NULL;
+    layer->texCoords.set(0.0f, height / float(layer->getHeight()),
+            width / float(layer->getWidth()), 0.0f);
+    layer->setAlpha(255, SkXfermode::kSrcOver_Mode);
+    layer->setBlend(!isOpaque);
+    layer->setColorFilter(NULL);
     layer->region.clear();
 
     GLuint previousFbo;
     glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*) &previousFbo);
 
-    glBindFramebuffer(GL_FRAMEBUFFER, layer->fbo);
-    glBindTexture(GL_TEXTURE_2D, layer->texture);
+    glBindFramebuffer(GL_FRAMEBUFFER, layer->getFbo());
+    layer->bindTexture();
 
     // Initialize the texture if needed
-    if (layer->empty) {
-        layer->empty = false;
-        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, layer->width, layer->height, 0,
-                GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+    if (layer->isEmpty()) {
+        layer->setEmpty(false);
+        layer->allocateTexture(GL_RGBA, GL_UNSIGNED_BYTE);
 
         if (glGetError() != GL_NO_ERROR) {
             LOGD("Could not allocate texture");
+
             glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
-            glDeleteTextures(1, &layer->texture);
             Caches::getInstance().fboCache.put(fbo);
+
+            layer->deleteTexture();
             delete layer;
+
             return NULL;
         }
     }
 
     glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
-            layer->texture, 0);
+            layer->getTexture(), 0);
 
     glDisable(GL_SCISSOR_TEST);
     glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
@@ -229,14 +230,14 @@
 
 bool LayerRenderer::resizeLayer(Layer* layer, uint32_t width, uint32_t height) {
     if (layer) {
-        LAYER_RENDERER_LOGD("Resizing layer fbo = %d to %dx%d", layer->fbo, width, height);
+        LAYER_RENDERER_LOGD("Resizing layer fbo = %d to %dx%d", layer->getFbo(), width, height);
 
         if (Caches::getInstance().layerCache.resize(layer, width, height)) {
             layer->layer.set(0.0f, 0.0f, width, height);
-            layer->texCoords.set(0.0f, height / float(layer->height),
-                    width / float(layer->width), 0.0f);
+            layer->texCoords.set(0.0f, height / float(layer->getHeight()),
+                    width / float(layer->getWidth()), 0.0f);
         } else {
-            if (layer->texture) glDeleteTextures(1, &layer->texture);
+            layer->deleteTexture();
             delete layer;
             return false;
         }
@@ -245,37 +246,23 @@
     return true;
 }
 
-static void setTextureParameters(Layer* layer) {
-    glBindTexture(layer->renderTarget, layer->texture);
-
-    glTexParameteri(layer->renderTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-    glTexParameteri(layer->renderTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-
-    glTexParameteri(layer->renderTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-    glTexParameteri(layer->renderTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-}
-
 Layer* LayerRenderer::createTextureLayer(bool isOpaque) {
     LAYER_RENDERER_LOGD("Creating new texture layer");
 
     Layer* layer = new Layer(0, 0);
-    layer->isCacheable = false;
-    layer->isTextureLayer = true;
-    layer->blend = !isOpaque;
-    layer->empty = true;
-    layer->fbo = 0;
-    layer->colorFilter = NULL;
-    layer->fbo = 0;
+    layer->setCacheable(false);
+    layer->setTextureLayer(true);
+    layer->setBlend(!isOpaque);
+    layer->setEmpty(true);
+    layer->setFbo(0);
+    layer->setAlpha(255, SkXfermode::kSrcOver_Mode);
     layer->layer.set(0.0f, 0.0f, 0.0f, 0.0f);
     layer->texCoords.set(0.0f, 1.0f, 0.0f, 1.0f);
-    layer->alpha = 255;
-    layer->mode = SkXfermode::kSrcOver_Mode;
-    layer->colorFilter = NULL;
     layer->region.clear();
-    layer->renderTarget = GL_NONE; // see ::updateTextureLayer()
+    layer->setRenderTarget(GL_NONE); // see ::updateTextureLayer()
 
     glActiveTexture(GL_TEXTURE0);
-    glGenTextures(1, &layer->texture);
+    layer->generateTexture();
 
     return layer;
 }
@@ -283,31 +270,32 @@
 void LayerRenderer::updateTextureLayer(Layer* layer, uint32_t width, uint32_t height,
         bool isOpaque, GLenum renderTarget, float* transform) {
     if (layer) {
-        layer->blend = !isOpaque;
-        layer->width = width;
-        layer->height = height;
+        layer->setBlend(!isOpaque);
+        layer->setSize(width, height);
         layer->layer.set(0.0f, 0.0f, width, height);
         layer->region.set(width, height);
         layer->regionRect.set(0.0f, 0.0f, width, height);
-        layer->texTransform.load(transform);
+        layer->getTexTransform().load(transform);
 
-        if (renderTarget != layer->renderTarget) {
-            layer->renderTarget = renderTarget;
-            setTextureParameters(layer);
+        if (renderTarget != layer->getRenderTarget()) {
+            layer->setRenderTarget(renderTarget);
+            layer->bindTexture();
+            layer->setFilter(GL_NEAREST, GL_NEAREST, false, true);
+            layer->setWrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, false, true);
         }
     }
 }
 
 void LayerRenderer::destroyLayer(Layer* layer) {
     if (layer) {
-        LAYER_RENDERER_LOGD("Destroying layer, fbo = %d", layer->fbo);
+        LAYER_RENDERER_LOGD("Destroying layer, fbo = %d", layer->getFbo());
 
-        if (layer->fbo) {
-            Caches::getInstance().fboCache.put(layer->fbo);
+        if (layer->getFbo()) {
+            Caches::getInstance().fboCache.put(layer->getFbo());
         }
 
         if (!Caches::getInstance().layerCache.put(layer)) {
-            if (layer->texture) glDeleteTextures(1, &layer->texture);
+            layer->deleteTexture();
             delete layer;
         } else {
             layer->region.clear();
@@ -317,7 +305,7 @@
 
 void LayerRenderer::destroyLayerDeferred(Layer* layer) {
     if (layer) {
-        LAYER_RENDERER_LOGD("Deferring layer destruction, fbo = %d", layer->fbo);
+        LAYER_RENDERER_LOGD("Deferring layer destruction, fbo = %d", layer->getFbo());
 
         Caches::getInstance().deleteLayerDeferred(layer);
     }
@@ -325,7 +313,7 @@
 
 bool LayerRenderer::copyLayer(Layer* layer, SkBitmap* bitmap) {
     Caches& caches = Caches::getInstance();
-    if (layer && layer->isTextureLayer && bitmap->width() <= caches.maxTextureSize &&
+    if (layer && layer->isTextureLayer() && bitmap->width() <= caches.maxTextureSize &&
             bitmap->height() <= caches.maxTextureSize) {
 
         GLuint fbo = caches.fboCache.get();
@@ -365,12 +353,11 @@
                 break;
         }
 
-        float alpha = layer->alpha;
-        SkXfermode::Mode mode = layer->mode;
+        float alpha = layer->getAlpha();
+        SkXfermode::Mode mode = layer->getMode();
 
-        layer->mode = SkXfermode::kSrc_Mode;
-        layer->alpha = 255;
-        layer->fbo = fbo;
+        layer->setAlpha(255, SkXfermode::kSrc_Mode);
+        layer->setFbo(fbo);
 
         glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*) &previousFbo);
         glBindFramebuffer(GL_FRAMEBUFFER, fbo);
@@ -399,7 +386,7 @@
             LayerRenderer renderer(layer);
             renderer.setViewport(bitmap->width(), bitmap->height());
             renderer.OpenGLRenderer::prepareDirty(0.0f, 0.0f,
-                    bitmap->width(), bitmap->height(), !layer->blend);
+                    bitmap->width(), bitmap->height(), !layer->isBlend());
             if ((error = glGetError()) != GL_NO_ERROR) goto error;
 
             {
@@ -424,9 +411,8 @@
 #endif
 
         glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
-        layer->mode = mode;
-        layer->alpha = alpha;
-        layer->fbo = 0;
+        layer->setAlpha(alpha, mode);
+        layer->setFbo(0);
         glDeleteTextures(1, &texture);
         caches.fboCache.put(fbo);
 
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 1c06a0b..cb5a82b 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -449,12 +449,11 @@
         return false;
     }
 
-    layer->mode = mode;
-    layer->alpha = alpha;
+    layer->setAlpha(alpha, mode);
     layer->layer.set(bounds);
-    layer->texCoords.set(0.0f, bounds.getHeight() / float(layer->height),
-            bounds.getWidth() / float(layer->width), 0.0f);
-    layer->colorFilter = mColorFilter;
+    layer->texCoords.set(0.0f, bounds.getHeight() / float(layer->getHeight()),
+            bounds.getWidth() / float(layer->getWidth()), 0.0f);
+    layer->setColorFilter(mColorFilter);
 
     // Save the layer in the snapshot
     snapshot->flags |= Snapshot::kFlagIsLayer;
@@ -464,12 +463,13 @@
         return createFboLayer(layer, bounds, snapshot, previousFbo);
     } else {
         // Copy the framebuffer into the layer
-        glBindTexture(GL_TEXTURE_2D, layer->texture);
+        layer->bindTexture();
         if (!bounds.isEmpty()) {
-            if (layer->empty) {
-                glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left,
-                        snapshot->height - bounds.bottom, layer->width, layer->height, 0);
-                layer->empty = false;
+            if (layer->isEmpty()) {
+                glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
+                        bounds.left, snapshot->height - bounds.bottom,
+                        layer->getWidth(), layer->getHeight(), 0);
+                layer->setEmpty(false);
             } else {
                 glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bounds.left,
                         snapshot->height - bounds.bottom, bounds.getWidth(), bounds.getHeight());
@@ -485,7 +485,7 @@
 
 bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, sp<Snapshot> snapshot,
         GLuint previousFbo) {
-    layer->fbo = mCaches.fboCache.get();
+    layer->setFbo(mCaches.fboCache.get());
 
 #if RENDER_LAYERS_AS_REGIONS
     snapshot->region = &snapshot->layer->region;
@@ -507,7 +507,7 @@
     clip.translate(-bounds.left, -bounds.top);
 
     snapshot->flags |= Snapshot::kFlagIsFboLayer;
-    snapshot->fbo = layer->fbo;
+    snapshot->fbo = layer->getFbo();
     snapshot->resetTransform(-bounds.left, -bounds.top, 0.0f);
     snapshot->resetClip(clip.left, clip.top, clip.right, clip.bottom);
     snapshot->viewport.set(0.0f, 0.0f, bounds.getWidth(), bounds.getHeight());
@@ -516,18 +516,17 @@
     snapshot->orthoMatrix.load(mOrthoMatrix);
 
     // Bind texture to FBO
-    glBindFramebuffer(GL_FRAMEBUFFER, layer->fbo);
-    glBindTexture(GL_TEXTURE_2D, layer->texture);
+    glBindFramebuffer(GL_FRAMEBUFFER, layer->getFbo());
+    layer->bindTexture();
 
     // Initialize the texture if needed
-    if (layer->empty) {
-        layer->empty = false;
-        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, layer->width, layer->height, 0,
-                GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+    if (layer->isEmpty()) {
+        layer->allocateTexture(GL_RGBA, GL_UNSIGNED_BYTE);
+        layer->setEmpty(false);
     }
 
     glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
-            layer->texture, 0);
+            layer->getTexture(), 0);
 
 #if DEBUG_LAYERS_AS_REGIONS
     GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
@@ -535,8 +534,8 @@
         LOGE("Framebuffer incomplete (GL error code 0x%x)", status);
 
         glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
-        glDeleteTextures(1, &layer->texture);
-        mCaches.fboCache.put(layer->fbo);
+        layer->deleteTexture();
+        mCaches.fboCache.put(layer->getFbo());
 
         delete layer;
 
@@ -578,11 +577,11 @@
     Layer* layer = current->layer;
     const Rect& rect = layer->layer;
 
-    if (!fboLayer && layer->alpha < 255) {
+    if (!fboLayer && layer->getAlpha() < 255) {
         drawColorRect(rect.left, rect.top, rect.right, rect.bottom,
-                layer->alpha << 24, SkXfermode::kDstIn_Mode, true);
+                layer->getAlpha() << 24, SkXfermode::kDstIn_Mode, true);
         // Required below, composeLayerRect() will divide by 255
-        layer->alpha = 255;
+        layer->setAlpha(255);
     }
 
     mCaches.unbindMeshBuffer();
@@ -593,18 +592,16 @@
     // drawing only the dirty region
     if (fboLayer) {
         dirtyLayer(rect.left, rect.top, rect.right, rect.bottom, *previous->transform);
-        if (layer->colorFilter) {
-            setupColorFilter(layer->colorFilter);
+        if (layer->getColorFilter()) {
+            setupColorFilter(layer->getColorFilter());
         }
         composeLayerRegion(layer, rect);
-        if (layer->colorFilter) {
+        if (layer->getColorFilter()) {
             resetColorFilter();
         }
-    } else {
-        if (!rect.isEmpty()) {
-            dirtyLayer(rect.left, rect.top, rect.right, rect.bottom);
-            composeLayerRect(layer, rect, true);
-        }
+    } else if (!rect.isEmpty()) {
+        dirtyLayer(rect.left, rect.top, rect.right, rect.bottom);
+        composeLayerRect(layer, rect, true);
     }
 
     if (fboLayer) {
@@ -622,16 +619,16 @@
     // Failing to add the layer to the cache should happen only if the layer is too large
     if (!mCaches.layerCache.put(layer)) {
         LAYER_LOGD("Deleting layer");
-        glDeleteTextures(1, &layer->texture);
+        layer->deleteTexture();
         delete layer;
     }
 }
 
 void OpenGLRenderer::drawTextureLayer(Layer* layer, const Rect& rect) {
-    float alpha = layer->alpha / 255.0f;
+    float alpha = layer->getAlpha() / 255.0f;
 
     setupDraw();
-    if (layer->renderTarget == GL_TEXTURE_2D) {
+    if (layer->getRenderTarget() == GL_TEXTURE_2D) {
         setupDrawWithTexture();
     } else {
         setupDrawWithExternalTexture();
@@ -639,17 +636,26 @@
     setupDrawTextureTransform();
     setupDrawColor(alpha, alpha, alpha, alpha);
     setupDrawColorFilter();
-    setupDrawBlending(layer->blend || layer->alpha < 255, layer->mode);
+    setupDrawBlending(layer->isBlend() || alpha < 1.0f, layer->getMode());
     setupDrawProgram();
-    setupDrawModelView(rect.left, rect.top, rect.right, rect.bottom);
     setupDrawPureColorUniforms();
     setupDrawColorFilterUniforms();
-    if (layer->renderTarget == GL_TEXTURE_2D) {
-        setupDrawTexture(layer->texture);
+    if (layer->getRenderTarget() == GL_TEXTURE_2D) {
+        setupDrawTexture(layer->getTexture());
     } else {
-        setupDrawExternalTexture(layer->texture);
+        setupDrawExternalTexture(layer->getTexture());
     }
-    setupDrawTextureTransformUniforms(layer->texTransform);
+    if (mSnapshot->transform->isPureTranslate()) {
+        const float x = (int) floorf(rect.left + mSnapshot->transform->getTranslateX() + 0.5f);
+        const float y = (int) floorf(rect.top + mSnapshot->transform->getTranslateY() + 0.5f);
+
+        layer->setFilter(GL_NEAREST, GL_NEAREST);
+        setupDrawModelView(x, y, x + rect.getWidth(), y + rect.getHeight(), true);
+    } else {
+        layer->setFilter(GL_LINEAR, GL_LINEAR);
+        setupDrawModelView(rect.left, rect.top, rect.right, rect.bottom);
+    }
+    setupDrawTextureTransformUniforms(layer->getTexTransform());
     setupDrawMesh(&mMeshVertices[0].position[0], &mMeshVertices[0].texture[0]);
 
     glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
@@ -658,14 +664,32 @@
 }
 
 void OpenGLRenderer::composeLayerRect(Layer* layer, const Rect& rect, bool swap) {
-    if (!layer->isTextureLayer) {
+    if (!layer->isTextureLayer()) {
         const Rect& texCoords = layer->texCoords;
         resetDrawTextureTexCoords(texCoords.left, texCoords.top,
                 texCoords.right, texCoords.bottom);
 
-        drawTextureMesh(rect.left, rect.top, rect.right, rect.bottom, layer->texture,
-                layer->alpha / 255.0f, layer->mode, layer->blend, &mMeshVertices[0].position[0],
-                &mMeshVertices[0].texture[0], GL_TRIANGLE_STRIP, gMeshCount, swap, swap);
+        float x = rect.left;
+        float y = rect.top;
+        bool simpleTransform = mSnapshot->transform->isPureTranslate();
+
+        if (simpleTransform) {
+            // When we're swapping, the layer is already in screen coordinates
+            if (!swap) {
+                x = (int) floorf(rect.left + mSnapshot->transform->getTranslateX() + 0.5f);
+                y = (int) floorf(rect.top + mSnapshot->transform->getTranslateY() + 0.5f);
+            }
+
+            layer->setFilter(GL_NEAREST, GL_NEAREST, true);
+        } else {
+            layer->setFilter(GL_LINEAR, GL_LINEAR, true);
+        }
+
+        drawTextureMesh(x, y, x + rect.getWidth(), y + rect.getHeight(),
+                layer->getTexture(), layer->getAlpha() / 255.0f,
+                layer->getMode(), layer->isBlend(),
+                &mMeshVertices[0].position[0], &mMeshVertices[0].texture[0],
+                GL_TRIANGLE_STRIP, gMeshCount, swap, swap || simpleTransform);
 
         resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
     } else {
@@ -690,9 +714,9 @@
         size_t count;
         const android::Rect* rects = layer->region.getArray(&count);
 
-        const float alpha = layer->alpha / 255.0f;
-        const float texX = 1.0f / float(layer->width);
-        const float texY = 1.0f / float(layer->height);
+        const float alpha = layer->getAlpha() / 255.0f;
+        const float texX = 1.0f / float(layer->getWidth());
+        const float texY = 1.0f / float(layer->getHeight());
         const float height = rect.getHeight();
 
         TextureVertex* mesh = mCaches.getRegionMesh();
@@ -702,13 +726,22 @@
         setupDrawWithTexture();
         setupDrawColor(alpha, alpha, alpha, alpha);
         setupDrawColorFilter();
-        setupDrawBlending(layer->blend || layer->alpha < 255, layer->mode, false);
+        setupDrawBlending(layer->isBlend() || alpha < 1.0f, layer->getMode(), false);
         setupDrawProgram();
         setupDrawDirtyRegionsDisabled();
         setupDrawPureColorUniforms();
         setupDrawColorFilterUniforms();
-        setupDrawTexture(layer->texture);
-        setupDrawModelViewTranslate(rect.left, rect.top, rect.right, rect.bottom);
+        setupDrawTexture(layer->getTexture());
+        if (mSnapshot->transform->isPureTranslate()) {
+            const float x = (int) floorf(rect.left + mSnapshot->transform->getTranslateX() + 0.5f);
+            const float y = (int) floorf(rect.top + mSnapshot->transform->getTranslateY() + 0.5f);
+
+            layer->setFilter(GL_NEAREST, GL_NEAREST);
+            setupDrawModelViewTranslate(x, y, x + rect.getWidth(), y + rect.getHeight(), true);
+        } else {
+            layer->setFilter(GL_LINEAR, GL_LINEAR);
+            setupDrawModelViewTranslate(rect.left, rect.top, rect.right, rect.bottom);
+        }
         setupDrawMesh(&mesh[0].position[0], &mesh[0].texture[0]);
 
         for (size_t i = 0; i < count; i++) {
@@ -2154,8 +2187,7 @@
     SkXfermode::Mode mode;
     getAlphaAndMode(paint, &alpha, &mode);
 
-    layer->alpha = alpha;
-    layer->mode = mode;
+    layer->setAlpha(alpha, mode);
 
 #if RENDER_LAYERS_AS_REGIONS
     if (!layer->region.isEmpty()) {
@@ -2169,13 +2201,23 @@
             setupDrawWithTexture();
             setupDrawColor(a, a, a, a);
             setupDrawColorFilter();
-            setupDrawBlending(layer->blend || layer->alpha < 255, layer->mode, false);
+            setupDrawBlending(layer->isBlend() || a < 1.0f, layer->getMode(), false);
             setupDrawProgram();
-            setupDrawModelViewTranslate(x, y,
-                    x + layer->layer.getWidth(), y + layer->layer.getHeight());
             setupDrawPureColorUniforms();
             setupDrawColorFilterUniforms();
-            setupDrawTexture(layer->texture);
+            setupDrawTexture(layer->getTexture());
+            if (mSnapshot->transform->isPureTranslate()) {
+                x = (int) floorf(x + mSnapshot->transform->getTranslateX() + 0.5f);
+                y = (int) floorf(y + mSnapshot->transform->getTranslateY() + 0.5f);
+
+                layer->setFilter(GL_NEAREST, GL_NEAREST);
+                setupDrawModelViewTranslate(x, y,
+                        x + layer->layer.getWidth(), y + layer->layer.getHeight(), true);
+            } else {
+                layer->setFilter(GL_LINEAR, GL_LINEAR);
+                setupDrawModelViewTranslate(x, y,
+                        x + layer->layer.getWidth(), y + layer->layer.getHeight());
+            }
             setupDrawMesh(&layer->mesh[0].position[0], &layer->mesh[0].texture[0]);
 
             glDrawElements(GL_TRIANGLES, layer->meshElementCount,
diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h
index 4922bb3..c6ae326 100644
--- a/libs/hwui/Texture.h
+++ b/libs/hwui/Texture.h
@@ -29,8 +29,22 @@
     Texture() {
         cleanup = false;
         bitmapSize = 0;
+
         wrapS = GL_CLAMP_TO_EDGE;
         wrapT = GL_CLAMP_TO_EDGE;
+
+        minFilter = GL_NEAREST;
+        magFilter = GL_NEAREST;
+    }
+
+    void setWrap(GLenum wrapS, GLenum wrapT) {
+        this->wrapS = wrapS;
+        this->wrapT = wrapT;
+    }
+
+    void setFilter(GLenum min, GLenum mag) {
+        minFilter = min;
+        magFilter = mag;
     }
 
     /**
@@ -67,6 +81,12 @@
      */
     GLenum wrapS;
     GLenum wrapT;
+
+    /**
+     * Last filters set on this texture. Defaults to GL_NEAREST.
+     */
+    GLenum minFilter;
+    GLenum magFilter;
 }; // struct Texture
 
 class AutoTexture {
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java
index c857ded..95b84c4 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java
@@ -62,7 +62,9 @@
             }
         });
 
-        mContent.addView(mTextureView, new FrameLayout.LayoutParams(500, 400, Gravity.CENTER));
+        mContent.addView(mTextureView, new FrameLayout.LayoutParams(
+                FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT,
+                Gravity.CENTER));
         mContent.addView(button, new FrameLayout.LayoutParams(
                 FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT,
                 Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM));
@@ -72,6 +74,9 @@
     @Override
     public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
         mCamera = Camera.open();
+        Camera.Size previewSize = mCamera.getParameters().getPreviewSize();
+        mTextureView.setLayoutParams(new FrameLayout.LayoutParams(
+                previewSize.width, previewSize.height, Gravity.CENTER));
 
         try {
             mCamera.setPreviewTexture(surface);
@@ -82,20 +87,6 @@
         mCamera.startPreview();
 
         mTextureView.setCameraDistance(5000);
-
-//        ObjectAnimator rotationY = ObjectAnimator.ofFloat(mTextureView, "rotationY", 0.0f, 360.0f);
-//        rotationY.setRepeatMode(ObjectAnimator.REVERSE);
-//        rotationY.setRepeatCount(ObjectAnimator.INFINITE);
-//        rotationY.setDuration(4000);
-
-//        ObjectAnimator alpha = ObjectAnimator.ofFloat(mTextureView, "alpha", 1.0f, 0.0f);
-//        alpha.setRepeatMode(ObjectAnimator.REVERSE);
-//        alpha.setRepeatCount(ObjectAnimator.INFINITE);
-//        alpha.setDuration(4000);
-
-//        mAnimatorSet = new AnimatorSet();
-//        mAnimatorSet.play(alpha).with(rotationY);
-//        mAnimatorSet.start();
     }
 
     @Override