Add dithering to gradients

Change-Id: Ic1208855bde3a254eca2fd7cef43e0f1318ce419
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index a54c188..1947c32 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -11,6 +11,7 @@
 		Caches.cpp \
 		DisplayListLogBuffer.cpp \
 		DisplayListRenderer.cpp \
+		Dither.cpp \
 		FboCache.cpp \
 		GradientCache.cpp \
 		LayerCache.cpp \
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index 258ced0..aa2bc3f 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -252,6 +252,7 @@
             dropShadowCache.clear();
             gradientCache.clear();
             fontRenderer->clear();
+            dither.clear();
             // fall through
         case kFlushMode_Moderate:
             fontRenderer->flush();
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index 4cbac41..ad1098c 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -38,6 +38,7 @@
 #include "TextDropShadowCache.h"
 #include "FboCache.h"
 #include "ResourceCache.h"
+#include "Dither.h"
 
 namespace android {
 namespace uirenderer {
@@ -250,6 +251,7 @@
     TextDropShadowCache dropShadowCache;
     FboCache fboCache;
     ResourceCache resourceCache;
+    Dither dither;
 
     GammaFontRenderer* fontRenderer;
 
diff --git a/libs/hwui/Dither.cpp b/libs/hwui/Dither.cpp
new file mode 100755
index 0000000..5817977
--- /dev/null
+++ b/libs/hwui/Dither.cpp
@@ -0,0 +1,84 @@
+/*
+ * 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 {
+
+///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+// Must be a power of two
+#define DITHER_KERNEL_SIZE 4
+
+///////////////////////////////////////////////////////////////////////////////
+// Lifecycle
+///////////////////////////////////////////////////////////////////////////////
+
+void Dither::bindDitherTexture() {
+    if (!mInitialized) {
+        const uint8_t pattern[] = {
+             0,  8,  2, 10,
+            12,  4, 14,  6,
+             3, 11,  1,  9,
+            15,  7, 13,  5
+        };
+
+        glGenTextures(1, &mDitherTexture);
+        glBindTexture(GL_TEXTURE_2D, mDitherTexture);
+
+        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+        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);
+
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, DITHER_KERNEL_SIZE, DITHER_KERNEL_SIZE, 0,
+                GL_ALPHA, GL_UNSIGNED_BYTE, &pattern);
+
+        mInitialized = true;
+    } else {
+        glBindTexture(GL_TEXTURE_2D, mDitherTexture);
+    }
+}
+
+void Dither::clear() {
+    if (mInitialized) {
+        glDeleteTextures(1, &mDitherTexture);
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Program management
+///////////////////////////////////////////////////////////////////////////////
+
+void Dither::setupProgram(Program* program, GLuint* textureUnit) {
+    GLuint textureSlot = (*textureUnit)++;
+    Caches::getInstance().activeTexture(textureSlot);
+
+    bindDitherTexture();
+
+    glUniform1i(program->getUniform("ditherSampler"), textureSlot);
+    glUniform1f(program->getUniform("ditherSize"), 1.0f / DITHER_KERNEL_SIZE);
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/Dither.h b/libs/hwui/Dither.h
new file mode 100755
index 0000000..34cf9bf
--- /dev/null
+++ b/libs/hwui/Dither.h
@@ -0,0 +1,47 @@
+/*
+ * 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 <GLES2/gl2.h>
+
+#include "Program.h"
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * Handles dithering for programs.
+ */
+class Dither {
+public:
+    Dither(): mInitialized(false), mDitherTexture(0) { }
+
+    void clear();
+    void setupProgram(Program* program, GLuint* textureUnit);
+
+private:
+    void bindDitherTexture();
+
+    bool mInitialized;
+    GLuint mDitherTexture;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_DITHER_H
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index d601f01..0e77cb2 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -75,9 +75,11 @@
         // Linear
         "varying highp vec2 linear;\n",
         "varying highp float linear;\n",
+
         // Circular
         "varying highp vec2 circular;\n",
         "varying highp vec2 circular;\n",
+
         // Sweep
         "varying highp vec2 sweep;\n",
         "varying highp vec2 sweep;\n",
@@ -92,9 +94,11 @@
         // Linear
         "    linear = vec2((screenSpace * position).x, 0.5);\n",
         "    linear = (screenSpace * position).x;\n",
+
         // Circular
         "    circular = (screenSpace * position).xy;\n",
         "    circular = (screenSpace * position).xy;\n",
+
         // Sweep
         "    sweep = (screenSpace * position).xy;\n",
         "    sweep = (screenSpace * position).xy;\n",
@@ -137,19 +141,24 @@
         "uniform sampler2D sampler;\n";
 const char* gFS_Uniforms_ExternalTextureSampler =
         "uniform samplerExternalOES sampler;\n";
+#define FS_UNIFORMS_DITHER \
+        "uniform float ditherSize;\n" \
+        "uniform sampler2D ditherSampler;\n"
+#define FS_UNIFORMS_GRADIENT \
+        "uniform vec4 startColor;\n" \
+        "uniform vec4 endColor;\n"
 const char* gFS_Uniforms_GradientSampler[6] = {
         // Linear
-        "uniform sampler2D gradientSampler;\n",
-        "uniform vec4 startColor;\n"
-        "uniform vec4 endColor;\n",
+        FS_UNIFORMS_DITHER "uniform sampler2D gradientSampler;\n",
+        FS_UNIFORMS_DITHER FS_UNIFORMS_GRADIENT,
+
         // Circular
-        "uniform sampler2D gradientSampler;\n",
-        "uniform vec4 startColor;\n"
-        "uniform vec4 endColor;\n",
+        FS_UNIFORMS_DITHER "uniform sampler2D gradientSampler;\n",
+        FS_UNIFORMS_DITHER FS_UNIFORMS_GRADIENT,
+
         // Sweep
-        "uniform sampler2D gradientSampler;\n",
-        "uniform vec4 startColor;\n"
-        "uniform vec4 endColor;\n",
+        FS_UNIFORMS_DITHER "uniform sampler2D gradientSampler;\n",
+        FS_UNIFORMS_DITHER FS_UNIFORMS_GRADIENT
 };
 const char* gFS_Uniforms_BitmapSampler =
         "uniform sampler2D bitmapSampler;\n";
@@ -176,6 +185,11 @@
         "    highp vec2 outBitmapTexCoords = outPointBitmapTexCoords + "
         "((gl_PointCoord - vec2(0.5, 0.5)) * textureDimension * vec2(pointSize, pointSize));\n";
 
+#define FS_MAIN_DITHER \
+        "texture2D(ditherSampler, gl_FragCoord.xy * ditherSize).a * ditherSize * ditherSize"
+const char* gFS_Main_AddDitherToGradient =
+        "    gradientColor += " FS_MAIN_DITHER ";\n";
+
 // Fast cases
 const char* gFS_Fast_SingleColor =
         "\nvoid main(void) {\n"
@@ -207,18 +221,18 @@
         "}\n\n";
 const char* gFS_Fast_SingleGradient[2] = {
         "\nvoid main(void) {\n"
-        "    gl_FragColor = texture2D(gradientSampler, linear);\n"
+        "    gl_FragColor = " FS_MAIN_DITHER " + texture2D(gradientSampler, linear);\n"
         "}\n\n",
         "\nvoid main(void) {\n"
-        "    gl_FragColor = mix(startColor, endColor, clamp(linear, 0.0, 1.0));\n"
+        "    gl_FragColor = " FS_MAIN_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 = color.a * texture2D(gradientSampler, linear);\n"
+        "    gl_FragColor " FS_MAIN_DITHER " + color.a * texture2D(gradientSampler, linear);\n"
         "}\n\n",
         "\nvoid main(void) {\n"
-        "    gl_FragColor = color.a * mix(startColor, endColor, clamp(linear, 0.0, 1.0));\n"
+        "    gl_FragColor " FS_MAIN_DITHER " + color.a * mix(startColor, endColor, clamp(linear, 0.0, 1.0));\n"
         "}\n\n"
 };
 
@@ -254,16 +268,21 @@
 };
 const char* gFS_Main_FetchGradient[6] = {
         // Linear
-        "    vec4 gradientColor = texture2D(gradientSampler, linear);\n",
-        "    vec4 gradientColor = mix(startColor, endColor, clamp(linear, 0.0, 1.0));\n",
+        "    highp vec4 gradientColor = texture2D(gradientSampler, linear);\n",
+
+        "    highp vec4 gradientColor = mix(startColor, endColor, clamp(linear, 0.0, 1.0));\n",
+
         // Circular
-        "    vec4 gradientColor = texture2D(gradientSampler, vec2(length(circular), 0.5));\n",
-        "    vec4 gradientColor = mix(startColor, endColor, clamp(length(circular), 0.0, 1.0));\n",
+        "    highp vec4 gradientColor = texture2D(gradientSampler, vec2(length(circular), 0.5));\n",
+
+        "    highp vec4 gradientColor = mix(startColor, endColor, clamp(length(circular), 0.0, 1.0));\n",
+
         // Sweep
         "    highp float index = atan(sweep.y, sweep.x) * 0.15915494309; // inv(2 * PI)\n"
-        "    vec4 gradientColor = texture2D(gradientSampler, vec2(index - floor(index), 0.5));\n",
+        "    highp vec4 gradientColor = texture2D(gradientSampler, vec2(index - floor(index), 0.5));\n",
+
         "    highp float index = atan(sweep.y, sweep.x) * 0.15915494309; // inv(2 * PI)\n"
-        "    vec4 gradientColor = mix(startColor, endColor, clamp(index - floor(index), 0.0, 1.0));\n"
+        "    highp vec4 gradientColor = mix(startColor, endColor, clamp(index - floor(index), 0.0, 1.0));\n"
 };
 const char* gFS_Main_FetchBitmap =
         "    vec4 bitmapColor = texture2D(bitmapSampler, outBitmapTexCoords);\n";
@@ -651,6 +670,7 @@
         }
         if (description.hasGradient) {
             shader.append(gFS_Main_FetchGradient[gradientIndex(description)]);
+            shader.append(gFS_Main_AddDitherToGradient);
         }
         if (description.hasBitmap) {
             if (description.isPoint) {
diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp
index 71e1739..8916efd 100644
--- a/libs/hwui/SkiaShader.cpp
+++ b/libs/hwui/SkiaShader.cpp
@@ -250,6 +250,8 @@
         bindUniformColor(program->getUniform("endColor"), mColors[1]);
     }
 
+    Caches::getInstance().dither.setupProgram(program, textureUnit);
+
     mat4 screenSpace;
     computeScreenSpaceMatrix(screenSpace, modelView);
     glUniformMatrix4fv(program->getUniform("screenSpace"), 1, GL_FALSE, &screenSpace.data[0]);
@@ -375,6 +377,8 @@
        bindUniformColor(program->getUniform("endColor"), mColors[1]);
     }
 
+    Caches::getInstance().dither.setupProgram(program, textureUnit);
+
     mat4 screenSpace;
     computeScreenSpaceMatrix(screenSpace, modelView);
     glUniformMatrix4fv(program->getUniform("screenSpace"), 1, GL_FALSE, &screenSpace.data[0]);
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/GradientStopsActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/GradientStopsActivity.java
index ed00ecd..a73eab5 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/GradientStopsActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/GradientStopsActivity.java
@@ -19,6 +19,7 @@
 import android.app.Activity;
 import android.content.Context;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.LinearGradient;
 import android.graphics.Paint;
 import android.graphics.Shader;
@@ -111,6 +112,14 @@
 
             canvas.translate(0.0f, 75.0f);
             canvas.drawRect(0.0f, 0.0f, 768.0f, 50.0f, paint);
+
+            gradient = new LinearGradient(0.0f, 0.0f, 512.0f, 0.0f,
+                    colors, null, Shader.TileMode.CLAMP);
+
+            paint.setShader(gradient);
+
+            canvas.translate(0.0f, 75.0f);
+            canvas.drawRect(0.0f, 0.0f, 512.0f, 50.0f, paint);
         }
     }
 }