support dither in skvm

Dither is handled here by the blitter itself,
when requested and the shader is not constant.

A little refactoring to hang onto the integer (dx,dy)
dst coordinates we need, a little math using them,
and some plumbing to pass around whether or not to
dither and if so how much.

Left another TODO to think about unpremul dither.

Change-Id: Ia660cf12cc729578684233a48088aa703563ecd9
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/266362
Commit-Queue: Mike Klein <mtklein@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
diff --git a/src/core/SkVMBlitter.cpp b/src/core/SkVMBlitter.cpp
index 7b30a80..585c90a 100644
--- a/src/core/SkVMBlitter.cpp
+++ b/src/core/SkVMBlitter.cpp
@@ -38,6 +38,7 @@
         SkBlendMode         blendMode;
         Coverage            coverage;
         SkFilterQuality     quality;
+        bool                dither;
         SkMatrix            ctm;
 
         Params withCoverage(Coverage c) const {
@@ -54,8 +55,9 @@
         uint8_t  colorType,
                  alphaType,
                  blendMode,
-                 coverage;
-        uint32_t padding{0};
+                 coverage,
+                 dither;
+        uint8_t padding[3] = {0,0,0};
         // Params::quality and Params::ctm are only passed to shader->program(),
         // not used here by the blitter itself.  No need to include them in the key;
         // they'll be folded into the shader key if used.
@@ -66,7 +68,8 @@
                 && this->colorType  == that.colorType
                 && this->alphaType  == that.alphaType
                 && this->blendMode  == that.blendMode
-                && this->coverage   == that.coverage;
+                && this->coverage   == that.coverage
+                && this->dither     == that.dither;
         }
 
         Key withCoverage(Coverage c) const {
@@ -78,11 +81,12 @@
     SK_END_REQUIRE_DENSE;
 
     static SkString debug_name(const Key& key) {
-        return SkStringPrintf("CT%d-AT%d-Cov%d-Blend%d-CS%llx-Shader%llx",
+        return SkStringPrintf("CT%d-AT%d-Cov%d-Blend%d-Dither%d-CS%llx-Shader%llx",
                               key.colorType,
                               key.alphaType,
                               key.coverage,
                               key.blendMode,
+                              key.dither,
                               key.colorSpace,
                               key.shader);
     }
@@ -114,15 +118,14 @@
             {
                 const SkShaderBase* shader = as_SB(params.shader);
                 skvm::Builder p;
-                skvm::F32 x = p.to_f32(p.sub(p.uniform32(uniforms->ptr,
-                                                         offsetof(BlitterUniforms, right)),
-                                             p.index())),
-                          y = p.to_f32(p.uniform32(uniforms->ptr,
-                                                   offsetof(BlitterUniforms, y)));
-                x = p.add(x, p.splat(0.5f));
-                y = p.add(y, p.splat(0.5f));
-                skvm::F32 r,g,b,a;
 
+                skvm::I32 dx = p.sub(p.uniform32(uniforms->ptr, offsetof(BlitterUniforms, right)),
+                                     p.index()),
+                          dy = p.uniform32(uniforms->ptr, offsetof(BlitterUniforms, y));
+                skvm::F32 x = p.add(p.to_f32(dx), p.splat(0.5f)),
+                          y = p.add(p.to_f32(dy), p.splat(0.5f));
+
+                skvm::F32 r,g,b,a;
                 if (shader->program(&p,
                                     params.ctm, /*localM=*/nullptr,
                                     params.quality, params.colorSpace.get(),
@@ -152,6 +155,7 @@
                 SkToU8(params.alphaType),
                 SkToU8(params.blendMode),
                 SkToU8(params.coverage),
+                SkToU8(params.dither),
             };
         }
 
@@ -166,15 +170,13 @@
             //    - MaskLCD16: 565 coverage varying
             //    - UniformA8: 8-bit coverage uniform
 
+            skvm::I32 dx = sub(uniform32(uniforms->ptr, offsetof(BlitterUniforms, right)),
+                               index()),
+                      dy = uniform32(uniforms->ptr, offsetof(BlitterUniforms, y));
+            skvm::F32 x = add(to_f32(dx), splat(0.5f)),
+                      y = add(to_f32(dy), splat(0.5f));
+
             skvm::Color src;
-            SkASSERT(params.shader);
-            skvm::F32 x = to_f32(sub(uniform32(uniforms->ptr,
-                                               offsetof(BlitterUniforms, right)),
-                                     index())),
-                      y = to_f32(uniform32(uniforms->ptr,
-                                           offsetof(BlitterUniforms, y)));
-            x = add(x, splat(0.5f));
-            y = add(y, splat(0.5f));
             SkAssertResult(as_SB(params.shader)->program(this,
                                                          params.ctm, /*localM=*/nullptr,
                                                          params.quality, params.colorSpace.get(),
@@ -309,6 +311,51 @@
                 unpremul(&src.r, &src.g, &src.b, src.a);
             }
 
+            float dither_rate = 0.0f;
+            switch (params.colorType) {
+                default:                        dither_rate =      0.0f; break;
+                case kARGB_4444_SkColorType:    dither_rate =   1/15.0f; break;
+                case   kRGB_565_SkColorType:    dither_rate =   1/63.0f; break;
+                case    kGray_8_SkColorType:
+                case  kRGB_888x_SkColorType:
+                case kRGBA_8888_SkColorType:
+                case kBGRA_8888_SkColorType:    dither_rate =  1/255.0f; break;
+                case kRGB_101010x_SkColorType:
+                case kRGBA_1010102_SkColorType: dither_rate = 1/1023.0f; break;
+            }
+            if (params.dither && dither_rate > 0) {
+                // See SkRasterPipeline dither stage.
+
+                // This is 8x8 ordered dithering.  From here we'll only need dx and dx^dy.
+                skvm::I32 X = dx,
+                          Y = bit_xor(dx,dy);
+
+                // If X's low bits are abc and Y's def, M is fcebda,
+                // 6 bits producing all values [0,63] shuffled over an 8x8 grid.
+                skvm::I32 M = bit_or(shl(bit_and(Y, splat(1)), 5),
+                              bit_or(shl(bit_and(X, splat(1)), 4),
+                              bit_or(shl(bit_and(Y, splat(2)), 2),
+                              bit_or(shl(bit_and(X, splat(2)), 1),
+                              bit_or(shr(bit_and(Y, splat(4)), 1),
+                                     shr(bit_and(X, splat(4)), 2))))));
+
+                // Scale to [0,1) by /64, then to (-0.5,0.5) using 63/128 (~0.492) as 0.5-ε,
+                // and finally scale all that by the dither_rate.  We keep dither strength
+                // strictly within ±0.5 to not change exact values like 0 or 1.
+                float scale = dither_rate * (  2/128.0f),
+                      bias  = dither_rate * (-63/128.0f);
+                skvm::F32 dither = mad(to_f32(M), splat(scale), splat(bias));
+
+                src.r = add(src.r, dither);
+                src.g = add(src.g, dither);
+                src.b = add(src.b, dither);
+
+                // TODO: this is consistent with the old code but doesn't make sense for unpremul.
+                src.r = clamp(src.r, splat(0.0f), src.a);
+                src.g = clamp(src.g, splat(0.0f), src.a);
+                src.b = clamp(src.b, splat(0.0f), src.a);
+            }
+
             // Store back to the destination.
             switch (params.colorType) {
                 default: SkUNREACHABLE;
@@ -369,6 +416,8 @@
             blendMode =  SkBlendMode::kSrc;
         }
 
+        bool dither = paint.isDither() && !as_SB(shader)->isConstant();
+
         // In general all the information we use to make decisions here need to
         // be reflected in Params and Key to make program caching sound, and it
         // might appear that shader->isOpaque() is a property of the shader's
@@ -388,6 +437,7 @@
             blendMode,
             Coverage::Full,  // Placeholder... withCoverage() will change as needed.
             paint.getFilterQuality(),
+            dither,
             ctm,
         };
     }