Simple GPU based dithering:
authorkrajcevski <krajcevski@google.com>
Thu, 19 Jun 2014 21:14:06 +0000 (14:14 -0700)
committerCommit bot <commit-bot@chromium.org>
Thu, 19 Jun 2014 21:14:06 +0000 (14:14 -0700)
If dithering is turned on, apply an effect that filters the pixel through
the following pipeline:

for each channel c:
  1. Compute quantized colors [low, high] that c is between
  2. Pick high by flipping a coin weighted by (c - low)

R=bsalomon@google.com, egdaniel@google.com, robertphillips@google.com

Author: krajcevski@google.com

Review URL: https://codereview.chromium.org/321253002

bench/GradientBench.cpp
expectations/gm/ignored-tests.txt
gyp/gpu.gypi
gyp/skia_for_chromium_defines.gypi
src/gpu/SkGr.cpp
src/gpu/effects/GrDitherEffect.cpp [new file with mode: 0644]
src/gpu/effects/GrDitherEffect.h [new file with mode: 0644]

index 90d45c8..f3e783e 100644 (file)
@@ -34,12 +34,15 @@ static const SkColor gColors[] = {
     SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, SK_ColorWHITE, SK_ColorBLACK, // 10 lines, 50 colors
 };
 
+static const SkColor gShallowColors[] = { 0xFF555555, 0xFF444444 };
+
 // We have several special-cases depending on the number (and spacing) of colors, so
 // try to exercise those here.
 static const GradData gGradData[] = {
     { 2, gColors, NULL, "" },
     { 50, gColors, NULL, "_hicolor" }, // many color gradient
     { 3, gColors, NULL, "_3color" },
+    { 2, gShallowColors, NULL, "_shallow" },
 };
 
 /// Ignores scale
@@ -200,12 +203,22 @@ static const char* geomtypename(GeomType gt) {
 class GradientBench : public Benchmark {
     SkString fName;
     SkShader* fShader;
+    bool fDither;
     enum {
         W   = 400,
         H   = 400,
         kRepeat = 15,
     };
 public:
+    SkShader* makeShader(GradType gradType, GradData data, SkShader::TileMode tm, float scale) {
+        const SkPoint pts[2] = {
+            { 0, 0 },
+            { SkIntToScalar(W), SkIntToScalar(H) }
+        };
+
+        return gGrads[gradType].fMaker(pts, data, tm, scale);
+    }
+
     GradientBench(GradType gradType,
                   GradData data = gGradData[0],
                   SkShader::TileMode tm = SkShader::kClamp_TileMode,
@@ -224,15 +237,25 @@ public:
 
         fName.append(data.fName);
 
-        const SkPoint pts[2] = {
-            { 0, 0 },
-            { SkIntToScalar(W), SkIntToScalar(H) }
-        };
-
-        fShader = gGrads[gradType].fMaker(pts, data, tm, scale);
+        fDither = false;
+        fShader = this->makeShader(gradType, data, tm, scale);
         fGeomType = geomType;
     }
 
+    GradientBench(GradType gradType, GradData data, bool dither) {
+        const char *tmname = tilemodename(SkShader::kClamp_TileMode);
+        fName.printf("gradient_%s_%s", gGrads[gradType].fName, tmname);
+        fName.append(data.fName);
+
+        fDither = dither;
+        if (dither) {
+            fName.appendf("_dither");
+        }
+
+        fShader = this->makeShader(gradType, data, SkShader::kClamp_TileMode, 1.0f);
+        fGeomType = kRect_GeomType;
+    }
+
     virtual ~GradientBench() {
         fShader->unref();
     }
@@ -247,6 +270,9 @@ protected:
         this->setupPaint(&paint);
 
         paint.setShader(fShader);
+        if (fDither) {
+            paint.setDither(true);
+        }
 
         SkRect r = { 0, 0, SkIntToScalar(W), SkIntToScalar(H) };
         for (int i = 0; i < loops * kRepeat; i++) {
@@ -304,6 +330,16 @@ DEF_BENCH( return new GradientBench(kConicalOutZero_GradType); )
 DEF_BENCH( return new GradientBench(kConicalOutZero_GradType, gGradData[1]); )
 DEF_BENCH( return new GradientBench(kConicalOutZero_GradType, gGradData[2]); )
 
+// Dithering
+DEF_BENCH( return new GradientBench(kLinear_GradType, gGradData[3], true); )
+DEF_BENCH( return new GradientBench(kLinear_GradType, gGradData[3], false); )
+DEF_BENCH( return new GradientBench(kRadial_GradType, gGradData[3], true); )
+DEF_BENCH( return new GradientBench(kRadial_GradType, gGradData[3], false); )
+DEF_BENCH( return new GradientBench(kSweep_GradType, gGradData[3], true); )
+DEF_BENCH( return new GradientBench(kSweep_GradType, gGradData[3], false); )
+DEF_BENCH( return new GradientBench(kConical_GradType, gGradData[3], true); )
+DEF_BENCH( return new GradientBench(kConical_GradType, gGradData[3], false); )
+
 ///////////////////////////////////////////////////////////////////////////////
 
 class Gradient2Bench : public Benchmark {
index bd5c6ec..2cc1b85 100644 (file)
@@ -50,3 +50,26 @@ bigblurs
 # This CL actually fixes this GM's image
 distantclip
 
+# krajcevski:
+# Added GPU-based dithering for all SkPaints that have that flag set
+# https://codereview.chromium.org/321253002/
+radial_gradient
+modecolorfilters
+scaled_tilemodes
+tilemodes
+scaled_tilemodes_npot
+lerpmode
+xfermodes2
+xfermodes
+drawbitmapmatrix
+complexclip_bw_layer
+complexclip_aa_layer
+hairmodes
+aarectmodes
+tilemodes_npot
+convex_poly_clip
+lumafilter
+shadertext
+bitmapfilters
+arithmode
+optimizations
index fed08f2..c03f8ef 100644 (file)
       '<(skia_src_path)/gpu/effects/GrDashingEffect.h',
       '<(skia_src_path)/gpu/effects/GrDistanceFieldTextureEffect.cpp',
       '<(skia_src_path)/gpu/effects/GrDistanceFieldTextureEffect.h',
+      '<(skia_src_path)/gpu/effects/GrDitherEffect.cpp',
+      '<(skia_src_path)/gpu/effects/GrDitherEffect.h',
       '<(skia_src_path)/gpu/effects/GrOvalEffect.cpp',
       '<(skia_src_path)/gpu/effects/GrOvalEffect.h',
       '<(skia_src_path)/gpu/effects/GrRRectEffect.cpp',
index 4f4b8d0..2212519 100644 (file)
@@ -19,6 +19,7 @@
       'SK_SUPPORT_LEGACY_N32_NAME',
       'SK_SUPPORT_LEGACY_SETCONFIG',
       'SK_IGNORE_ETC1_SUPPORT',
+      'SK_IGNORE_GPU_DITHER',
     ],
   },
 }
index 8e7cea6..a1b379f 100644 (file)
@@ -13,6 +13,7 @@
 #include "SkPixelRef.h"
 #include "GrResourceCache.h"
 #include "GrGpu.h"
+#include "effects/GrDitherEffect.h"
 #include "GrDrawTargetCaps.h"
 
 #ifndef SK_IGNORE_ETC1_SUPPORT
@@ -460,6 +461,29 @@ void SkPaint2GrPaintNoShader(GrContext* context, const SkPaint& skPaint, GrColor
             }
         }
     }
+
+#ifndef SK_IGNORE_GPU_DITHER
+    // If the dither flag is set, then we need to see if the underlying context
+    // supports it. If not, then install a dither effect.
+    if (skPaint.isDither() && grPaint->numColorStages() > 0) {
+        // What are we rendering into?
+        const GrRenderTarget *target = context->getRenderTarget();
+        SkASSERT(NULL != target);
+
+        // Suspect the dithering flag has no effect on these configs, otherwise
+        // fall back on setting the appropriate state.
+        if (target->config() == kRGBA_8888_GrPixelConfig ||
+            target->config() == kBGRA_8888_GrPixelConfig) {
+            // The dither flag is set and the target is likely
+            // not going to be dithered by the GPU.
+            SkAutoTUnref<GrEffectRef> effect(GrDitherEffect::Create());
+            if (NULL != effect.get()) {
+                grPaint->addColorEffect(effect);
+                grPaint->setDither(false);
+            }
+        }
+    }
+#endif
 }
 
 /**
@@ -494,19 +518,26 @@ void SkPaint2GrPaintShader(GrContext* context, const SkPaint& skPaint,
     // SkShader::asNewEffect() may do offscreen rendering. Save off the current RT, clip, and
     // matrix. We don't reset the matrix on the context because SkShader::asNewEffect may use
     // GrContext::getMatrix() to know the transformation from local coords to device space.
-    GrContext::AutoRenderTarget art(context, NULL);
-    GrContext::AutoClip ac(context, GrContext::AutoClip::kWideOpen_InitialClip);
-    AutoMatrix am(context);
-
-    // setup the shader as the first color effect on the paint
-    // the default grColor is the paint's color
     GrColor grColor = SkColor2GrColor(skPaint.getColor());
-    GrEffectRef* grEffect = NULL;
-    if (shader->asNewEffect(context, skPaint, NULL, &grColor, &grEffect) && NULL != grEffect) {
-        SkAutoTUnref<GrEffectRef> effect(grEffect);
-        grPaint->addColorEffect(effect);
-        constantColor = false;
+
+    // Start a new block here in order to preserve our context state after calling
+    // asNewEffect(). Since these calls get passed back to the client, we don't really
+    // want them messing around with the context.
+    {
+        GrContext::AutoRenderTarget art(context, NULL);
+        GrContext::AutoClip ac(context, GrContext::AutoClip::kWideOpen_InitialClip);
+        AutoMatrix am(context);
+
+        // setup the shader as the first color effect on the paint
+        // the default grColor is the paint's color
+        GrEffectRef* grEffect = NULL;
+        if (shader->asNewEffect(context, skPaint, NULL, &grColor, &grEffect) && NULL != grEffect) {
+            SkAutoTUnref<GrEffectRef> effect(grEffect);
+            grPaint->addColorEffect(effect);
+            constantColor = false;
+        }
     }
+
     // The grcolor is automatically set when calling asneweffect.
     // If the shader can be seen as an effect it returns true and adds its effect to the grpaint.
     SkPaint2GrPaintNoShader(context, skPaint, grColor, constantColor, grPaint);
diff --git a/src/gpu/effects/GrDitherEffect.cpp b/src/gpu/effects/GrDitherEffect.cpp
new file mode 100644 (file)
index 0000000..be22657
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrDitherEffect.h"
+
+#include "gl/GrGLEffect.h"
+#include "gl/GrGLSL.h"
+#include "GrTBackendEffectFactory.h"
+
+#include "SkRect.h"
+
+//////////////////////////////////////////////////////////////////////////////
+
+class GLDitherEffect;
+
+class DitherEffect : public GrEffect {
+public:
+    static GrEffectRef* Create() {
+        return CreateEffectRef(AutoEffectUnref(SkNEW(DitherEffect)));
+    }
+
+    virtual ~DitherEffect() {};
+    static const char* Name() { return "Dither"; }
+
+    typedef GLDitherEffect GLEffect;
+
+    virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags) const SK_OVERRIDE;
+
+    virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE {
+        return GrTBackendEffectFactory<DitherEffect>::getInstance();
+    }
+
+private:
+    DitherEffect() {
+        this->setWillReadFragmentPosition();
+    }
+
+    // All dither effects are equal
+    virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE { return true; }
+
+    GR_DECLARE_EFFECT_TEST;
+
+    typedef GrEffect INHERITED;
+};
+
+void DitherEffect::getConstantColorComponents(GrColor* color, uint32_t* validFlags) const {
+    *validFlags = 0;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_EFFECT_TEST(DitherEffect);
+
+GrEffectRef* DitherEffect::TestCreate(SkRandom*,
+                                      GrContext*,
+                                      const GrDrawTargetCaps&,
+                                      GrTexture*[]) {
+    return DitherEffect::Create();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+class GLDitherEffect : public GrGLEffect {
+public:
+    GLDitherEffect(const GrBackendEffectFactory&, const GrDrawEffect&);
+
+    virtual void emitCode(GrGLShaderBuilder* builder,
+                          const GrDrawEffect& drawEffect,
+                          EffectKey key,
+                          const char* outputColor,
+                          const char* inputColor,
+                          const TransformedCoordsArray&,
+                          const TextureSamplerArray&) SK_OVERRIDE;
+
+private:
+    typedef GrGLEffect INHERITED;
+};
+
+GLDitherEffect::GLDitherEffect(const GrBackendEffectFactory& factory,
+                               const GrDrawEffect& drawEffect)
+    : INHERITED (factory) {
+}
+
+void GLDitherEffect::emitCode(GrGLShaderBuilder* builder,
+                              const GrDrawEffect& drawEffect,
+                              EffectKey key,
+                              const char* outputColor,
+                              const char* inputColor,
+                              const TransformedCoordsArray&,
+                              const TextureSamplerArray& samplers) {
+    // Generate a random number based on the fragment position. For this
+    // random number generator, we use the "GLSL rand" function
+    // that seems to be floating around on the internet. It works under
+    // the assumption that sin(<big number>) oscillates with high frequency
+    // and sampling it will generate "randomness". Since we're using this
+    // for rendering and not cryptography it should be OK.
+
+    // For each channel c, add the random offset to the pixel to either bump
+    // it up or let it remain constant during quantization.
+    builder->fsCodeAppendf("\t\tfloat r = "
+                           "fract(sin(dot(%s.xy ,vec2(12.9898,78.233))) * 43758.5453);\n",
+                           builder->fragmentPosition());
+    builder->fsCodeAppendf("\t\t%s = (1.0f/255.0f) * vec4(r, r, r, r) + %s;\n",
+                           outputColor, GrGLSLExpr4(inputColor).c_str());
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+GrEffectRef* GrDitherEffect::Create() {
+    return DitherEffect::Create();
+}
diff --git a/src/gpu/effects/GrDitherEffect.h b/src/gpu/effects/GrDitherEffect.h
new file mode 100644 (file)
index 0000000..63036c0
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrDitherEffect_DEFINED
+#define GrDitherEffect_DEFINED
+
+#include "GrTypes.h"
+#include "GrTypesPriv.h"
+
+class GrEffectRef;
+
+namespace GrDitherEffect {
+    /**
+     * Creates an effect that dithers the resulting color to an RGBA8 framebuffer
+     */
+    GrEffectRef* Create();
+};
+
+#endif