Linear blending, step 1

NOTE: Linear blending is currently disabled in this CL as the
      feature is still a work in progress

Android currently performs all blending (any kind of linear math
on colors really) on gamma-encoded colors. Since Android assumes
that the default color space is sRGB, all bitmaps and colors
are encoded with the sRGB Opto-Electronic Conversion Function
(OECF, which can be approximated with a power function). Since
the power curve is not linear, our linear math is incorrect.
The result is that we generate colors that tend to be too dark;
this affects blending but also anti-aliasing, gradients, blurs,
etc.

The solution is to convert gamma-encoded colors back to linear
space before doing any math on them, using the sRGB Electo-Optical
Conversion Function (EOCF). This is achieved in different
ways in different parts of the pipeline:

- Using hardware conversions when sampling from OpenGL textures
  or writing into OpenGL frame buffers
- Using software conversion functions, to translate app-supplied
  colors to and from sRGB
- Using Skia's color spaces

Any type of processing on colors must roughly ollow these steps:

[sRGB input]->EOCF->[linear data]->[processing]->OECF->[sRGB output]

For the sRGB color space, the conversion functions are defined as
follows:

OECF(linear) :=
linear <= 0.0031308 ? linear * 12.92 : (pow(linear, 1/2.4) * 1.055) - 0.055

EOCF(srgb) :=
srgb <= 0.04045 ? srgb / 12.92 : pow((srgb + 0.055) / 1.055, 2.4)

The EOCF is simply the reciprocal of the OECF.
While it is highly recommended to use the exact sRGB conversion
functions everywhere possible, it is sometimes useful or beneficial
to rely on approximations:

- pow(x,2.2) and pow(x,1/2.2)
- x^2 and sqrt(x)

The latter is particularly useful in fragment shaders (for instance
to apply dithering in sRGB space), especially if the sqrt() can be
replaced with an inversesqrt().

Here is a fairly exhaustive list of modifications implemented
in this CL:

- Set TARGET_ENABLE_LINEAR_BLENDING := false in BoardConfig.mk
  to disable linear blending. This is only for GLES 2.0 GPUs
  with no hardware sRGB support. This flag is currently assumed
  to be false (see note above)
- sRGB writes are disabled when entering a functor (WebView).
  This will need to be fixed at some point
- Skia bitmaps are created with the sRGB color space
- Bitmaps using a 565 config are expanded to 888
- Linear blending is disabled when entering a functor
- External textures are not properly sampled (see below)
- Gradients are interpolated in linear space
- Texture-based dithering was replaced with analytical dithering
- Dithering is done in the quantization color space, which is
  why we must do EOCF(OECF(color)+dither)
- Text is now gamma corrected differently depending on the luminance
  of the source pixel. The asumption is that a bright pixel will be
  blended on a dark background and the other way around. The source
  alpha is gamma corrected to thicken dark on bright and thin
  bright on dark to match the intended design of fonts. This also
  matches the behavior of popular design/drawing applications
- Removed the asset atlas. It did not contain anything useful and
  could not be sampled in sRGB without a yet-to-be-defined GL
  extension
- The last column of color matrices is converted to linear space
  because its value are added to linear colors

Missing features:
- Resource qualifier?
- Regeneration of goldeng images for automated tests
- Handle alpha8/grey8 properly
- Disable sRGB write for layers with external textures

Test: Manual testing while work in progress
Bug: 29940137

Change-Id: I6a07b15ab49b554377cd33a36b6d9971a15e9a0b
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 81a1831..2d9b764 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -45,7 +45,6 @@
     AnimationContext.cpp \
     Animator.cpp \
     AnimatorManager.cpp \
-    AssetAtlas.cpp \
     BakedOpDispatcher.cpp \
     BakedOpRenderer.cpp \
     BakedOpState.cpp \
@@ -56,7 +55,6 @@
     DeferredLayerUpdater.cpp \
     DeviceInfo.cpp \
     DisplayList.cpp \
-    Dither.cpp \
     Extensions.cpp \
     FboCache.cpp \
     FontRenderer.cpp \
@@ -130,6 +128,14 @@
     hwui_cflags += -DUSE_HWC2
 endif
 
+# TODO: Linear blending should be enabled by default, but we are
+# TODO: making it an opt-in while it's a work in progress
+# TODO: The final test should be:
+# TODO: ifneq ($(TARGET_ENABLE_LINEAR_BLENDING),false)
+ifeq ($(TARGET_ENABLE_LINEAR_BLENDING),true)
+    hwui_cflags += -DANDROID_ENABLE_LINEAR_BLENDING
+endif
+
 # GCC false-positives on this warning, and since we -Werror that's
 # a problem
 hwui_cflags += -Wno-free-nonheap-object
@@ -143,7 +149,6 @@
     hwui_cflags += -DBUGREPORT_FONT_CACHE_USAGE
 endif
 
-
 ifndef HWUI_COMPILE_SYMBOLS
     hwui_cflags += -fvisibility=hidden
 endif
diff --git a/libs/hwui/AssetAtlas.cpp b/libs/hwui/AssetAtlas.cpp
deleted file mode 100644
index e2e70372..0000000
--- a/libs/hwui/AssetAtlas.cpp
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "AssetAtlas.h"
-#include "Caches.h"
-#include "Image.h"
-
-#include <GLES2/gl2ext.h>
-
-namespace android {
-namespace uirenderer {
-
-///////////////////////////////////////////////////////////////////////////////
-// Lifecycle
-///////////////////////////////////////////////////////////////////////////////
-
-void AssetAtlas::init(const sp<GraphicBuffer>& buffer, int64_t* map, int count) {
-    if (mImage) {
-        return;
-    }
-
-    ATRACE_NAME("AssetAtlas::init");
-
-    mImage = new Image(buffer);
-    if (mImage->getTexture()) {
-        if (!mTexture) {
-            Caches& caches = Caches::getInstance();
-            mTexture = new Texture(caches);
-            mTexture->wrap(mImage->getTexture(),
-                    buffer->getWidth(), buffer->getHeight(), GL_RGBA);
-            createEntries(caches, map, count);
-        }
-    } else {
-        ALOGW("Could not create atlas image");
-        terminate();
-    }
-}
-
-void AssetAtlas::terminate() {
-    delete mImage;
-    mImage = nullptr;
-    delete mTexture;
-    mTexture = nullptr;
-    mEntries.clear();
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Entries
-///////////////////////////////////////////////////////////////////////////////
-
-AssetAtlas::Entry* AssetAtlas::getEntry(const SkPixelRef* pixelRef) const {
-    auto result = mEntries.find(pixelRef);
-    return result != mEntries.end() ? result->second.get() : nullptr;
-}
-
-Texture* AssetAtlas::getEntryTexture(const SkPixelRef* pixelRef) const {
-    auto result = mEntries.find(pixelRef);
-    return result != mEntries.end() ? result->second->texture : nullptr;
-}
-
-/**
- * Delegates changes to wrapping and filtering to the base atlas texture
- * instead of applying the changes to the virtual textures.
- */
-struct DelegateTexture: public Texture {
-    DelegateTexture(Caches& caches, Texture* delegate)
-            : Texture(caches), mDelegate(delegate) { }
-
-    virtual void setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture = false,
-            bool force = false, GLenum renderTarget = GL_TEXTURE_2D) override {
-        mDelegate->setWrapST(wrapS, wrapT, bindTexture, force, renderTarget);
-    }
-
-    virtual void setFilterMinMag(GLenum min, GLenum mag, bool bindTexture = false,
-            bool force = false, GLenum renderTarget = GL_TEXTURE_2D) override {
-        mDelegate->setFilterMinMag(min, mag, bindTexture, force, renderTarget);
-    }
-
-private:
-    Texture* const mDelegate;
-}; // struct DelegateTexture
-
-void AssetAtlas::createEntries(Caches& caches, int64_t* map, int count) {
-    const float width = float(mTexture->width());
-    const float height = float(mTexture->height());
-
-    for (int i = 0; i < count; ) {
-        SkPixelRef* pixelRef = reinterpret_cast<SkPixelRef*>(map[i++]);
-        // NOTE: We're converting from 64 bit signed values to 32 bit
-        // signed values. This is guaranteed to be safe because the "x"
-        // and "y" coordinate values are guaranteed to be representable
-        // with 32 bits. The array is 64 bits wide so that it can carry
-        // pointers on 64 bit architectures.
-        const int x = static_cast<int>(map[i++]);
-        const int y = static_cast<int>(map[i++]);
-
-        // Bitmaps should never be null, we're just extra paranoid
-        if (!pixelRef) continue;
-
-        const UvMapper mapper(
-                x / width, (x + pixelRef->info().width()) / width,
-                y / height, (y + pixelRef->info().height()) / height);
-
-        Texture* texture = new DelegateTexture(caches, mTexture);
-        texture->blend = !SkAlphaTypeIsOpaque(pixelRef->info().alphaType());
-        texture->wrap(mTexture->id(), pixelRef->info().width(),
-                pixelRef->info().height(), mTexture->format());
-
-        std::unique_ptr<Entry> entry(new Entry(pixelRef, texture, mapper, *this));
-        texture->uvMapper = &entry->uvMapper;
-
-        mEntries.emplace(entry->pixelRef, std::move(entry));
-    }
-}
-
-}; // namespace uirenderer
-}; // namespace android
diff --git a/libs/hwui/AssetAtlas.h b/libs/hwui/AssetAtlas.h
deleted file mode 100644
index b32e518..0000000
--- a/libs/hwui/AssetAtlas.h
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_HWUI_ASSET_ATLAS_H
-#define ANDROID_HWUI_ASSET_ATLAS_H
-
-#include "Texture.h"
-#include "UvMapper.h"
-
-#include <cutils/compiler.h>
-#include <GLES2/gl2.h>
-#include <ui/GraphicBuffer.h>
-#include <SkBitmap.h>
-
-#include <memory>
-#include <unordered_map>
-
-namespace android {
-namespace uirenderer {
-
-class Caches;
-class Image;
-
-/**
- * An asset atlas holds a collection of framework bitmaps in a single OpenGL
- * texture. Each bitmap is associated with a location, defined in pixels,
- * inside the atlas. The atlas is generated by the framework and bound as
- * an external texture using the EGLImageKHR extension.
- */
-class AssetAtlas {
-public:
-    /**
-     * Entry representing the texture and uvMapper of a PixelRef in the
-     * atlas
-     */
-    class Entry {
-    public:
-        /*
-         * A "virtual texture" object that represents the texture
-         * this entry belongs to. This texture should never be
-         * modified.
-         */
-        Texture* texture;
-
-        /**
-         * Maps texture coordinates in the [0..1] range into the
-         * correct range to sample this entry from the atlas.
-         */
-        const UvMapper uvMapper;
-
-        /**
-         * Unique identifier used to merge bitmaps and 9-patches stored
-         * in the atlas.
-         */
-        const void* getMergeId() const {
-            return texture->blend ? &atlas.mBlendKey : &atlas.mOpaqueKey;
-        }
-
-        ~Entry() {
-            delete texture;
-        }
-
-    private:
-        /**
-         * The pixel ref that generated this atlas entry.
-         */
-        SkPixelRef* pixelRef;
-
-        /**
-         * Atlas this entry belongs to.
-         */
-        const AssetAtlas& atlas;
-
-        Entry(SkPixelRef* pixelRef, Texture* texture, const UvMapper& mapper,
-                    const AssetAtlas& atlas)
-                : texture(texture)
-                , uvMapper(mapper)
-                , pixelRef(pixelRef)
-                , atlas(atlas) {
-        }
-
-        friend class AssetAtlas;
-    };
-
-    AssetAtlas(): mTexture(nullptr), mImage(nullptr),
-            mBlendKey(true), mOpaqueKey(false) { }
-    ~AssetAtlas() { terminate(); }
-
-    /**
-     * Initializes the atlas with the specified buffer and
-     * map. The buffer is a gralloc'd texture that will be
-     * used as an EGLImage. The map is a list of SkBitmap*
-     * and their (x, y) positions
-     *
-     * This method returns immediately if the atlas is already
-     * initialized. To re-initialize the atlas, you must
-     * first call terminate().
-     */
-    ANDROID_API void init(const sp<GraphicBuffer>& buffer, int64_t* map, int count);
-
-    /**
-     * Destroys the atlas texture. This object can be
-     * re-initialized after calling this method.
-     *
-     * After calling this method, the width, height
-     * and texture are set to 0.
-     */
-    void terminate();
-
-    /**
-     * Returns the width of this atlas in pixels.
-     * Can return 0 if the atlas is not initialized.
-     */
-    uint32_t getWidth() const {
-        return mTexture ? mTexture->width() : 0;
-    }
-
-    /**
-     * Returns the height of this atlas in pixels.
-     * Can return 0 if the atlas is not initialized.
-     */
-    uint32_t getHeight() const {
-        return mTexture ? mTexture->height() : 0;
-    }
-
-    /**
-     * Returns the OpenGL name of the texture backing this atlas.
-     * Can return 0 if the atlas is not initialized.
-     */
-    GLuint getTexture() const {
-        return mTexture ? mTexture->id() : 0;
-    }
-
-    /**
-     * Returns the entry in the atlas associated with the specified
-     * pixelRef. If the pixelRef is not in the atlas, return NULL.
-     */
-    Entry* getEntry(const SkPixelRef* pixelRef) const;
-
-    /**
-     * Returns the texture for the atlas entry associated with the
-     * specified pixelRef. If the pixelRef is not in the atlas, return NULL.
-     */
-    Texture* getEntryTexture(const SkPixelRef* pixelRef) const;
-
-private:
-    void createEntries(Caches& caches, int64_t* map, int count);
-
-    Texture* mTexture;
-    Image* mImage;
-
-    const bool mBlendKey;
-    const bool mOpaqueKey;
-
-    std::unordered_map<const SkPixelRef*, std::unique_ptr<Entry>> mEntries;
-}; // class AssetAtlas
-
-}; // namespace uirenderer
-}; // namespace android
-
-#endif // ANDROID_HWUI_ASSET_ATLAS_H
diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp
index 8b3f172..6995039 100644
--- a/libs/hwui/BakedOpDispatcher.cpp
+++ b/libs/hwui/BakedOpDispatcher.cpp
@@ -35,11 +35,11 @@
 namespace android {
 namespace uirenderer {
 
-static void storeTexturedRect(TextureVertex* vertices, const Rect& bounds, const Rect& texCoord) {
-    vertices[0] = { bounds.left, bounds.top, texCoord.left, texCoord.top };
-    vertices[1] = { bounds.right, bounds.top, texCoord.right, texCoord.top };
-    vertices[2] = { bounds.left, bounds.bottom, texCoord.left, texCoord.bottom };
-    vertices[3] = { bounds.right, bounds.bottom, texCoord.right, texCoord.bottom };
+static void storeTexturedRect(TextureVertex* vertices, const Rect& bounds) {
+    vertices[0] = { bounds.left,  bounds.top,    0, 0 };
+    vertices[1] = { bounds.right, bounds.top,    1, 0 };
+    vertices[2] = { bounds.left,  bounds.bottom, 0, 1 };
+    vertices[3] = { bounds.right, bounds.bottom, 1, 1 };
 }
 
 void BakedOpDispatcher::onMergedBitmapOps(BakedOpRenderer& renderer,
@@ -48,16 +48,11 @@
     const BakedOpState& firstState = *(opList.states[0]);
     const SkBitmap* bitmap = (static_cast<const BitmapOp*>(opList.states[0]->op))->bitmap;
 
-    AssetAtlas::Entry* entry = renderer.renderState().assetAtlas().getEntry(bitmap->pixelRef());
-    Texture* texture = entry ? entry->texture : renderer.caches().textureCache.get(bitmap);
+    Texture* texture = renderer.caches().textureCache.get(bitmap);
     if (!texture) return;
     const AutoTexture autoCleanup(texture);
 
     TextureVertex vertices[opList.count * 4];
-    Rect texCoords(0, 0, 1, 1);
-    if (entry) {
-        entry->uvMapper.map(texCoords);
-    }
     for (size_t i = 0; i < opList.count; i++) {
         const BakedOpState& state = *(opList.states[i]);
         TextureVertex* rectVerts = &vertices[i * 4];
@@ -69,7 +64,7 @@
             // pure translate, so snap (same behavior as onBitmapOp)
             opBounds.snapToPixelBoundaries();
         }
-        storeTexturedRect(rectVerts, opBounds, texCoords);
+        storeTexturedRect(rectVerts, opBounds);
         renderer.dirtyRenderTarget(opBounds);
     }
 
@@ -92,8 +87,6 @@
         const MergedBakedOpList& opList) {
     const PatchOp& firstOp = *(static_cast<const PatchOp*>(opList.states[0]->op));
     const BakedOpState& firstState = *(opList.states[0]);
-    AssetAtlas::Entry* entry = renderer.renderState().assetAtlas().getEntry(
-            firstOp.bitmap->pixelRef());
 
     // Batches will usually contain a small number of items so it's
     // worth performing a first iteration to count the exact number
@@ -105,7 +98,7 @@
 
         // TODO: cache mesh lookups
         const Patch* opMesh = renderer.caches().patchCache.get(
-                entry, op.bitmap->width(), op.bitmap->height(),
+                op.bitmap->width(), op.bitmap->height(),
                 op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.patch);
         totalVertices += opMesh->verticesCount;
     }
@@ -126,7 +119,7 @@
 
         // TODO: cache mesh lookups
         const Patch* opMesh = renderer.caches().patchCache.get(
-                entry, op.bitmap->width(), op.bitmap->height(),
+                op.bitmap->width(), op.bitmap->height(),
                 op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.patch);
 
 
@@ -172,7 +165,7 @@
     }
 
 
-    Texture* texture = entry ? entry->texture : renderer.caches().textureCache.get(firstOp.bitmap);
+    Texture* texture = renderer.caches().textureCache.get(firstOp.bitmap);
     if (!texture) return;
     const AutoTexture autoCleanup(texture);
 
@@ -442,7 +435,12 @@
 }
 
 void BakedOpDispatcher::onBitmapMeshOp(BakedOpRenderer& renderer, const BitmapMeshOp& op, const BakedOpState& state) {
-    const static UvMapper defaultUvMapper;
+    Texture* texture = renderer.caches().textureCache.get(op.bitmap);
+    if (!texture) {
+        return;
+    }
+    const AutoTexture autoCleanup(texture);
+
     const uint32_t elementCount = op.meshWidth * op.meshHeight * 6;
 
     std::unique_ptr<ColorTextureVertex[]> mesh(new ColorTextureVertex[elementCount]);
@@ -457,9 +455,6 @@
         colors = tempColors.get();
     }
 
-    Texture* texture = renderer.renderState().assetAtlas().getEntryTexture(op.bitmap->pixelRef());
-    const UvMapper& mapper(texture && texture->uvMapper ? *texture->uvMapper : defaultUvMapper);
-
     for (int32_t y = 0; y < op.meshHeight; y++) {
         for (int32_t x = 0; x < op.meshWidth; x++) {
             uint32_t i = (y * (op.meshWidth + 1) + x) * 2;
@@ -469,8 +464,6 @@
             float v1 = float(y) / op.meshHeight;
             float v2 = float(y + 1) / op.meshHeight;
 
-            mapper.map(u1, v1, u2, v2);
-
             int ax = i + (op.meshWidth + 1) * 2;
             int ay = ax + 1;
             int bx = i;
@@ -491,14 +484,6 @@
         }
     }
 
-    if (!texture) {
-        texture = renderer.caches().textureCache.get(op.bitmap);
-        if (!texture) {
-            return;
-        }
-    }
-    const AutoTexture autoCleanup(texture);
-
     /*
      * TODO: handle alpha_8 textures correctly by applying paint color, but *not*
      * shader in that case to mimic the behavior in SkiaCanvas::drawBitmapMesh.
@@ -599,12 +584,11 @@
     }
 
     // TODO: avoid redoing the below work each frame:
-    AssetAtlas::Entry* entry = renderer.renderState().assetAtlas().getEntry(op.bitmap->pixelRef());
     const Patch* mesh = renderer.caches().patchCache.get(
-            entry, op.bitmap->width(), op.bitmap->height(),
+            op.bitmap->width(), op.bitmap->height(),
             op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.patch);
 
-    Texture* texture = entry ? entry->texture : renderer.caches().textureCache.get(op.bitmap);
+    Texture* texture = renderer.caches().textureCache.get(op.bitmap);
     if (CC_LIKELY(texture)) {
         const AutoTexture autoCleanup(texture);
         Glop glop;
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index 6db345a..ac7a600 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -182,11 +182,7 @@
 }
 
 Texture* BakedOpRenderer::getTexture(const SkBitmap* bitmap) {
-    Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap->pixelRef());
-    if (!texture) {
-        return mCaches.textureCache.get(bitmap);
-    }
-    return texture;
+    return mCaches.textureCache.get(bitmap);
 }
 
 void BakedOpRenderer::drawRects(const float* rects, int count, const SkPaint* paint) {
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index 741cdcc..b463e45 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -53,7 +53,6 @@
         : gradientCache(mExtensions)
         , patchCache(renderState)
         , programCache(mExtensions)
-        , dither(*this)
         , mRenderState(&renderState)
         , mInitialized(false) {
     INIT_LOGD("Creating OpenGL renderer caches");
@@ -238,7 +237,6 @@
             gradientCache.clear();
             fontRenderer.clear();
             fboCache.clear();
-            dither.clear();
             // fall through
         case FlushMode::Moderate:
             fontRenderer.flush();
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index 344ee71..7c2e78c 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -16,8 +16,6 @@
 
 #pragma once
 
-#include "AssetAtlas.h"
-#include "Dither.h"
 #include "Extensions.h"
 #include "FboCache.h"
 #include "GammaFontRenderer.h"
@@ -130,6 +128,15 @@
     TextureVertex* getRegionMesh();
 
     /**
+     * Returns the GL RGBA internal format to use for the current device
+     * If the device supports linear blending and needSRGB is true,
+     * this function returns GL_SRGB8_ALPHA8, otherwise it returns GL_RGBA
+     */
+    constexpr GLint rgbaInternalFormat(bool needSRGB = true) const {
+        return extensions().hasSRGB() && needSRGB ? GL_SRGB8_ALPHA8 : GL_RGBA;
+    }
+
+    /**
      * Displays the memory usage of each cache and the total sum.
      */
     void dumpMemoryUsage();
@@ -157,8 +164,6 @@
 
     TaskManager tasks;
 
-    Dither dither;
-
     bool gpuPixelBuffersEnabled;
 
     // Debug methods
@@ -169,7 +174,7 @@
     void setProgram(const ProgramDescription& description);
     void setProgram(Program* program);
 
-    Extensions& extensions() { return mExtensions; }
+    const Extensions& extensions() const { return mExtensions; }
     Program& program() { return *mProgram; }
     PixelBufferState& pixelBufferState() { return *mPixelBufferState; }
     TextureState& textureState() { return *mTextureState; }
diff --git a/libs/hwui/Dither.cpp b/libs/hwui/Dither.cpp
deleted file mode 100644
index ec2013e..0000000
--- a/libs/hwui/Dither.cpp
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "Caches.h"
-#include "Dither.h"
-
-namespace android {
-namespace uirenderer {
-
-///////////////////////////////////////////////////////////////////////////////
-// Lifecycle
-///////////////////////////////////////////////////////////////////////////////
-
-Dither::Dither(Caches& caches)
-        : mCaches(caches)
-        , mInitialized(false)
-        , mDitherTexture(0) {
-}
-
-void Dither::bindDitherTexture() {
-    if (!mInitialized) {
-        glGenTextures(1, &mDitherTexture);
-        mCaches.textureState().bindTexture(mDitherTexture);
-
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
-
-        if (mCaches.extensions().hasFloatTextures()) {
-            // We use a R16F texture, let's remap the alpha channel to the
-            // red channel to avoid changing the shader sampling code on GL ES 3.0+
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_RED);
-
-            float dither = 1.0f / (255.0f * DITHER_KERNEL_SIZE * DITHER_KERNEL_SIZE);
-            const GLfloat pattern[] = {
-                 0 * dither,  8 * dither,  2 * dither, 10 * dither,
-                12 * dither,  4 * dither, 14 * dither,  6 * dither,
-                 3 * dither, 11 * dither,  1 * dither,  9 * dither,
-                15 * dither,  7 * dither, 13 * dither,  5 * dither
-            };
-
-            glTexImage2D(GL_TEXTURE_2D, 0, GL_R16F, DITHER_KERNEL_SIZE, DITHER_KERNEL_SIZE, 0,
-                    GL_RED, GL_FLOAT, &pattern);
-        } else {
-            const uint8_t pattern[] = {
-                 0,  8,  2, 10,
-                12,  4, 14,  6,
-                 3, 11,  1,  9,
-                15,  7, 13,  5
-            };
-
-            glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, DITHER_KERNEL_SIZE, DITHER_KERNEL_SIZE, 0,
-                    GL_ALPHA, GL_UNSIGNED_BYTE, &pattern);
-        }
-
-        mInitialized = true;
-    } else {
-        mCaches.textureState().bindTexture(mDitherTexture);
-    }
-}
-
-void Dither::clear() {
-    if (mInitialized) {
-        mCaches.textureState().deleteTexture(mDitherTexture);
-        mInitialized = false;
-    }
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Program management
-///////////////////////////////////////////////////////////////////////////////
-
-void Dither::setupProgram(Program& program, GLuint* textureUnit) {
-    GLuint textureSlot = (*textureUnit)++;
-    mCaches.textureState().activateTexture(textureSlot);
-
-    bindDitherTexture();
-
-    glUniform1i(program.getUniform("ditherSampler"), textureSlot);
-}
-
-}; // namespace uirenderer
-}; // namespace android
diff --git a/libs/hwui/Dither.h b/libs/hwui/Dither.h
deleted file mode 100644
index 6af3e83..0000000
--- a/libs/hwui/Dither.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_HWUI_DITHER_H
-#define ANDROID_HWUI_DITHER_H
-
-#include <GLES3/gl3.h>
-
-namespace android {
-namespace uirenderer {
-
-class Caches;
-class Extensions;
-class Program;
-
-// Must be a power of two
-#define DITHER_KERNEL_SIZE 4
-// These must not use the .0f notation as they are used from GLSL
-#define DITHER_KERNEL_SIZE_INV (1.0 / 4.0)
-#define DITHER_KERNEL_SIZE_INV_SQUARE (1.0 / 16.0)
-
-/**
- * Handles dithering for programs.
- */
-class Dither {
-public:
-    explicit Dither(Caches& caches);
-
-    void clear();
-    void setupProgram(Program& program, GLuint* textureUnit);
-
-private:
-    void bindDitherTexture();
-
-    Caches& mCaches;
-    bool mInitialized;
-    GLuint mDitherTexture;
-};
-
-}; // namespace uirenderer
-}; // namespace android
-
-#endif // ANDROID_HWUI_DITHER_H
diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp
index 02caaa4..4dc7536 100644
--- a/libs/hwui/Extensions.cpp
+++ b/libs/hwui/Extensions.cpp
@@ -22,6 +22,7 @@
 
 #include <GLES2/gl2.h>
 #include <GLES2/gl2ext.h>
+
 #include <utils/Log.h>
 
 namespace android {
@@ -44,6 +45,14 @@
     mHas1BitStencil = extensions.has("GL_OES_stencil1");
     mHas4BitStencil = extensions.has("GL_OES_stencil4");
     mHasUnpackSubImage = extensions.has("GL_EXT_unpack_subimage");
+    mHasSRGB = extensions.has("GL_EXT_sRGB");
+    mHasSRGBWriteControl = extensions.has("GL_EXT_sRGB_write_control");
+
+    // If linear blending is enabled, the device must have ES3.0 and GL_EXT_sRGB_write_control
+#ifdef ANDROID_ENABLE_LINEAR_BLENDING
+    assert(mVersionMajor >= 3 || mHasSRGB);
+    assert(mHasSRGBWriteControl);
+#endif
 
     const char* version = (const char*) glGetString(GL_VERSION);
 
diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h
index 67cc747..cc73c23 100644
--- a/libs/hwui/Extensions.h
+++ b/libs/hwui/Extensions.h
@@ -43,6 +43,8 @@
     inline bool hasPixelBufferObjects() const { return mVersionMajor >= 3; }
     inline bool hasOcclusionQueries() const { return mVersionMajor >= 3; }
     inline bool hasFloatTextures() const { return mVersionMajor >= 3; }
+    inline bool hasSRGB() const { return mVersionMajor >= 3 || mHasSRGB; }
+    inline bool hasSRGBWriteControl() const { return hasSRGB() && mHasSRGBWriteControl; }
 
     inline int getMajorGlVersion() const { return mVersionMajor; }
     inline int getMinorGlVersion() const { return mVersionMinor; }
@@ -55,6 +57,8 @@
     bool mHas1BitStencil;
     bool mHas4BitStencil;
     bool mHasUnpackSubImage;
+    bool mHasSRGB;
+    bool mHasSRGBWriteControl;
 
     int mVersionMajor;
     int mVersionMinor;
diff --git a/libs/hwui/FloatColor.h b/libs/hwui/FloatColor.h
index 9a39ec2..6d19b7c 100644
--- a/libs/hwui/FloatColor.h
+++ b/libs/hwui/FloatColor.h
@@ -16,6 +16,7 @@
 #ifndef FLOATCOLOR_H
 #define FLOATCOLOR_H
 
+#include "utils/Color.h"
 #include "utils/Macros.h"
 #include "utils/MathUtils.h"
 
@@ -25,11 +26,13 @@
 namespace uirenderer {
 
 struct FloatColor {
+    // "color" is a gamma-encoded sRGB color
+    // After calling this method, the color is stored as a pre-multiplied linear color
     void set(uint32_t color) {
         a = ((color >> 24) & 0xff) / 255.0f;
-        r = a * ((color >> 16) & 0xff) / 255.0f;
-        g = a * ((color >>  8) & 0xff) / 255.0f;
-        b = a * ((color      ) & 0xff) / 255.0f;
+        r = a * EOCF_sRGB(((color >> 16) & 0xff) / 255.0f);
+        g = a * EOCF_sRGB(((color >>  8) & 0xff) / 255.0f);
+        b = a * EOCF_sRGB(((color      ) & 0xff) / 255.0f);
     }
 
     bool isNotBlack() {
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 9b60dfc..effc65e 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -60,11 +60,17 @@
     }
     int transformFlags = pureTranslate
             ? TransformFlags::MeshIgnoresCanvasTransform : TransformFlags::None;
+#ifdef ANDROID_ENABLE_LINEAR_BLENDING
+    bool gammaCorrection = true;
+#else
+    bool gammaCorrection = false;
+#endif
     Glop glop;
     GlopBuilder(renderer->renderState(), renderer->caches(), &glop)
             .setRoundRectClipState(bakedState->roundRectClipState)
             .setMeshTexturedIndexedQuads(texture.mesh(), texture.meshElementCount())
             .setFillTexturePaint(texture.getTexture(), textureFillFlags, paint, bakedState->alpha)
+            .setGammaCorrection(gammaCorrection)
             .setTransform(bakedState->computedState.transform, transformFlags)
             .setModelViewIdentityEmptyBounds()
             .build();
@@ -287,24 +293,23 @@
     // Copy the glyph image, taking the mask format into account
     switch (format) {
         case SkMask::kA8_Format: {
-            uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
             uint32_t row = (startY - TEXTURE_BORDER_SIZE) * cacheWidth + startX
                     - TEXTURE_BORDER_SIZE;
             // write leading border line
             memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE);
             // write glyph data
             if (mGammaTable) {
-                for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) {
+                for (uint32_t cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) {
                     row = cacheY * cacheWidth;
                     cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0;
-                    for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) {
+                    for (uint32_t cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) {
                         uint8_t tempCol = bitmapBuffer[bY + bX];
                         cacheBuffer[row + cacheX] = mGammaTable[tempCol];
                     }
                     cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0;
                 }
             } else {
-                for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) {
+                for (uint32_t cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) {
                     row = cacheY * cacheWidth;
                     memcpy(&cacheBuffer[row + startX], &bitmapBuffer[bY], glyph.fWidth);
                     cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0;
diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp
index be4fdac..17ad0e3 100644
--- a/libs/hwui/FrameBuilder.cpp
+++ b/libs/hwui/FrameBuilder.cpp
@@ -612,7 +612,6 @@
             && op.bitmap->colorType() != kAlpha_8_SkColorType
             && hasMergeableClip(*bakedState)) {
         mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.bitmap->getGenerationID());
-        // TODO: AssetAtlas in mergeId
         currentLayer().deferMergeableOp(mAllocator, bakedState, OpBatchType::Bitmap, mergeId);
     } else {
         currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap);
@@ -687,7 +686,6 @@
             && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode
             && hasMergeableClip(*bakedState)) {
         mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.bitmap->getGenerationID());
-        // TODO: AssetAtlas in mergeId
 
         // Only use the MergedPatch batchId when merged, so Bitmap+Patch don't try to merge together
         currentLayer().deferMergeableOp(mAllocator, bakedState, OpBatchType::MergedPatch, mergeId);
diff --git a/libs/hwui/GammaFontRenderer.cpp b/libs/hwui/GammaFontRenderer.cpp
index 96cac86..8aff0a2 100644
--- a/libs/hwui/GammaFontRenderer.cpp
+++ b/libs/hwui/GammaFontRenderer.cpp
@@ -24,12 +24,13 @@
 GammaFontRenderer::GammaFontRenderer() {
     INIT_LOGD("Creating lookup gamma font renderer");
 
+#ifndef ANDROID_ENABLE_LINEAR_BLENDING
     // Compute the gamma tables
     const float gamma = 1.0f / Properties::textGamma;
-
     for (uint32_t i = 0; i <= 255; i++) {
         mGammaTable[i] = uint8_t((float)::floor(pow(i / 255.0f, gamma) * 255.0f + 0.5f));
     }
+#endif
 }
 
 void GammaFontRenderer::endPrecaching() {
diff --git a/libs/hwui/GammaFontRenderer.h b/libs/hwui/GammaFontRenderer.h
index bd27a1a..c9cf69b 100644
--- a/libs/hwui/GammaFontRenderer.h
+++ b/libs/hwui/GammaFontRenderer.h
@@ -18,11 +18,6 @@
 #define ANDROID_HWUI_GAMMA_FONT_RENDERER_H
 
 #include "FontRenderer.h"
-#include "Program.h"
-
-#include <SkPaint.h>
-
-#include <utils/String8.h>
 
 namespace android {
 namespace uirenderer {
@@ -43,7 +38,11 @@
 
     FontRenderer& getFontRenderer() {
         if (!mRenderer) {
-            mRenderer.reset(new FontRenderer(&mGammaTable[0]));
+            const uint8_t* table = nullptr;
+#ifndef ANDROID_ENABLE_LINEAR_BLENDING
+            table = &mGammaTable[0];
+#endif
+            mRenderer.reset(new FontRenderer(table));
         }
         return *mRenderer;
     }
@@ -64,7 +63,9 @@
 
 private:
     std::unique_ptr<FontRenderer> mRenderer;
+#ifndef ANDROID_ENABLE_LINEAR_BLENDING
     uint8_t mGammaTable[256];
+#endif
 };
 
 }; // namespace uirenderer
diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp
index 1091736..ff88d02 100644
--- a/libs/hwui/GlopBuilder.cpp
+++ b/libs/hwui/GlopBuilder.cpp
@@ -223,16 +223,16 @@
         SkXfermode::Mode mode, Blend::ModeOrderSwap modeUsage,
         const SkShader* shader, const SkColorFilter* colorFilter) {
     if (mode != SkXfermode::kClear_Mode) {
-        float alpha = (SkColorGetA(color) / 255.0f) * alphaScale;
         if (!shader) {
-            float colorScale = alpha / 255.0f;
-            mOutGlop->fill.color = {
-                    colorScale * SkColorGetR(color),
-                    colorScale * SkColorGetG(color),
-                    colorScale * SkColorGetB(color),
-                    alpha
-            };
+            FloatColor c;
+            c.set(color);
+            c.r *= alphaScale;
+            c.g *= alphaScale;
+            c.b *= alphaScale;
+            c.a *= alphaScale;
+            mOutGlop->fill.color = c;
         } else {
+            float alpha = (SkColorGetA(color) / 255.0f) * alphaScale;
             mOutGlop->fill.color = { 1, 1, 1, alpha };
         }
     } else {
@@ -276,15 +276,7 @@
         if (colorFilter->asColorMode(&color, &mode)) {
             mOutGlop->fill.filterMode = mDescription.colorOp = ProgramDescription::ColorFilterMode::Blend;
             mDescription.colorMode = mode;
-
-            const float alpha = SkColorGetA(color) / 255.0f;
-            float colorScale = alpha / 255.0f;
-            mOutGlop->fill.filter.color = {
-                    colorScale * SkColorGetR(color),
-                    colorScale * SkColorGetG(color),
-                    colorScale * SkColorGetB(color),
-                    alpha,
-            };
+            mOutGlop->fill.filter.color.set(color);
         } else if (colorFilter->asColorMatrix(srcColorMatrix)) {
             mOutGlop->fill.filterMode = mDescription.colorOp = ProgramDescription::ColorFilterMode::Matrix;
 
@@ -297,10 +289,10 @@
             // Skia uses the range [0..255] for the addition vector, but we need
             // the [0..1] range to apply the vector in GLSL
             float* colorVector = mOutGlop->fill.filter.matrix.vector;
-            colorVector[0] = srcColorMatrix[4] / 255.0f;
-            colorVector[1] = srcColorMatrix[9] / 255.0f;
-            colorVector[2] = srcColorMatrix[14] / 255.0f;
-            colorVector[3] = srcColorMatrix[19] / 255.0f;
+            colorVector[0] = EOCF_sRGB(srcColorMatrix[4]  / 255.0f);
+            colorVector[1] = EOCF_sRGB(srcColorMatrix[9]  / 255.0f);
+            colorVector[2] = EOCF_sRGB(srcColorMatrix[14] / 255.0f);
+            colorVector[3] = EOCF_sRGB(srcColorMatrix[19] / 255.0f);
         } else {
             LOG_ALWAYS_FATAL("unsupported ColorFilter");
         }
@@ -481,6 +473,13 @@
     return *this;
 }
 
+GlopBuilder& GlopBuilder::setGammaCorrection(bool enabled) {
+    REQUIRE_STAGES(kFillStage);
+
+    mDescription.hasGammaCorrection = enabled;
+    return *this;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // Transform
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/GlopBuilder.h b/libs/hwui/GlopBuilder.h
index 1152461..1f3b53a 100644
--- a/libs/hwui/GlopBuilder.h
+++ b/libs/hwui/GlopBuilder.h
@@ -105,6 +105,8 @@
 
     GlopBuilder& setRoundRectClipState(const RoundRectClipState* roundRectClipState);
 
+    GlopBuilder& setGammaCorrection(bool enabled);
+
     void build();
 
     static void dump(const Glop& glop);
diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp
index c8f5e94..8573ab0 100644
--- a/libs/hwui/GradientCache.cpp
+++ b/libs/hwui/GradientCache.cpp
@@ -67,7 +67,8 @@
         , mSize(0)
         , mMaxSize(Properties::gradientCacheSize)
         , mUseFloatTexture(extensions.hasFloatTextures())
-        , mHasNpot(extensions.hasNPot()){
+        , mHasNpot(extensions.hasNPot())
+        , mHasSRGB(extensions.hasSRGB()) {
     glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
 
     mCache.setOnEntryRemovedListener(this);
@@ -176,71 +177,50 @@
 
 size_t GradientCache::bytesPerPixel() const {
     // We use 4 channels (RGBA)
+    return 4 * (mUseFloatTexture ? /* fp16 */ 2 : sizeof(uint8_t));
+}
+
+size_t GradientCache::sourceBytesPerPixel() const {
+    // We use 4 channels (RGBA) and upload from floats (not half floats)
     return 4 * (mUseFloatTexture ? sizeof(float) : sizeof(uint8_t));
 }
 
-void GradientCache::splitToBytes(uint32_t inColor, GradientColor& outColor) const {
-    outColor.r = (inColor >> 16) & 0xff;
-    outColor.g = (inColor >>  8) & 0xff;
-    outColor.b = (inColor >>  0) & 0xff;
-    outColor.a = (inColor >> 24) & 0xff;
-}
-
-void GradientCache::splitToFloats(uint32_t inColor, GradientColor& outColor) const {
-    outColor.r = ((inColor >> 16) & 0xff) / 255.0f;
-    outColor.g = ((inColor >>  8) & 0xff) / 255.0f;
-    outColor.b = ((inColor >>  0) & 0xff) / 255.0f;
-    outColor.a = ((inColor >> 24) & 0xff) / 255.0f;
-}
-
-void GradientCache::mixBytes(GradientColor& start, GradientColor& end, float amount,
+void GradientCache::mixBytes(FloatColor& start, FloatColor& end, float amount,
         uint8_t*& dst) const {
     float oppAmount = 1.0f - amount;
-    const float alpha = start.a * oppAmount + end.a * amount;
-    const float a = alpha / 255.0f;
-
-    *dst++ = uint8_t(a * (start.r * oppAmount + end.r * amount));
-    *dst++ = uint8_t(a * (start.g * oppAmount + end.g * amount));
-    *dst++ = uint8_t(a * (start.b * oppAmount + end.b * amount));
-    *dst++ = uint8_t(alpha);
+    *dst++ = uint8_t(OECF_sRGB((start.r * oppAmount + end.r * amount) * 255.0f));
+    *dst++ = uint8_t(OECF_sRGB((start.g * oppAmount + end.g * amount) * 255.0f));
+    *dst++ = uint8_t(OECF_sRGB((start.b * oppAmount + end.b * amount) * 255.0f));
+    *dst++ = uint8_t(          (start.a * oppAmount + end.a * amount) * 255.0f);
 }
 
-void GradientCache::mixFloats(GradientColor& start, GradientColor& end, float amount,
+void GradientCache::mixFloats(FloatColor& start, FloatColor& end, float amount,
         uint8_t*& dst) const {
     float oppAmount = 1.0f - amount;
-    const float a = start.a * oppAmount + end.a * amount;
-
     float* d = (float*) dst;
-    *d++ = a * (start.r * oppAmount + end.r * amount);
-    *d++ = a * (start.g * oppAmount + end.g * amount);
-    *d++ = a * (start.b * oppAmount + end.b * amount);
-    *d++ = a;
-
+    *d++ = start.r * oppAmount + end.r * amount;
+    *d++ = start.g * oppAmount + end.g * amount;
+    *d++ = start.b * oppAmount + end.b * amount;
+    *d++ = start.a * oppAmount + end.a * amount;
     dst += 4 * sizeof(float);
 }
 
 void GradientCache::generateTexture(uint32_t* colors, float* positions,
         const uint32_t width, const uint32_t height, Texture* texture) {
-    const GLsizei rowBytes = width * bytesPerPixel();
+    const GLsizei rowBytes = width * sourceBytesPerPixel();
     uint8_t pixels[rowBytes * height];
 
-    static ChannelSplitter gSplitters[] = {
-            &android::uirenderer::GradientCache::splitToBytes,
-            &android::uirenderer::GradientCache::splitToFloats,
-    };
-    ChannelSplitter split = gSplitters[mUseFloatTexture];
-
     static ChannelMixer gMixers[] = {
-            &android::uirenderer::GradientCache::mixBytes,
-            &android::uirenderer::GradientCache::mixFloats,
+            &android::uirenderer::GradientCache::mixBytes,  // colors are stored gamma-encoded
+            &android::uirenderer::GradientCache::mixFloats, // colors are stored in linear
     };
     ChannelMixer mix = gMixers[mUseFloatTexture];
 
-    GradientColor start;
-    (this->*split)(colors[0], start);
+    FloatColor start;
+    start.set(colors[0]);
 
-    GradientColor end;
-    (this->*split)(colors[1], end);
+    FloatColor end;
+    end.set(colors[1]);
 
     int currentPos = 1;
     float startPos = positions[0];
@@ -255,7 +235,7 @@
 
             currentPos++;
 
-            (this->*split)(colors[currentPos], end);
+            end.set(colors[currentPos]);
             distance = positions[currentPos] - startPos;
         }
 
@@ -266,10 +246,10 @@
     memcpy(pixels + rowBytes, pixels, rowBytes);
 
     if (mUseFloatTexture) {
-        // We have to use GL_RGBA16F because GL_RGBA32F does not support filtering
         texture->upload(GL_RGBA16F, width, height, GL_RGBA, GL_FLOAT, pixels);
     } else {
-        texture->upload(GL_RGBA, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
+        GLint internalFormat = mHasSRGB ? GL_SRGB8_ALPHA8 : GL_RGBA;
+        texture->upload(internalFormat, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
     }
 
     texture->setFilter(GL_LINEAR);
diff --git a/libs/hwui/GradientCache.h b/libs/hwui/GradientCache.h
index 49be19a..3fd8e80 100644
--- a/libs/hwui/GradientCache.h
+++ b/libs/hwui/GradientCache.h
@@ -26,6 +26,8 @@
 #include <utils/LruCache.h>
 #include <utils/Mutex.h>
 
+#include "FloatColor.h"
+
 namespace android {
 namespace uirenderer {
 
@@ -150,25 +152,13 @@
     void getGradientInfo(const uint32_t* colors, const int count, GradientInfo& info);
 
     size_t bytesPerPixel() const;
+    size_t sourceBytesPerPixel() const;
 
-    struct GradientColor {
-        float r;
-        float g;
-        float b;
-        float a;
-    };
-
-    typedef void (GradientCache::*ChannelSplitter)(uint32_t inColor,
-            GradientColor& outColor) const;
-
-    void splitToBytes(uint32_t inColor, GradientColor& outColor) const;
-    void splitToFloats(uint32_t inColor, GradientColor& outColor) const;
-
-    typedef void (GradientCache::*ChannelMixer)(GradientColor& start, GradientColor& end,
+    typedef void (GradientCache::*ChannelMixer)(FloatColor& start, FloatColor& end,
             float amount, uint8_t*& dst) const;
 
-    void mixBytes(GradientColor& start, GradientColor& end, float amount, uint8_t*& dst) const;
-    void mixFloats(GradientColor& start, GradientColor& end, float amount, uint8_t*& dst) const;
+    void mixBytes(FloatColor& start, FloatColor& end, float amount, uint8_t*& dst) const;
+    void mixFloats(FloatColor& start, FloatColor& end, float amount, uint8_t*& dst) const;
 
     LruCache<GradientCacheEntry, Texture*> mCache;
 
@@ -178,6 +168,7 @@
     GLint mMaxTextureSize;
     bool mUseFloatTexture;
     bool mHasNpot;
+    bool mHasSRGB;
 
     mutable Mutex mLock;
 }; // class GradientCache
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index c688a96..01650ef 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -75,7 +75,7 @@
     }
 
     void setSize(uint32_t width, uint32_t height) {
-        texture.updateSize(width, height, texture.format());
+        texture.updateSize(width, height, texture.internalFormat(), texture.format());
     }
 
     inline void setBlend(bool blend) {
diff --git a/libs/hwui/PatchCache.cpp b/libs/hwui/PatchCache.cpp
index a6c281d..52c62cc 100644
--- a/libs/hwui/PatchCache.cpp
+++ b/libs/hwui/PatchCache.cpp
@@ -225,17 +225,15 @@
 
 static const UvMapper sIdentity;
 
-const Patch* PatchCache::get(const AssetAtlas::Entry* entry,
-        const uint32_t bitmapWidth, const uint32_t bitmapHeight,
+const Patch* PatchCache::get( const uint32_t bitmapWidth, const uint32_t bitmapHeight,
         const float pixelWidth, const float pixelHeight, const Res_png_9patch* patch) {
 
     const PatchDescription description(bitmapWidth, bitmapHeight, pixelWidth, pixelHeight, patch);
     const Patch* mesh = mCache.get(description);
 
     if (!mesh) {
-        const UvMapper& mapper = entry ? entry->uvMapper : sIdentity;
         Patch* newMesh = new Patch(bitmapWidth, bitmapHeight,
-                pixelWidth, pixelHeight, mapper, patch);
+                pixelWidth, pixelHeight, sIdentity, patch);
 
         if (newMesh->vertices) {
             setupMesh(newMesh);
diff --git a/libs/hwui/PatchCache.h b/libs/hwui/PatchCache.h
index 6e6a730..0624c35 100644
--- a/libs/hwui/PatchCache.h
+++ b/libs/hwui/PatchCache.h
@@ -22,7 +22,6 @@
 
 #include <androidfw/ResourceTypes.h>
 
-#include "AssetAtlas.h"
 #include "Debug.h"
 #include "utils/Pair.h"
 
@@ -54,8 +53,7 @@
     explicit PatchCache(RenderState& renderState);
     ~PatchCache();
 
-    const Patch* get(const AssetAtlas::Entry* entry,
-            const uint32_t bitmapWidth, const uint32_t bitmapHeight,
+    const Patch* get(const uint32_t bitmapWidth, const uint32_t bitmapHeight,
             const float pixelWidth, const float pixelHeight, const Res_png_9patch* patch);
     void clear();
 
diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h
index e5200a5..f5beb62 100644
--- a/libs/hwui/Program.h
+++ b/libs/hwui/Program.h
@@ -85,6 +85,8 @@
 #define PROGRAM_HAS_DEBUG_HIGHLIGHT 42
 #define PROGRAM_HAS_ROUND_RECT_CLIP 43
 
+#define PROGRAM_HAS_GAMMA_CORRECTION 44
+
 ///////////////////////////////////////////////////////////////////////////////
 // Types
 ///////////////////////////////////////////////////////////////////////////////
@@ -158,6 +160,8 @@
     bool hasDebugHighlight;
     bool hasRoundRectClip;
 
+    bool hasGammaCorrection;
+
     /**
      * Resets this description. All fields are reset back to the default
      * values they hold after building a new instance.
@@ -196,6 +200,8 @@
 
         hasDebugHighlight = false;
         hasRoundRectClip = false;
+
+        hasGammaCorrection = false;
     }
 
     /**
@@ -262,6 +268,7 @@
         if (hasColors) key |= programid(0x1) << PROGRAM_HAS_COLORS;
         if (hasDebugHighlight) key |= programid(0x1) << PROGRAM_HAS_DEBUG_HIGHLIGHT;
         if (hasRoundRectClip) key |= programid(0x1) << PROGRAM_HAS_ROUND_RECT_CLIP;
+        if (hasGammaCorrection) key |= programid(0x1) << PROGRAM_HAS_GAMMA_CORRECTION;
         return key;
     }
 
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index 59225e1..315c60e 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -17,8 +17,8 @@
 #include <utils/String8.h>
 
 #include "Caches.h"
-#include "Dither.h"
 #include "ProgramCache.h"
+#include "Properties.h"
 
 namespace android {
 namespace uirenderer {
@@ -69,22 +69,16 @@
         "varying highp vec2 outBitmapTexCoords;\n";
 const char* gVS_Header_Varyings_HasGradient[6] = {
         // Linear
-        "varying highp vec2 linear;\n"
-        "varying vec2 ditherTexCoords;\n",
-        "varying float linear;\n"
-        "varying vec2 ditherTexCoords;\n",
+        "varying highp vec2 linear;\n",
+        "varying float linear;\n",
 
         // Circular
-        "varying highp vec2 circular;\n"
-        "varying vec2 ditherTexCoords;\n",
-        "varying highp vec2 circular;\n"
-        "varying vec2 ditherTexCoords;\n",
+        "varying highp vec2 circular;\n",
+        "varying highp vec2 circular;\n",
 
         // Sweep
-        "varying highp vec2 sweep;\n"
-        "varying vec2 ditherTexCoords;\n",
-        "varying highp vec2 sweep;\n"
-        "varying vec2 ditherTexCoords;\n",
+        "varying highp vec2 sweep;\n",
+        "varying highp vec2 sweep;\n",
 };
 const char* gVS_Header_Varyings_HasRoundRectClip =
         "varying highp vec2 roundRectPos;\n";
@@ -98,22 +92,16 @@
         "    outTexCoords = (mainTextureTransform * vec4(texCoords, 0.0, 1.0)).xy;\n";
 const char* gVS_Main_OutGradient[6] = {
         // Linear
-        "    linear = vec2((screenSpace * position).x, 0.5);\n"
-        "    ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n",
-        "    linear = (screenSpace * position).x;\n"
-        "    ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n",
+        "    linear = vec2((screenSpace * position).x, 0.5);\n",
+        "    linear = (screenSpace * position).x;\n",
 
         // Circular
-        "    circular = (screenSpace * position).xy;\n"
-        "    ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n",
-        "    circular = (screenSpace * position).xy;\n"
-        "    ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n",
+        "    circular = (screenSpace * position).xy;\n",
+        "    circular = (screenSpace * position).xy;\n",
 
         // Sweep
+        "    sweep = (screenSpace * position).xy;\n",
         "    sweep = (screenSpace * position).xy;\n"
-        "    ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n",
-        "    sweep = (screenSpace * position).xy;\n"
-        "    ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n",
 };
 const char* gVS_Main_OutBitmapTexCoords =
         "    outBitmapTexCoords = (textureTransform * position).xy * textureDimension;\n";
@@ -147,12 +135,11 @@
         "uniform sampler2D baseSampler;\n";
 const char* gFS_Uniforms_ExternalTextureSampler =
         "uniform samplerExternalOES baseSampler;\n";
-const char* gFS_Uniforms_Dither =
-        "uniform sampler2D ditherSampler;";
 const char* gFS_Uniforms_GradientSampler[2] = {
-        "%s\n"
+        "uniform vec2 screenSize;\n"
         "uniform sampler2D gradientSampler;\n",
-        "%s\n"
+
+        "uniform vec2 screenSize;\n"
         "uniform vec4 startColor;\n"
         "uniform vec4 endColor;\n"
 };
@@ -172,18 +159,51 @@
         "uniform vec4 roundRectInnerRectLTRB;\n"
         "uniform float roundRectRadius;\n";
 
+// Dithering must be done in the quantization space
+// When we are writing to an sRGB framebuffer, we must do the following:
+//     EOCF(OECF(color) + dither)
+// We approximate the transfer functions with gamma 2.0 to avoid branches and pow()
+// The dithering pattern is generated with a triangle noise generator in the range [-0.5,1.5[
+// TODO: Handle linear fp16 render targets
+const char* gFS_Dither_Functions =
+        "\nmediump float triangleNoise(const highp vec2 n) {\n"
+        "    highp vec2 p = fract(n * vec2(5.3987, 5.4421));\n"
+        "    p += dot(p.yx, p.xy + vec2(21.5351, 14.3137));\n"
+        "    highp float xy = p.x * p.y;\n"
+        "    return fract(xy * 95.4307) + fract(xy * 75.04961) - 0.5;\n"
+        "}\n";
+const char* gFS_Dither_Preamble[2] = {
+        // Linear framebuffer
+        "\nvec4 dither(const vec4 color) {\n"
+        "    return vec4(color.rgb + (triangleNoise(gl_FragCoord.xy * screenSize.xy) / 255.0), color.a);"
+        "}\n",
+        // sRGB framebuffer
+        "\nvec4 dither(const vec4 color) {\n"
+        "    vec3 dithered = sqrt(color.rgb) + (triangleNoise(gl_FragCoord.xy * screenSize.xy) / 255.0);\n"
+        "    return vec4(dithered * dithered, color.a);\n"
+        "}\n"
+};
+
+// Uses luminance coefficients from Rec.709 to choose the appropriate gamma
+// The gamma() function assumes that bright text will be displayed on a dark
+// background and that dark text will be displayed on bright background
+// The gamma coefficient is chosen to thicken or thin the text accordingly
+// The dot product used to compute the luminance could be approximated with
+// a simple max(color.r, color.g, color.b)
+const char* gFS_Gamma_Preamble =
+        "\n#define GAMMA (%.2f)\n"
+        "#define GAMMA_INV (%.2f)\n"
+        "\nfloat gamma(float a, const vec3 color) {\n"
+        "    float luminance = dot(color, vec3(0.2126, 0.7152, 0.0722));\n"
+        "    return pow(a, luminance < 0.5 ? GAMMA_INV : GAMMA);\n"
+        "}\n";
+
 const char* gFS_Main =
         "\nvoid main(void) {\n"
-        "    lowp vec4 fragColor;\n";
+        "    vec4 fragColor;\n";
 
-const char* gFS_Main_Dither[2] = {
-        // ES 2.0
-        "texture2D(ditherSampler, ditherTexCoords).a * " STR(DITHER_KERNEL_SIZE_INV_SQUARE),
-        // ES 3.0
-        "texture2D(ditherSampler, ditherTexCoords).a"
-};
-const char* gFS_Main_AddDitherToGradient =
-        "    gradientColor += %s;\n";
+const char* gFS_Main_AddDither =
+        "    fragColor = dither(fragColor);\n";
 
 // Fast cases
 const char* gFS_Fast_SingleColor =
@@ -202,24 +222,32 @@
         "\nvoid main(void) {\n"
         "    gl_FragColor = texture2D(baseSampler, outTexCoords);\n"
         "}\n\n";
+const char* gFS_Fast_SingleA8Texture_ApplyGamma =
+        "\nvoid main(void) {\n"
+        "    gl_FragColor = vec4(0.0, 0.0, 0.0, pow(texture2D(baseSampler, outTexCoords).a, GAMMA));\n"
+        "}\n\n";
 const char* gFS_Fast_SingleModulateA8Texture =
         "\nvoid main(void) {\n"
         "    gl_FragColor = color * texture2D(baseSampler, outTexCoords).a;\n"
         "}\n\n";
+const char* gFS_Fast_SingleModulateA8Texture_ApplyGamma =
+        "\nvoid main(void) {\n"
+        "    gl_FragColor = color * gamma(texture2D(baseSampler, outTexCoords).a, color.rgb);\n"
+        "}\n\n";
 const char* gFS_Fast_SingleGradient[2] = {
         "\nvoid main(void) {\n"
-        "    gl_FragColor = %s + texture2D(gradientSampler, linear);\n"
+        "    gl_FragColor = dither(texture2D(gradientSampler, linear));\n"
         "}\n\n",
         "\nvoid main(void) {\n"
-        "    gl_FragColor = %s + mix(startColor, endColor, clamp(linear, 0.0, 1.0));\n"
+        "    gl_FragColor = dither(mix(startColor, endColor, clamp(linear, 0.0, 1.0)));\n"
         "}\n\n",
 };
 const char* gFS_Fast_SingleModulateGradient[2] = {
         "\nvoid main(void) {\n"
-        "    gl_FragColor = %s + color.a * texture2D(gradientSampler, linear);\n"
+        "    gl_FragColor = dither(color.a * texture2D(gradientSampler, linear));\n"
         "}\n\n",
         "\nvoid main(void) {\n"
-        "    gl_FragColor = %s + color.a * mix(startColor, endColor, clamp(linear, 0.0, 1.0));\n"
+        "    gl_FragColor = dither(color.a * mix(startColor, endColor, clamp(linear, 0.0, 1.0)));\n"
         "}\n\n"
 };
 
@@ -239,11 +267,13 @@
         // Modulate
         "    fragColor = color * texture2D(baseSampler, outTexCoords);\n"
 };
-const char* gFS_Main_FetchA8Texture[2] = {
+const char* gFS_Main_FetchA8Texture[4] = {
         // Don't modulate
         "    fragColor = texture2D(baseSampler, outTexCoords);\n",
+        "    fragColor = texture2D(baseSampler, outTexCoords);\n",
         // Modulate
         "    fragColor = color * texture2D(baseSampler, outTexCoords).a;\n",
+        "    fragColor = color * gamma(texture2D(baseSampler, outTexCoords).a, color.rgb);\n",
 };
 const char* gFS_Main_FetchGradient[6] = {
         // Linear
@@ -271,29 +301,38 @@
         "    fragColor = blendShaders(gradientColor, bitmapColor)";
 const char* gFS_Main_BlendShadersGB =
         "    fragColor = blendShaders(bitmapColor, gradientColor)";
-const char* gFS_Main_BlendShaders_Modulate[3] = {
+const char* gFS_Main_BlendShaders_Modulate[6] = {
         // Don't modulate
         ";\n",
+        ";\n",
         // Modulate
         " * color.a;\n",
+        " * color.a;\n",
         // Modulate with alpha 8 texture
         " * texture2D(baseSampler, outTexCoords).a;\n",
+        " * gamma(texture2D(baseSampler, outTexCoords).a, color.rgb);\n",
 };
-const char* gFS_Main_GradientShader_Modulate[3] = {
+const char* gFS_Main_GradientShader_Modulate[6] = {
         // Don't modulate
         "    fragColor = gradientColor;\n",
+        "    fragColor = gradientColor;\n",
         // Modulate
         "    fragColor = gradientColor * color.a;\n",
+        "    fragColor = gradientColor * color.a;\n",
         // Modulate with alpha 8 texture
         "    fragColor = gradientColor * texture2D(baseSampler, outTexCoords).a;\n",
+        "    fragColor = gradientColor * gamma(texture2D(baseSampler, outTexCoords).a, gradientColor.rgb);\n",
     };
-const char* gFS_Main_BitmapShader_Modulate[3] = {
+const char* gFS_Main_BitmapShader_Modulate[6] = {
         // Don't modulate
         "    fragColor = bitmapColor;\n",
+        "    fragColor = bitmapColor;\n",
         // Modulate
         "    fragColor = bitmapColor * color.a;\n",
+        "    fragColor = bitmapColor * color.a;\n",
         // Modulate with alpha 8 texture
         "    fragColor = bitmapColor * texture2D(baseSampler, outTexCoords).a;\n",
+        "    fragColor = bitmapColor * gamma(texture2D(baseSampler, outTexCoords).a, bitmapColor.rgb);\n",
     };
 const char* gFS_Main_FragColor =
         "    gl_FragColor = fragColor;\n";
@@ -385,7 +424,8 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 ProgramCache::ProgramCache(Extensions& extensions)
-        : mHasES3(extensions.getMajorGlVersion() >= 3) {
+        : mHasES3(extensions.getMajorGlVersion() >= 3)
+        , mHasSRGB(extensions.hasSRGB()) {
 }
 
 ProgramCache::~ProgramCache() {
@@ -518,6 +558,7 @@
 static bool shaderOp(const ProgramDescription& description, String8& shader,
         const int modulateOp, const char** snippets) {
     int op = description.hasAlpha8Texture ? MODULATE_OP_MODULATE_A8 : modulateOp;
+    op = op * 2 + description.hasGammaCorrection;
     shader.append(snippets[op]);
     return description.hasAlpha8Texture;
 }
@@ -570,13 +611,16 @@
         shader.append(gFS_Uniforms_ExternalTextureSampler);
     }
     if (description.hasGradient) {
-        shader.appendFormat(gFS_Uniforms_GradientSampler[description.isSimpleGradient],
-                gFS_Uniforms_Dither);
+        shader.append(gFS_Uniforms_GradientSampler[description.isSimpleGradient]);
     }
     if (description.hasRoundRectClip) {
         shader.append(gFS_Uniforms_HasRoundRectClip);
     }
 
+    if (description.hasGammaCorrection) {
+        shader.appendFormat(gFS_Gamma_Preamble, Properties::textGamma, 1.0f / Properties::textGamma);
+    }
+
     // Optimization for common cases
     if (!description.hasVertexAlpha
             && !blendFramebuffer
@@ -607,18 +651,26 @@
             fast = true;
         } else if (singleA8Texture) {
             if (!description.modulate) {
-                shader.append(gFS_Fast_SingleA8Texture);
+                if (description.hasGammaCorrection) {
+                    shader.append(gFS_Fast_SingleA8Texture_ApplyGamma);
+                } else {
+                    shader.append(gFS_Fast_SingleA8Texture);
+                }
             } else {
-                shader.append(gFS_Fast_SingleModulateA8Texture);
+                if (description.hasGammaCorrection) {
+                    shader.append(gFS_Fast_SingleModulateA8Texture_ApplyGamma);
+                } else {
+                    shader.append(gFS_Fast_SingleModulateA8Texture);
+                }
             }
             fast = true;
         } else if (singleGradient) {
+            shader.append(gFS_Dither_Functions);
+            shader.append(gFS_Dither_Preamble[mHasSRGB]);
             if (!description.modulate) {
-                shader.appendFormat(gFS_Fast_SingleGradient[description.isSimpleGradient],
-                        gFS_Main_Dither[mHasES3]);
+                shader.append(gFS_Fast_SingleGradient[description.isSimpleGradient]);
             } else {
-                shader.appendFormat(gFS_Fast_SingleModulateGradient[description.isSimpleGradient],
-                        gFS_Main_Dither[mHasES3]);
+                shader.append(gFS_Fast_SingleModulateGradient[description.isSimpleGradient]);
             }
             fast = true;
         }
@@ -652,6 +704,10 @@
     if (description.isBitmapNpot) {
         generateTextureWrap(shader, description.bitmapWrapS, description.bitmapWrapT);
     }
+    if (description.hasGradient) {
+        shader.append(gFS_Dither_Functions);
+        shader.append(gFS_Dither_Preamble[mHasSRGB]);
+    }
 
     // Begin the shader
     shader.append(gFS_Main); {
@@ -659,7 +715,8 @@
         if (description.hasTexture || description.hasExternalTexture) {
             if (description.hasAlpha8Texture) {
                 if (!description.hasGradient && !description.hasBitmap) {
-                    shader.append(gFS_Main_FetchA8Texture[modulateOp]);
+                    shader.append(
+                            gFS_Main_FetchA8Texture[modulateOp * 2 + description.hasGammaCorrection]);
                 }
             } else {
                 shader.append(gFS_Main_FetchTexture[modulateOp]);
@@ -671,7 +728,6 @@
         }
         if (description.hasGradient) {
             shader.append(gFS_Main_FetchGradient[gradientIndex(description)]);
-            shader.appendFormat(gFS_Main_AddDitherToGradient, gFS_Main_Dither[mHasES3]);
         }
         if (description.hasBitmap) {
             if (!description.isBitmapNpot) {
@@ -715,6 +771,10 @@
             }
         }
 
+        if (description.hasGradient) {
+            shader.append(gFS_Main_AddDither);
+        }
+
         // Output the fragment
         if (!blendFramebuffer) {
             shader.append(gFS_Main_FragColor);
diff --git a/libs/hwui/ProgramCache.h b/libs/hwui/ProgramCache.h
index 9ac885b..292ecdf 100644
--- a/libs/hwui/ProgramCache.h
+++ b/libs/hwui/ProgramCache.h
@@ -59,6 +59,7 @@
     std::map<programid, std::unique_ptr<Program>> mCache;
 
     const bool mHasES3;
+    const bool mHasSRGB;
 }; // class ProgramCache
 
 }; // namespace uirenderer
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index eedc9e7..92a49d2 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -203,7 +203,7 @@
 #define PROPERTY_TEXT_LARGE_CACHE_WIDTH "ro.hwui.text_large_cache_width"
 #define PROPERTY_TEXT_LARGE_CACHE_HEIGHT "ro.hwui.text_large_cache_height"
 
-// Gamma (>= 1.0, <= 10.0)
+// Gamma (>= 1.0, <= 3.0)
 #define PROPERTY_TEXT_GAMMA "hwui.text_gamma"
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -222,7 +222,7 @@
 
 #define DEFAULT_TEXTURE_CACHE_FLUSH_RATE 0.6f
 
-#define DEFAULT_TEXT_GAMMA 1.4f
+#define DEFAULT_TEXT_GAMMA 1.45f // Match design tools
 
 // cap to 256 to limite paths in the path cache
 #define DEFAULT_PATH_TEXTURE_CAP 256
diff --git a/libs/hwui/PropertyValuesHolder.cpp b/libs/hwui/PropertyValuesHolder.cpp
index 6ba0ab5..2a03e6a 100644
--- a/libs/hwui/PropertyValuesHolder.cpp
+++ b/libs/hwui/PropertyValuesHolder.cpp
@@ -16,6 +16,7 @@
 
 #include "PropertyValuesHolder.h"
 
+#include "utils/Color.h"
 #include "utils/VectorDrawableUtils.h"
 
 #include <utils/Log.h>
@@ -25,18 +26,26 @@
 
 using namespace VectorDrawable;
 
-inline U8CPU lerp(U8CPU fromValue, U8CPU toValue, float fraction) {
-    return (U8CPU) (fromValue * (1 - fraction) + toValue * fraction);
+inline constexpr float lerp(float fromValue, float toValue, float fraction) {
+    return float (fromValue * (1 - fraction) + toValue * fraction);
+}
+
+inline constexpr float linearize(U8CPU component) {
+    return EOCF_sRGB(component / 255.0f);
 }
 
 // TODO: Add a test for this
 void ColorEvaluator::evaluate(SkColor* outColor,
         const SkColor& fromColor, const SkColor& toColor, float fraction) const {
-    U8CPU alpha = lerp(SkColorGetA(fromColor), SkColorGetA(toColor), fraction);
-    U8CPU red = lerp(SkColorGetR(fromColor), SkColorGetR(toColor), fraction);
-    U8CPU green = lerp(SkColorGetG(fromColor), SkColorGetG(toColor), fraction);
-    U8CPU blue = lerp(SkColorGetB(fromColor), SkColorGetB(toColor), fraction);
-    *outColor = SkColorSetARGB(alpha, red, green, blue);
+    float a = lerp(SkColorGetA(fromColor) / 255.0f, SkColorGetA(toColor) / 255.0f, fraction);
+    float r = lerp(linearize(SkColorGetR(fromColor)), linearize(SkColorGetR(toColor)), fraction);
+    float g = lerp(linearize(SkColorGetG(fromColor)), linearize(SkColorGetG(toColor)), fraction);
+    float b = lerp(linearize(SkColorGetB(fromColor)), linearize(SkColorGetB(toColor)), fraction);
+    *outColor = SkColorSetARGB(
+            (U8CPU) roundf(a * 255.0f),
+            (U8CPU) roundf(OECF_sRGB(r) * 255.0f),
+            (U8CPU) roundf(OECF_sRGB(g) * 255.0f),
+            (U8CPU) roundf(OECF_sRGB(b) * 255.0f));
 }
 
 void PathEvaluator::evaluate(PathData* out,
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index ddca122..22c6dfc 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -197,7 +197,7 @@
 
     Texture sourceTexture(caches);
     sourceTexture.wrap(sourceTexId,
-            sourceBuffer->getWidth(), sourceBuffer->getHeight(), 0 /* total lie */);
+            sourceBuffer->getWidth(), sourceBuffer->getHeight(), 0, 0 /* total lie */);
 
     CopyResult copyResult = copyTextureInto(caches, renderThread.renderState(),
             sourceTexture, texTransform, srcRect, bitmap);
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index a65c22c..ebc41b1 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -216,7 +216,6 @@
             : SUPER(BitmapOp)
             , bitmap(bitmap) {}
     const SkBitmap* bitmap;
-    // TODO: asset atlas/texture id lookup?
 };
 
 struct BitmapMeshOp : RecordedOp {
diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp
index 6f4a683..9838df2 100644
--- a/libs/hwui/SkiaShader.cpp
+++ b/libs/hwui/SkiaShader.cpp
@@ -177,11 +177,12 @@
         outData->endColor.set(gradInfo.fColors[1]);
     }
 
-    outData->ditherSampler = (*textureUnit)++;
     return true;
 }
 
-void applyGradient(Caches& caches, const SkiaShaderData::GradientShaderData& data) {
+void applyGradient(Caches& caches, const SkiaShaderData::GradientShaderData& data,
+        const GLsizei width, const GLsizei height) {
+
     if (CC_UNLIKELY(data.gradientTexture)) {
         caches.textureState().activateTexture(data.gradientSampler);
         bindTexture(&caches, data.gradientTexture, data.wrapST, data.wrapST);
@@ -191,10 +192,7 @@
         bindUniformColor(caches.program().getUniform("endColor"), data.endColor);
     }
 
-    // TODO: remove sampler slot incrementing from dither.setupProgram,
-    // since this assignment of slots is done at store, not apply time
-    GLuint ditherSampler = data.ditherSampler;
-    caches.dither.setupProgram(caches.program(), &ditherSampler);
+    glUniform2f(caches.program().getUniform("screenSize"), 1.0f / width, 1.0f / height);
     glUniformMatrix4fv(caches.program().getUniform("screenSpace"), 1,
             GL_FALSE, &data.screenSpace.data[0]);
 }
@@ -208,13 +206,7 @@
         return false;
     }
 
-    /*
-     * Bypass the AssetAtlas, since those textures:
-     * 1) require UV mapping, which isn't implemented in matrix computation below
-     * 2) can't handle REPEAT simply
-     * 3) are safe to upload here (outside of sync stage), since they're static
-     */
-    outData->bitmapTexture = caches.textureCache.getAndBypassAtlas(&bitmap);
+    outData->bitmapTexture = caches.textureCache.get(&bitmap);
     if (!outData->bitmapTexture) return false;
 
     outData->bitmapSampler = (*textureUnit)++;
@@ -388,11 +380,12 @@
     outData->skiaShaderType = kNone_SkiaShaderType;
 }
 
-void SkiaShader::apply(Caches& caches, const SkiaShaderData& data) {
+void SkiaShader::apply(Caches& caches, const SkiaShaderData& data,
+        const GLsizei width, const GLsizei height) {
     if (!data.skiaShaderType) return;
 
     if (data.skiaShaderType & kGradient_SkiaShaderType) {
-        applyGradient(caches, data.gradientData);
+        applyGradient(caches, data.gradientData, width, height);
     }
     if (data.skiaShaderType & kBitmap_SkiaShaderType) {
         applyBitmap(caches, data.bitmapData);
diff --git a/libs/hwui/SkiaShader.h b/libs/hwui/SkiaShader.h
index 884196d..5854289 100644
--- a/libs/hwui/SkiaShader.h
+++ b/libs/hwui/SkiaShader.h
@@ -62,7 +62,6 @@
     } bitmapData;
     struct GradientShaderData {
         Matrix4 screenSpace;
-        GLuint ditherSampler;
 
         // simple gradient
         FloatColor startColor;
@@ -72,7 +71,6 @@
         Texture* gradientTexture;
         GLuint gradientSampler;
         GLenum wrapST;
-
     } gradientData;
     struct LayerShaderData {
         Layer* layer;
@@ -90,7 +88,8 @@
     static void store(Caches& caches, const SkShader& shader, const Matrix4& modelViewMatrix,
             GLuint* textureUnit, ProgramDescription* description,
             SkiaShaderData* outData);
-    static void apply(Caches& caches, const SkiaShaderData& data);
+    static void apply(Caches& caches, const SkiaShaderData& data,
+            const GLsizei width, const GLsizei height);
 };
 
 }; // namespace uirenderer
diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp
index 4f49a35..908f572 100644
--- a/libs/hwui/Texture.cpp
+++ b/libs/hwui/Texture.cpp
@@ -26,19 +26,23 @@
 namespace android {
 namespace uirenderer {
 
+// Number of bytes used by a texture in the given format
 static int bytesPerPixel(GLint glFormat) {
     switch (glFormat) {
     // The wrapped-texture case, usually means a SurfaceTexture
     case 0:
         return 0;
+    case GL_LUMINANCE:
     case GL_ALPHA:
         return 1;
+    case GL_SRGB8:
     case GL_RGB:
         return 3;
+    case GL_SRGB8_ALPHA8:
     case GL_RGBA:
         return 4;
     case GL_RGBA16F:
-        return 16;
+        return 8;
     default:
         LOG_ALWAYS_FATAL("UNKNOWN FORMAT %d", glFormat);
     }
@@ -83,14 +87,16 @@
     mId = 0;
 }
 
-bool Texture::updateSize(uint32_t width, uint32_t height, GLint format) {
-    if (mWidth == width && mHeight == height && mFormat == format) {
+bool Texture::updateSize(uint32_t width, uint32_t height, GLint internalFormat, GLint format) {
+    if (mWidth == width && mHeight == height &&
+            mFormat == format && mInternalFormat == internalFormat) {
         return false;
     }
     mWidth = width;
     mHeight = height;
     mFormat = format;
-    notifySizeChanged(mWidth * mHeight * bytesPerPixel(mFormat));
+    mInternalFormat = internalFormat;
+    notifySizeChanged(mWidth * mHeight * bytesPerPixel(internalFormat));
     return true;
 }
 
@@ -101,10 +107,10 @@
     mMagFilter = GL_LINEAR;
 }
 
-void Texture::upload(GLint internalformat, uint32_t width, uint32_t height,
+void Texture::upload(GLint internalFormat, uint32_t width, uint32_t height,
         GLenum format, GLenum type, const void* pixels) {
     GL_CHECKPOINT(MODERATE);
-    bool needsAlloc = updateSize(width, height, internalformat);
+    bool needsAlloc = updateSize(width, height, internalFormat, format);
     if (!mId) {
         glGenTextures(1, &mId);
         needsAlloc = true;
@@ -112,17 +118,17 @@
     }
     mCaches.textureState().bindTexture(GL_TEXTURE_2D, mId);
     if (needsAlloc) {
-        glTexImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight, 0,
+        glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, mWidth, mHeight, 0,
                 format, type, pixels);
     } else if (pixels) {
-        glTexSubImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight, 0,
+        glTexSubImage2D(GL_TEXTURE_2D, 0, internalFormat, mWidth, mHeight, 0,
                 format, type, pixels);
     }
     GL_CHECKPOINT(MODERATE);
 }
 
-static void uploadToTexture(bool resize, GLenum format, GLenum type, GLsizei stride, GLsizei bpp,
-        GLsizei width, GLsizei height, const GLvoid * data) {
+static void uploadToTexture(bool resize, GLint internalFormat, GLenum format, GLenum type,
+        GLsizei stride, GLsizei bpp, GLsizei width, GLsizei height, const GLvoid * data) {
 
     const bool useStride = stride != width
             && Caches::getInstance().extensions().hasUnpackRowLength();
@@ -132,7 +138,7 @@
         }
 
         if (resize) {
-            glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, data);
+            glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, type, data);
         } else {
             glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, data);
         }
@@ -156,7 +162,7 @@
         }
 
         if (resize) {
-            glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, temp);
+            glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, type, temp);
         } else {
             glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, temp);
         }
@@ -166,31 +172,44 @@
 }
 
 static void uploadSkBitmapToTexture(const SkBitmap& bitmap,
-        bool resize, GLenum format, GLenum type) {
-    uploadToTexture(resize, format, type, bitmap.rowBytesAsPixels(), bitmap.bytesPerPixel(),
-            bitmap.width(), bitmap.height(), bitmap.getPixels());
+        bool resize, GLint internalFormat, GLenum format, GLenum type) {
+    uploadToTexture(resize, internalFormat, format, type, bitmap.rowBytesAsPixels(),
+            bitmap.bytesPerPixel(), bitmap.width(), bitmap.height(), bitmap.getPixels());
 }
 
-static void colorTypeToGlFormatAndType(SkColorType colorType,
-        GLint* outFormat, GLint* outType) {
+static void colorTypeToGlFormatAndType(const Caches& caches, SkColorType colorType,
+        bool needSRGB, GLint* outInternalFormat, GLint* outFormat, GLint* outType) {
     switch (colorType) {
     case kAlpha_8_SkColorType:
         *outFormat = GL_ALPHA;
+        *outInternalFormat = GL_ALPHA;
         *outType = GL_UNSIGNED_BYTE;
         break;
     case kRGB_565_SkColorType:
-        *outFormat = GL_RGB;
-        *outType = GL_UNSIGNED_SHORT_5_6_5;
+        if (needSRGB) {
+            // We would ideally use a GL_RGB/GL_SRGB8 texture but the
+            // intermediate Skia bitmap needs to be ARGB_8888
+            *outFormat = GL_RGBA;
+            *outInternalFormat = caches.rgbaInternalFormat();
+            *outType = GL_UNSIGNED_BYTE;
+        } else {
+            *outFormat = GL_RGB;
+            *outInternalFormat = GL_RGB;
+            *outType = GL_UNSIGNED_SHORT_5_6_5;
+        }
         break;
     // ARGB_4444 and Index_8 are both upconverted to RGBA_8888
     case kARGB_4444_SkColorType:
     case kIndex_8_SkColorType:
     case kN32_SkColorType:
         *outFormat = GL_RGBA;
+        *outInternalFormat = caches.rgbaInternalFormat(needSRGB);
         *outType = GL_UNSIGNED_BYTE;
         break;
     case kGray_8_SkColorType:
+        // TODO: Handle sRGB
         *outFormat = GL_LUMINANCE;
+        *outInternalFormat = GL_LUMINANCE;
         *outType = GL_UNSIGNED_BYTE;
         break;
     default:
@@ -224,29 +243,36 @@
         setDefaultParams = true;
     }
 
-    GLint format, type;
-    colorTypeToGlFormatAndType(bitmap.colorType(), &format, &type);
+    sk_sp<SkColorSpace> sRGB = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named);
+    bool needSRGB = bitmap.colorSpace() == sRGB.get();
 
-    if (updateSize(bitmap.width(), bitmap.height(), format)) {
+    GLint internalFormat, format, type;
+    colorTypeToGlFormatAndType(mCaches, bitmap.colorType(), needSRGB, &internalFormat, &format, &type);
+
+    if (updateSize(bitmap.width(), bitmap.height(), internalFormat, format)) {
         needsAlloc = true;
     }
 
     blend = !bitmap.isOpaque();
     mCaches.textureState().bindTexture(mId);
 
+    // TODO: Handle sRGB gray bitmaps
+    bool hasSRGB = mCaches.extensions().hasSRGB();
     if (CC_UNLIKELY(bitmap.colorType() == kARGB_4444_SkColorType
-            || bitmap.colorType() == kIndex_8_SkColorType)) {
+            || bitmap.colorType() == kIndex_8_SkColorType
+            || (bitmap.colorType() == kRGB_565_SkColorType && hasSRGB && needSRGB))) {
+
         SkBitmap rgbaBitmap;
-        rgbaBitmap.allocPixels(SkImageInfo::MakeN32(mWidth, mHeight,
-                bitmap.alphaType()));
+        rgbaBitmap.allocPixels(SkImageInfo::MakeN32(
+                mWidth, mHeight, bitmap.alphaType(), hasSRGB ? sRGB : nullptr));
         rgbaBitmap.eraseColor(0);
 
         SkCanvas canvas(rgbaBitmap);
         canvas.drawBitmap(bitmap, 0.0f, 0.0f, nullptr);
 
-        uploadSkBitmapToTexture(rgbaBitmap, needsAlloc, format, type);
+        uploadSkBitmapToTexture(rgbaBitmap, needsAlloc, internalFormat, format, type);
     } else {
-        uploadSkBitmapToTexture(bitmap, needsAlloc, format, type);
+        uploadSkBitmapToTexture(bitmap, needsAlloc, internalFormat, format, type);
     }
 
     if (canMipMap) {
@@ -262,11 +288,12 @@
     }
 }
 
-void Texture::wrap(GLuint id, uint32_t width, uint32_t height, GLint format) {
+void Texture::wrap(GLuint id, uint32_t width, uint32_t height, GLint internalFormat, GLint format) {
     mId = id;
     mWidth = width;
     mHeight = height;
     mFormat = format;
+    mInternalFormat = internalFormat;
     // We're wrapping an existing texture, so don't double count this memory
     notifySizeChanged(0);
 }
diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h
index b72742f..aa8a6d3 100644
--- a/libs/hwui/Texture.h
+++ b/libs/hwui/Texture.h
@@ -69,8 +69,8 @@
      *
      * The image data is undefined after calling this.
      */
-    void resize(uint32_t width, uint32_t height, GLint format) {
-        upload(format, width, height, format, GL_UNSIGNED_BYTE, nullptr);
+    void resize(uint32_t width, uint32_t height, GLint internalFormat, GLint format) {
+        upload(internalFormat, width, height, format, GL_UNSIGNED_BYTE, nullptr);
     }
 
     /**
@@ -85,13 +85,13 @@
     /**
      * Basically glTexImage2D/glTexSubImage2D.
      */
-    void upload(GLint internalformat, uint32_t width, uint32_t height,
+    void upload(GLint internalFormat, uint32_t width, uint32_t height,
             GLenum format, GLenum type, const void* pixels);
 
     /**
      * Wraps an existing texture.
      */
-    void wrap(GLuint id, uint32_t width, uint32_t height, GLint format);
+    void wrap(GLuint id, uint32_t width, uint32_t height, GLint internalFormat, GLint format);
 
     GLuint id() const {
         return mId;
@@ -109,6 +109,10 @@
         return mFormat;
     }
 
+    GLint internalFormat() const {
+        return mInternalFormat;
+    }
+
     /**
      * Generation of the backing bitmap,
      */
@@ -148,13 +152,14 @@
     friend class Layer;
 
     // Returns true if the size changed, false if it was the same
-    bool updateSize(uint32_t width, uint32_t height, GLint format);
+    bool updateSize(uint32_t width, uint32_t height, GLint internalFormat, GLint format);
     void resetCachedParams();
 
     GLuint mId = 0;
     uint32_t mWidth = 0;
     uint32_t mHeight = 0;
     GLint mFormat = 0;
+    GLint mInternalFormat = 0;
 
     /* See GLES spec section 3.8.14
      * "In the initial state, the value assigned to TEXTURE_MIN_FILTER is
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index 523924a..5ccdbda 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -18,7 +18,6 @@
 
 #include <utils/Mutex.h>
 
-#include "AssetAtlas.h"
 #include "Caches.h"
 #include "Texture.h"
 #include "TextureCache.h"
@@ -36,8 +35,7 @@
         : mCache(LruCache<uint32_t, Texture*>::kUnlimitedCapacity)
         , mSize(0)
         , mMaxSize(Properties::textureCacheSize)
-        , mFlushRate(Properties::textureCacheFlushRate)
-        , mAssetAtlas(nullptr) {
+        , mFlushRate(Properties::textureCacheFlushRate) {
     mCache.setOnEntryRemovedListener(this);
 
     glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
@@ -84,10 +82,6 @@
 // Caching
 ///////////////////////////////////////////////////////////////////////////////
 
-void TextureCache::setAssetAtlas(AssetAtlas* assetAtlas) {
-    mAssetAtlas = assetAtlas;
-}
-
 void TextureCache::resetMarkInUse(void* ownerToken) {
     LruCache<uint32_t, Texture*>::Iterator iter(mCache);
     while (iter.next()) {
@@ -108,14 +102,7 @@
 
 // Returns a prepared Texture* that either is already in the cache or can fit
 // in the cache (and is thus added to the cache)
-Texture* TextureCache::getCachedTexture(const SkBitmap* bitmap, AtlasUsageType atlasUsageType) {
-    if (CC_LIKELY(mAssetAtlas != nullptr) && atlasUsageType == AtlasUsageType::Use) {
-        AssetAtlas::Entry* entry = mAssetAtlas->getEntry(bitmap->pixelRef());
-        if (CC_UNLIKELY(entry)) {
-            return entry->texture;
-        }
-    }
-
+Texture* TextureCache::getCachedTexture(const SkBitmap* bitmap) {
     Texture* texture = mCache.get(bitmap->pixelRef()->getStableID());
 
     if (!texture) {
@@ -160,7 +147,7 @@
 }
 
 bool TextureCache::prefetchAndMarkInUse(void* ownerToken, const SkBitmap* bitmap) {
-    Texture* texture = getCachedTexture(bitmap, AtlasUsageType::Use);
+    Texture* texture = getCachedTexture(bitmap);
     if (texture) {
         texture->isInUse = ownerToken;
     }
@@ -168,11 +155,11 @@
 }
 
 bool TextureCache::prefetch(const SkBitmap* bitmap) {
-    return getCachedTexture(bitmap, AtlasUsageType::Use);
+    return getCachedTexture(bitmap);
 }
 
-Texture* TextureCache::get(const SkBitmap* bitmap, AtlasUsageType atlasUsageType) {
-    Texture* texture = getCachedTexture(bitmap, atlasUsageType);
+Texture* TextureCache::get(const SkBitmap* bitmap) {
+    Texture* texture = getCachedTexture(bitmap);
 
     if (!texture) {
         if (!canMakeTextureFromBitmap(bitmap)) {
diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h
index 0a61b6b..88ef771 100644
--- a/libs/hwui/TextureCache.h
+++ b/libs/hwui/TextureCache.h
@@ -47,8 +47,6 @@
 // Classes
 ///////////////////////////////////////////////////////////////////////////////
 
-class AssetAtlas;
-
 /**
  * A simple LRU texture cache. The cache has a maximum size expressed in bytes.
  * Any texture added to the cache causing the cache to grow beyond the maximum
@@ -85,20 +83,10 @@
     bool prefetch(const SkBitmap* bitmap);
 
     /**
-     * Returns the texture associated with the specified bitmap from either within the cache, or
-     * the AssetAtlas. If the texture cannot be found in the cache, a new texture is generated.
+     * Returns the texture associated with the specified bitmap from within the cache.
+     * If the texture cannot be found in the cache, a new texture is generated.
      */
-    Texture* get(const SkBitmap* bitmap) {
-        return get(bitmap, AtlasUsageType::Use);
-    }
-
-    /**
-     * Returns the texture associated with the specified bitmap. If the texture cannot be found in
-     * the cache, a new texture is generated, even if it resides in the AssetAtlas.
-     */
-    Texture* getAndBypassAtlas(const SkBitmap* bitmap) {
-        return get(bitmap, AtlasUsageType::Bypass);
-    }
+    Texture* get(const SkBitmap* bitmap);
 
     /**
      * Removes the texture associated with the specified pixelRef. This is meant
@@ -130,18 +118,10 @@
      */
     void flush();
 
-    void setAssetAtlas(AssetAtlas* assetAtlas);
-
 private:
-    enum class AtlasUsageType {
-        Use,
-        Bypass,
-    };
-
     bool canMakeTextureFromBitmap(const SkBitmap* bitmap);
 
-    Texture* get(const SkBitmap* bitmap, AtlasUsageType atlasUsageType);
-    Texture* getCachedTexture(const SkBitmap* bitmap, AtlasUsageType atlasUsageType);
+    Texture* getCachedTexture(const SkBitmap* bitmap);
 
     LruCache<uint32_t, Texture*> mCache;
 
@@ -155,8 +135,6 @@
 
     std::vector<uint32_t> mGarbage;
     mutable Mutex mLock;
-
-    AssetAtlas* mAssetAtlas;
 }; // class TextureCache
 
 }; // namespace uirenderer
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index 2b79941..4e5b9ad 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -561,8 +561,12 @@
 
 bool Tree::allocateBitmapIfNeeded(SkBitmap* outCache, int width, int height) {
     if (!canReuseBitmap(*outCache, width, height)) {
-        SkImageInfo info = SkImageInfo::Make(width, height,
-                kN32_SkColorType, kPremul_SkAlphaType);
+#ifndef ANDROID_ENABLE_LINEAR_BLENDING
+        sk_sp<SkColorSpace> colorSpace = nullptr;
+#else
+        sk_sp<SkColorSpace> colorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named);
+#endif
+        SkImageInfo info = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType, colorSpace);
         outCache->setInfo(info);
         // TODO: Count the bitmap cache against app's java heap
         outCache->allocPixels(info);
diff --git a/libs/hwui/Vertex.h b/libs/hwui/Vertex.h
index c1bf980..db982ad 100644
--- a/libs/hwui/Vertex.h
+++ b/libs/hwui/Vertex.h
@@ -19,6 +19,7 @@
 
 #include "Vector.h"
 
+#include "FloatColor.h"
 #include "utils/Macros.h"
 
 namespace android {
@@ -76,21 +77,19 @@
 REQUIRE_COMPATIBLE_LAYOUT(TextureVertex);
 
 /**
- * Simple structure to describe a vertex with a position, texture UV and ARGB color.
+ * Simple structure to describe a vertex with a position, texture UV and an
+ * sRGB color with alpha. The color is stored pre-multiplied in linear space.
  */
 struct ColorTextureVertex {
     float x, y;
     float u, v;
-    float r, g, b, a;
+    float r, g, b, a; // pre-multiplied linear
 
     static inline void set(ColorTextureVertex* vertex, float x, float y,
-            float u, float v, int color) {
-
-        float a =     ((color >> 24) & 0xff) / 255.0f;
-        float r = a * ((color >> 16) & 0xff) / 255.0f;
-        float g = a * ((color >>  8) & 0xff) / 255.0f;
-        float b = a * ((color) & 0xff) / 255.0f;
-        *vertex = { x, y, u, v, r, g, b, a };
+            float u, float v, uint32_t color) {
+        FloatColor c;
+        c.set(color);
+        *vertex = { x, y, u, v, c.r, c.g, c.b, c.a };
     }
 }; // struct ColorTextureVertex
 
diff --git a/libs/hwui/font/CacheTexture.cpp b/libs/hwui/font/CacheTexture.cpp
index 49e9f65..e2844ad06 100644
--- a/libs/hwui/font/CacheTexture.cpp
+++ b/libs/hwui/font/CacheTexture.cpp
@@ -180,7 +180,12 @@
         mPixelBuffer = PixelBuffer::create(mFormat, getWidth(), getHeight());
     }
 
-    mTexture.resize(mWidth, mHeight, mFormat);
+    GLint internalFormat = mFormat;
+    if (mFormat == GL_RGBA) {
+        internalFormat = mCaches.rgbaInternalFormat();
+    }
+
+    mTexture.resize(mWidth, mHeight, internalFormat, mFormat);
     mTexture.setFilter(getLinearFiltering() ? GL_LINEAR : GL_NEAREST);
     mTexture.setWrap(GL_CLAMP_TO_EDGE);
 }
diff --git a/libs/hwui/renderstate/OffscreenBufferPool.cpp b/libs/hwui/renderstate/OffscreenBufferPool.cpp
index 10a26e0..a9bbb27 100644
--- a/libs/hwui/renderstate/OffscreenBufferPool.cpp
+++ b/libs/hwui/renderstate/OffscreenBufferPool.cpp
@@ -22,6 +22,7 @@
 #include "utils/FatVector.h"
 #include "utils/TraceUtils.h"
 
+#include <utils/Color.h>
 #include <utils/Log.h>
 
 #include <GLES2/gl2.h>
@@ -44,7 +45,7 @@
     uint32_t height = computeIdealDimension(viewportHeight);
     ATRACE_FORMAT("Allocate %ux%u HW Layer", width, height);
     caches.textureState().activateTexture(0);
-    texture.resize(width, height, GL_RGBA);
+    texture.resize(width, height, caches.rgbaInternalFormat(), GL_RGBA);
     texture.blend = true;
     texture.setWrap(GL_CLAMP_TO_EDGE);
     // not setting filter on texture, since it's set when drawing, based on transform
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index ee4619d..84ab3f3 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -52,7 +52,6 @@
         mCaches = &Caches::createInstance(*this);
     }
     mCaches->init();
-    mCaches->textureCache.setAssetAtlas(&mAssetAtlas);
 }
 
 static void layerLostGlContext(Layer* layer) {
@@ -64,7 +63,6 @@
 
     // TODO: reset all cached state in state objects
     std::for_each(mActiveLayers.begin(), mActiveLayers.end(), layerLostGlContext);
-    mAssetAtlas.terminate();
 
     mCaches->terminate();
 
@@ -147,9 +145,17 @@
     meshState().resetVertexPointers();
     meshState().disableTexCoordsVertexArray();
     debugOverdraw(false, false);
+    // TODO: We need a way to know whether the functor is sRGB aware (b/32072673)
+    if (mCaches->extensions().hasSRGBWriteControl()) {
+        glDisable(GL_FRAMEBUFFER_SRGB_EXT);
+    }
 }
 
 void RenderState::resumeFromFunctorInvoke() {
+    if (mCaches->extensions().hasSRGBWriteControl()) {
+        glEnable(GL_FRAMEBUFFER_SRGB_EXT);
+    }
+
     glViewport(0, 0, mViewportWidth, mViewportHeight);
     glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
     debugOverdraw(false, false);
@@ -308,7 +314,7 @@
         glVertexAttribPointer(alphaLocation, 1, GL_FLOAT, GL_FALSE, vertices.stride, alphaCoords);
     }
     // Shader uniforms
-    SkiaShader::apply(*mCaches, fill.skiaShaderData);
+    SkiaShader::apply(*mCaches, fill.skiaShaderData, mViewportWidth, mViewportHeight);
 
     GL_CHECKPOINT(MODERATE);
     Texture* texture = (fill.skiaShaderData.skiaShaderType & kBitmap_SkiaShaderType) ?
diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h
index 9e0fb12..3d119dc 100644
--- a/libs/hwui/renderstate/RenderState.h
+++ b/libs/hwui/renderstate/RenderState.h
@@ -16,7 +16,6 @@
 #ifndef RENDERSTATE_H
 #define RENDERSTATE_H
 
-#include "AssetAtlas.h"
 #include "Caches.h"
 #include "Glop.h"
 #include "renderstate/Blend.h"
@@ -92,7 +91,6 @@
 
     void render(const Glop& glop, const Matrix4& orthoMatrix);
 
-    AssetAtlas& assetAtlas() { return mAssetAtlas; }
     Blend& blend() { return *mBlend; }
     MeshState& meshState() { return *mMeshState; }
     Scissor& scissor() { return *mScissor; }
@@ -120,7 +118,6 @@
 
     OffscreenBufferPool mLayerPool;
 
-    AssetAtlas mAssetAtlas;
     std::set<Layer*> mActiveLayers;
     std::set<renderthread::CanvasContext*> mRegisteredContexts;
 
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 0f2d55b..fe0f56a 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -545,11 +545,6 @@
     return mRenderPipeline->createTextureLayer();
 }
 
-void CanvasContext::setTextureAtlas(RenderThread& thread,
-        const sp<GraphicBuffer>& buffer, int64_t* map, size_t mapSize) {
-    thread.eglManager().setTextureAtlas(buffer, map, mapSize);
-}
-
 void CanvasContext::dumpFrames(int fd) {
     FILE* file = fdopen(fd, "a");
     fprintf(file, "\n\n---PROFILEDATA---\n");
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 652cddd..41b658e 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -119,9 +119,6 @@
 
     DeferredLayerUpdater* createTextureLayer();
 
-    ANDROID_API static void setTextureAtlas(RenderThread& thread,
-            const sp<GraphicBuffer>& buffer, int64_t* map, size_t mapSize);
-
     void stopDrawing();
     void notifyFramePending();
 
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 86731c9..beda045 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -91,9 +91,7 @@
         , mEglConfig(nullptr)
         , mEglContext(EGL_NO_CONTEXT)
         , mPBufferSurface(EGL_NO_SURFACE)
-        , mCurrentSurface(EGL_NO_SURFACE)
-        , mAtlasMap(nullptr)
-        , mAtlasMapSize(0) {
+        , mCurrentSurface(EGL_NO_SURFACE) {
 }
 
 void EglManager::initialize() {
@@ -128,7 +126,6 @@
     makeCurrent(mPBufferSurface);
     DeviceInfo::initialize();
     mRenderThread.renderState().onGLContextCreated();
-    initAtlas();
 }
 
 void EglManager::initExtensions() {
@@ -191,32 +188,6 @@
         "Failed to create context, error = %s", egl_error_str());
 }
 
-void EglManager::setTextureAtlas(const sp<GraphicBuffer>& buffer,
-        int64_t* map, size_t mapSize) {
-
-    // Already initialized
-    if (mAtlasBuffer.get()) {
-        ALOGW("Multiple calls to setTextureAtlas!");
-        delete map;
-        return;
-    }
-
-    mAtlasBuffer = buffer;
-    mAtlasMap = map;
-    mAtlasMapSize = mapSize;
-
-    if (hasEglContext()) {
-        initAtlas();
-    }
-}
-
-void EglManager::initAtlas() {
-    if (mAtlasBuffer.get()) {
-        mRenderThread.renderState().assetAtlas().init(mAtlasBuffer,
-                mAtlasMap, mAtlasMapSize);
-    }
-}
-
 void EglManager::createPBufferSurface() {
     LOG_ALWAYS_FATAL_IF(mEglDisplay == EGL_NO_DISPLAY,
             "usePBufferSurface() called on uninitialized GlobalContext!");
@@ -229,7 +200,16 @@
 
 EGLSurface EglManager::createSurface(EGLNativeWindowType window) {
     initialize();
-    EGLSurface surface = eglCreateWindowSurface(mEglDisplay, mEglConfig, window, nullptr);
+
+    EGLint attribs[] = {
+#ifdef ANDROID_ENABLE_LINEAR_BLENDING
+            EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR,
+            EGL_COLORSPACE, EGL_COLORSPACE_sRGB,
+#endif
+            EGL_NONE
+    };
+
+    EGLSurface surface = eglCreateWindowSurface(mEglDisplay, mEglConfig, window, attribs);
     LOG_ALWAYS_FATAL_IF(surface == EGL_NO_SURFACE,
             "Failed to create EGLSurface for window %p, eglErr = %s",
             (void*) window, egl_error_str());
diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h
index 41047fe..ba4a3e1 100644
--- a/libs/hwui/renderthread/EglManager.h
+++ b/libs/hwui/renderthread/EglManager.h
@@ -79,8 +79,6 @@
     // Returns true iff the surface is now preserving buffers.
     bool setPreserveBuffer(EGLSurface surface, bool preserve);
 
-    void setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t mapSize);
-
     void fence();
 
 private:
@@ -94,7 +92,6 @@
     void createPBufferSurface();
     void loadConfig();
     void createContext();
-    void initAtlas();
     EGLint queryBufferAge(EGLSurface surface);
 
     RenderThread& mRenderThread;
@@ -106,10 +103,6 @@
 
     EGLSurface mCurrentSurface;
 
-    sp<GraphicBuffer> mAtlasBuffer;
-    int64_t* mAtlasMap;
-    size_t mAtlasMapSize;
-
     enum class SwapBehavior {
         Discard,
         Preserved,
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index dcbc980..c2ed864 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -480,23 +480,6 @@
     staticPostAndWait(task);
 }
 
-CREATE_BRIDGE4(setTextureAtlas, RenderThread* thread, GraphicBuffer* buffer, int64_t* map,
-               size_t size) {
-    CanvasContext::setTextureAtlas(*args->thread, args->buffer, args->map, args->size);
-    args->buffer->decStrong(nullptr);
-    return nullptr;
-}
-
-void RenderProxy::setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t size) {
-    SETUP_TASK(setTextureAtlas);
-    args->thread = &mRenderThread;
-    args->buffer = buffer.get();
-    args->buffer->incStrong(nullptr);
-    args->map = map;
-    args->size = size;
-    post(task);
-}
-
 CREATE_BRIDGE2(setProcessStatsBuffer, RenderThread* thread, int fd) {
     args->thread->jankTracker().switchStorageToAshmem(args->fd);
     close(args->fd);
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index d4aaea6..50a6f64 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -112,7 +112,6 @@
     uint32_t frameTimePercentile(int p);
     ANDROID_API static void dumpGraphicsMemory(int fd);
 
-    ANDROID_API void setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t size);
     ANDROID_API void setProcessStatsBuffer(int fd);
     ANDROID_API int getRenderThreadTid();
 
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 78e9bc4..51c0a05 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -124,8 +124,9 @@
     static SkBitmap createSkBitmap(int width, int height,
             SkColorType colorType = kN32_SkColorType) {
         SkBitmap bitmap;
+        sk_sp<SkColorSpace> colorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named);
         SkImageInfo info = SkImageInfo::Make(width, height,
-                colorType, kPremul_SkAlphaType);
+                colorType, kPremul_SkAlphaType, colorSpace);
         bitmap.setInfo(info);
         bitmap.allocPixels(info);
         return bitmap;
diff --git a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
index a30ada0..5cab04d 100644
--- a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
+++ b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
@@ -18,6 +18,7 @@
 
 #include <gtest/gtest.h>
 #include <SkColorMatrixFilter.h>
+#include <SkColorSpace.h>
 #include <SkImagePriv.h>
 #include <SkShader.h>
 
@@ -82,3 +83,9 @@
     paint.setXfermodeMode(SkXfermode::kOverlay_Mode);
     ASSERT_EQ(expected, paint.getXfermode());
 }
+
+TEST(SkiaBehavior, srgbColorSpaceIsSingleton) {
+    sk_sp<SkColorSpace> sRGB1 = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named);
+    sk_sp<SkColorSpace> sRGB2 = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named);
+    ASSERT_EQ(sRGB1.get(), sRGB2.get());
+}
diff --git a/libs/hwui/utils/Color.h b/libs/hwui/utils/Color.h
index b5157f4..c8f8c70 100644
--- a/libs/hwui/utils/Color.h
+++ b/libs/hwui/utils/Color.h
@@ -16,6 +16,8 @@
 #ifndef COLOR_H
 #define COLOR_H
 
+#include <math.h>
+
 #include <SkColor.h>
 
 namespace android {
@@ -80,6 +82,28 @@
     };
     static constexpr int BrightColorsCount = sizeof(BrightColors) / sizeof(Color::Color);
 
+    // Opto-electronic conversion function for the sRGB color space
+    // Takes a gamma-encoded sRGB value and converts it to a linear sRGB value
+    static constexpr float OECF_sRGB(float linear) {
+#ifdef ANDROID_ENABLE_LINEAR_BLENDING
+        // IEC 61966-2-1:1999
+        return linear <= 0.0031308f ?
+                linear * 12.92f : (powf(linear, 1.0f / 2.4f) * 1.055f) - 0.055f;
+#else
+        return linear;
+#endif
+    }
+
+    // Electro-optical conversion function for the sRGB color space
+    // Takes a linear sRGB value and converts it to a gamma-encoded sRGB value
+    static constexpr float EOCF_sRGB(float srgb) {
+#ifdef ANDROID_ENABLE_LINEAR_BLENDING
+        // IEC 61966-2-1:1999
+        return srgb <= 0.04045f ? srgb / 12.92f : powf((srgb + 0.055f) / 1.055f, 2.4f);
+#else
+        return srgb;
+#endif
+    }
 } /* namespace uirenderer */
 } /* namespace android */
 
diff --git a/libs/hwui/utils/TestWindowContext.cpp b/libs/hwui/utils/TestWindowContext.cpp
index b879f78..624d207 100644
--- a/libs/hwui/utils/TestWindowContext.cpp
+++ b/libs/hwui/utils/TestWindowContext.cpp
@@ -110,9 +110,10 @@
     }
 
     bool capturePixels(SkBitmap* bmp) {
+        sk_sp<SkColorSpace> colorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named);
         SkImageInfo destinationConfig =
             SkImageInfo::Make(mSize.width(), mSize.height(),
-                              kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+                              kRGBA_8888_SkColorType, kPremul_SkAlphaType, colorSpace);
         bmp->allocPixels(destinationConfig);
         android_memset32((uint32_t*) bmp->getPixels(), SK_ColorRED,
                          mSize.width() * mSize.height() * 4);