Add shader-based text gamma correction

To enable it, the system property ro.hwui.text_gamma_shader must be
set to true. For testing, DEBUG_FONT_RENDERER_FORCE_SHADER_GAMMA
can be set to 1 in libhwui/Debug.h.

Change-Id: If345c6b71b67ecf1ef2e8847b71f30f3ef251a27
diff --git a/libs/hwui/Debug.h b/libs/hwui/Debug.h
index 55a860e..13780c3 100644
--- a/libs/hwui/Debug.h
+++ b/libs/hwui/Debug.h
@@ -65,6 +65,9 @@
 // Turn on to enable additional debugging in the font renderers
 #define DEBUG_FONT_RENDERER 0
 
+// Force gamma correction in shaders
+#define DEBUG_FONT_RENDERER_FORCE_SHADER_GAMMA 0
+
 // Turn on to dump display list state
 #define DEBUG_DISPLAY_LIST 0
 
diff --git a/libs/hwui/GammaFontRenderer.cpp b/libs/hwui/GammaFontRenderer.cpp
index 75d5b10..226f4bc 100644
--- a/libs/hwui/GammaFontRenderer.cpp
+++ b/libs/hwui/GammaFontRenderer.cpp
@@ -24,6 +24,18 @@
 namespace uirenderer {
 
 ///////////////////////////////////////////////////////////////////////////////
+// Utils
+///////////////////////////////////////////////////////////////////////////////
+
+static int luminance(const SkPaint* paint) {
+    uint32_t c = paint->getColor();
+    const int r = (c >> 16) & 0xFF;
+    const int g = (c >>  8) & 0xFF;
+    const int b = (c      ) & 0xFF;
+    return (r * 2 + g * 5 + b) >> 3;
+}
+
+///////////////////////////////////////////////////////////////////////////////
 // Base class GammaFontRenderer
 ///////////////////////////////////////////////////////////////////////////////
 
@@ -36,7 +48,11 @@
         }
     }
 
+#if DEBUG_FONT_RENDERER_FORCE_SHADER_GAMMA
+    return new ShaderGammaFontRenderer();
+#else
     return new LookupGammaFontRenderer();
+#endif
 }
 
 GammaFontRenderer::GammaFontRenderer() {
@@ -82,6 +98,29 @@
 
 ShaderGammaFontRenderer::ShaderGammaFontRenderer(): GammaFontRenderer() {
     INIT_LOGD("Creating shader gamma font renderer");
+    mRenderer = NULL;
+}
+
+void ShaderGammaFontRenderer::describe(ProgramDescription& description,
+        const SkPaint* paint) const {
+    if (paint->getShader() == NULL) {
+        const int l = luminance(paint);
+
+        if (l <= mBlackThreshold) {
+            description.hasGammaCorrection = true;
+            description.gamma = mGamma;
+        } else if (l >= mWhiteThreshold) {
+            description.hasGammaCorrection = true;
+            description.gamma = 1.0f / mGamma;
+        }
+    }
+}
+
+void ShaderGammaFontRenderer::setupProgram(ProgramDescription& description,
+        Program* program) const {
+    if (description.hasGammaCorrection) {
+        glUniform1f(program->getUniform("gamma"), description.gamma);
+    }
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -164,15 +203,11 @@
 
 FontRenderer& LookupGammaFontRenderer::getFontRenderer(const SkPaint* paint) {
     if (paint->getShader() == NULL) {
-        uint32_t c = paint->getColor();
-        const int r = (c >> 16) & 0xFF;
-        const int g = (c >>  8) & 0xFF;
-        const int b = (c      ) & 0xFF;
-        const int luminance = (r * 2 + g * 5 + b) >> 3;
+        const int l = luminance(paint);
 
-        if (luminance <= mBlackThreshold) {
+        if (l <= mBlackThreshold) {
             return *getRenderer(kGammaBlack);
-        } else if (luminance >= mWhiteThreshold) {
+        } else if (l >= mWhiteThreshold) {
             return *getRenderer(kGammaWhite);
         }
     }
diff --git a/libs/hwui/GammaFontRenderer.h b/libs/hwui/GammaFontRenderer.h
index 988947a..8e1db78 100644
--- a/libs/hwui/GammaFontRenderer.h
+++ b/libs/hwui/GammaFontRenderer.h
@@ -20,6 +20,7 @@
 #include <SkPaint.h>
 
 #include "FontRenderer.h"
+#include "Program.h"
 
 namespace android {
 namespace uirenderer {
@@ -34,9 +35,11 @@
     virtual FontRenderer& getFontRenderer(const SkPaint* paint) = 0;
 
     virtual uint32_t getFontRendererCount() const = 0;
-
     virtual uint32_t getFontRendererSize(uint32_t fontRenderer) const = 0;
 
+    virtual void describe(ProgramDescription& description, const SkPaint* paint) const = 0;
+    virtual void setupProgram(ProgramDescription& description, Program* program) const = 0;
+
     static GammaFontRenderer* createRenderer();
 
 protected:
@@ -79,6 +82,9 @@
         return mRenderer->getCacheSize();
     }
 
+    void describe(ProgramDescription& description, const SkPaint* paint) const;
+    void setupProgram(ProgramDescription& description, Program* program) const;
+
 private:
     ShaderGammaFontRenderer();
 
@@ -109,6 +115,12 @@
         return renderer->getCacheSize();
     }
 
+    void describe(ProgramDescription& description, const SkPaint* paint) const {
+    }
+
+    void setupProgram(ProgramDescription& description, Program* program) const {
+    }
+
 private:
     LookupGammaFontRenderer();
 
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 99016d6..64b6c17 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -1174,6 +1174,10 @@
     mSetShaderColor = mDescription.setAlpha8Color(mColorR, mColorG, mColorB, mColorA);
 }
 
+void OpenGLRenderer::setupDrawTextGamma(const SkPaint* paint) {
+    mCaches.fontRenderer->describe(mDescription, paint);
+}
+
 void OpenGLRenderer::setupDrawColor(float r, float g, float b, float a) {
     mColorA = a;
     mColorR = r;
@@ -1301,6 +1305,10 @@
     }
 }
 
+void OpenGLRenderer::setupDrawTextGammaUniforms() {
+    mCaches.fontRenderer->setupProgram(mDescription, mCaches.currentProgram);
+}
+
 void OpenGLRenderer::setupDrawSimpleMesh() {
     bool force = mCaches.bindMeshBuffer();
     mCaches.bindPositionVertexPointer(force, mCaches.currentProgram->position, 0);
@@ -2302,6 +2310,7 @@
 
     mCaches.activeTexture(0);
     setupDraw();
+    setupDrawTextGamma(paint);
     setupDrawDirtyRegionsDisabled();
     setupDrawWithTexture(true);
     setupDrawAlpha8Color(paint->getColor(), alpha);
@@ -2314,6 +2323,7 @@
     setupDrawPureColorUniforms();
     setupDrawColorFilterUniforms();
     setupDrawShaderUniforms(pureTranslate);
+    setupDrawTextGammaUniforms();
 
     const Rect* clip = pureTranslate ? mSnapshot->clipRect : &mSnapshot->getLocalClip();
     Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
@@ -2387,6 +2397,8 @@
     if (CC_UNLIKELY(mHasShadow)) {
         mCaches.activeTexture(0);
 
+        // NOTE: The drop shadow will not perform gamma correction
+        //       if shader-based correction is enabled
         mCaches.dropShadowCache.setFontRenderer(fontRenderer);
         const ShadowTexture* shadow = mCaches.dropShadowCache.get(
                 paint, text, bytesCount, count, mShadowRadius);
@@ -2427,6 +2439,7 @@
     // The font renderer will always use texture unit 0
     mCaches.activeTexture(0);
     setupDraw();
+    setupDrawTextGamma(paint);
     setupDrawDirtyRegionsDisabled();
     setupDrawWithTexture(true);
     setupDrawAlpha8Color(paint->getColor(), alpha);
@@ -2441,6 +2454,7 @@
     setupDrawPureColorUniforms();
     setupDrawColorFilterUniforms();
     setupDrawShaderUniforms(pureTranslate);
+    setupDrawTextGammaUniforms();
 
     const Rect* clip = pureTranslate ? mSnapshot->clipRect : &mSnapshot->getLocalClip();
     Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
@@ -2485,6 +2499,7 @@
 
     mCaches.activeTexture(0);
     setupDraw();
+    setupDrawTextGamma(paint);
     setupDrawDirtyRegionsDisabled();
     setupDrawWithTexture(true);
     setupDrawAlpha8Color(paint->getColor(), alpha);
@@ -2497,6 +2512,7 @@
     setupDrawPureColorUniforms();
     setupDrawColorFilterUniforms();
     setupDrawShaderUniforms(false);
+    setupDrawTextGammaUniforms();
 
     const Rect* clip = &mSnapshot->getLocalClip();
     Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 8bdc450..7795d08 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -572,6 +572,7 @@
     void setupDrawColor(int color, int alpha);
     void setupDrawColor(float r, float g, float b, float a);
     void setupDrawAlpha8Color(int color, int alpha);
+    void setupDrawTextGamma(const SkPaint* paint);
     void setupDrawShader();
     void setupDrawColorFilter();
     void setupDrawBlending(SkXfermode::Mode mode = SkXfermode::kSrcOver_Mode,
@@ -596,6 +597,7 @@
     void setupDrawExternalTexture(GLuint texture);
     void setupDrawTextureTransform();
     void setupDrawTextureTransformUniforms(mat4& transform);
+    void setupDrawTextGammaUniforms();
     void setupDrawMesh(GLvoid* vertices, GLvoid* texCoords = NULL, GLuint vbo = 0);
     void setupDrawMeshIndices(GLvoid* vertices, GLvoid* texCoords);
     void setupDrawVertices(GLvoid* vertices);
diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h
index e9c666b..491767f 100644
--- a/libs/hwui/Program.h
+++ b/libs/hwui/Program.h
@@ -77,6 +77,8 @@
 #define PROGRAM_HAS_EXTERNAL_TEXTURE_SHIFT 38
 #define PROGRAM_HAS_TEXTURE_TRANSFORM_SHIFT 39
 
+#define PROGRAM_HAS_GAMMA_CORRECTION 40
+
 ///////////////////////////////////////////////////////////////////////////////
 // Types
 ///////////////////////////////////////////////////////////////////////////////
@@ -146,6 +148,9 @@
     bool isPoint;
     float pointSize;
 
+    bool hasGammaCorrection;
+    float gamma;
+
     /**
      * Resets this description. All fields are reset back to the default
      * values they hold after building a new instance.
@@ -180,6 +185,9 @@
 
         isPoint = false;
         pointSize = 0.0f;
+
+        hasGammaCorrection = false;
+        gamma = 2.2f;
     }
 
     /**
@@ -246,6 +254,7 @@
         if (isAA) key |= programid(0x1) << PROGRAM_HAS_AA_SHIFT;
         if (hasExternalTexture) key |= programid(0x1) << PROGRAM_HAS_EXTERNAL_TEXTURE_SHIFT;
         if (hasTextureTransform) key |= programid(0x1) << PROGRAM_HAS_TEXTURE_TRANSFORM_SHIFT;
+        if (hasGammaCorrection) key |= programid(0x1) << PROGRAM_HAS_GAMMA_CORRECTION;
         return key;
     }
 
@@ -261,7 +270,7 @@
     }
 
 private:
-    inline uint32_t getEnumForWrap(GLenum wrap) const {
+    static inline uint32_t getEnumForWrap(GLenum wrap) {
         switch (wrap) {
             case GL_CLAMP_TO_EDGE:
                 return 0;
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index a7f1277..fc60279 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -159,6 +159,9 @@
         // PorterDuff
         "uniform vec4 colorBlend;\n"
 };
+const char* gFS_Uniforms_Gamma =
+        "uniform float gamma;\n";
+
 const char* gFS_Main =
         "\nvoid main(void) {\n"
         "    lowp vec4 fragColor;\n";
@@ -184,10 +187,18 @@
         "\nvoid main(void) {\n"
         "    gl_FragColor = texture2D(sampler, 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(sampler, outTexCoords).a, gamma));\n"
+        "}\n\n";
 const char* gFS_Fast_SingleModulateA8Texture =
         "\nvoid main(void) {\n"
         "    gl_FragColor = color * texture2D(sampler, outTexCoords).a;\n"
         "}\n\n";
+const char* gFS_Fast_SingleModulateA8Texture_ApplyGamma =
+        "\nvoid main(void) {\n"
+        "    gl_FragColor = color * pow(texture2D(sampler, outTexCoords).a, gamma);\n"
+        "}\n\n";
 const char* gFS_Fast_SingleGradient =
         "\nvoid main(void) {\n"
         "    gl_FragColor = texture2D(gradientSampler, linear);\n"
@@ -202,6 +213,8 @@
         "    fragColor = color;\n";
 const char* gFS_Main_ModulateColor =
         "    fragColor *= color.a;\n";
+const char* gFS_Main_ModulateColor_ApplyGamma =
+        "    fragColor *= pow(color.a, gamma);\n";
 const char* gFS_Main_AccountForAA =
         "    if (widthProportion < boundaryWidth) {\n"
         "        fragColor *= (widthProportion * inverseBoundaryWidth);\n"
@@ -517,6 +530,9 @@
     if (description.hasBitmap && description.isPoint) {
         shader.append(gFS_Header_Uniforms_PointHasBitmap);
     }
+    if (description.hasGammaCorrection) {
+        shader.append(gFS_Uniforms_Gamma);
+    }
 
     // Optimization for common cases
     if (!description.isAA && !blendFramebuffer &&
@@ -544,9 +560,17 @@
             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) {
@@ -643,7 +667,11 @@
             }
         }
         if (description.modulate && applyModulate) {
-            shader.append(gFS_Main_ModulateColor);
+            if (description.hasGammaCorrection) {
+                shader.append(gFS_Main_ModulateColor_ApplyGamma);
+            } else {
+                shader.append(gFS_Main_ModulateColor);
+            }
         }
         // Apply the color op if needed
         shader.append(gFS_Main_ApplyColorOp[description.colorOp]);