Add GPU implementation of rescaling for SkSurface::asyncRescaleAndRead.

The only new capability added is bicubic downscaling but this opens up
more possibilities to add specializations to reduce passes in the GPU
implementation.

Bug: skia:8962
Change-Id: I4ff2e89b24df9dc15988d31ed10a3316b018e583
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/215445
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
diff --git a/src/image/SkSurface.cpp b/src/image/SkSurface.cpp
index a50f581..63c7bc4 100644
--- a/src/image/SkSurface.cpp
+++ b/src/image/SkSurface.cpp
@@ -88,13 +88,109 @@
     }
 }
 
-void SkSurface_Base::onAsyncReadPixels(const SkImageInfo& info, int srcX, int srcY,
-                                       ReadPixelsCallback callback, ReadPixelsContext context) {
-    SkASSERT(SkIRect::MakeWH(this->width(), this->height())
-                     .contains(SkIRect::MakeXYWH(srcX, srcY, info.width(), info.height())));
+void SkSurface_Base::onAsyncRescaleAndReadPixels(const SkImageInfo& info, const SkIRect& srcRect,
+                                                 SkSurface::RescaleGamma rescaleGamma,
+                                                 SkFilterQuality rescaleQuality,
+                                                 SkSurface::ReadPixelsCallback callback,
+                                                 SkSurface::ReadPixelsContext context) {
+    int srcW = srcRect.width();
+    int srcH = srcRect.height();
+    float sx = (float)info.width() / srcW;
+    float sy = (float)info.height() / srcH;
+    // How many bilerp/bicubic steps to do in X and Y. + means upscaling, - means downscaling.
+    int stepsX;
+    int stepsY;
+    if (rescaleQuality > kNone_SkFilterQuality) {
+        stepsX = static_cast<int>((sx > 1.f) ? std::ceil(std::log2f(sx))
+                                             : std::floor(std::log2f(sx)));
+        stepsY = static_cast<int>((sy > 1.f) ? std::ceil(std::log2f(sy))
+                                             : std::floor(std::log2f(sy)));
+    } else {
+        stepsX = sx != 1.f;
+        stepsY = sy != 1.f;
+    }
+
+    SkPaint paint;
+    paint.setBlendMode(SkBlendMode::kSrc);
+    if (stepsX < 0 || stepsY < 0) {
+        // Don't trigger MIP generation. We don't currently have a way to trigger bicubic for
+        // downscaling draws.
+        rescaleQuality = std::min(rescaleQuality, kLow_SkFilterQuality);
+    }
+    paint.setFilterQuality(rescaleQuality);
+    sk_sp<SkSurface> src(SkRef(this));
+    int srcX = srcRect.fLeft;
+    int srcY = srcRect.fTop;
+    SkCanvas::SrcRectConstraint constraint = SkCanvas::kStrict_SrcRectConstraint;
+    // Assume we should ignore the rescale linear request if the surface has no color space since
+    // it's unclear how we'd linearize from an unknown color space.
+    if (rescaleGamma == SkSurface::RescaleGamma::kLinear &&
+        this->getCanvas()->imageInfo().colorSpace() &&
+        !this->getCanvas()->imageInfo().colorSpace()->gammaIsLinear()) {
+        auto cs = this->getCanvas()->imageInfo().colorSpace()->makeLinearGamma();
+        // Promote to F16 color type to preserve precision.
+        auto ii = SkImageInfo::Make(srcW, srcH, kRGBA_F16_SkColorType,
+                                    this->getCanvas()->imageInfo().alphaType(), std::move(cs));
+        auto linearSurf = this->makeSurface(ii);
+        if (!linearSurf) {
+            // Maybe F16 isn't supported? Try again with original color type.
+            ii = ii.makeColorType(this->getCanvas()->imageInfo().colorType());
+            linearSurf = this->makeSurface(ii);
+            if (!linearSurf) {
+                callback(context, nullptr, 0);
+                return;
+            }
+        }
+        this->draw(linearSurf->getCanvas(), -srcX, -srcY, &paint);
+        src = std::move(linearSurf);
+        srcX = 0;
+        srcY = 0;
+        constraint = SkCanvas::kFast_SrcRectConstraint;
+    }
+    while (stepsX || stepsY) {
+        int nextW = info.width();
+        int nextH = info.height();
+        if (stepsX < 0) {
+            nextW = info.width() << (-stepsX - 1);
+            stepsX++;
+        } else if (stepsX != 0) {
+            if (stepsX > 1) {
+                nextW = srcW * 2;
+            }
+            --stepsX;
+        }
+        if (stepsY < 0) {
+            nextH = info.height() << (-stepsY - 1);
+            stepsY++;
+        } else if (stepsY != 0) {
+            if (stepsY > 1) {
+                nextH = srcH * 2;
+            }
+            --stepsY;
+        }
+        auto ii = src->getCanvas()->imageInfo().makeWH(nextW, nextH);
+        if (!stepsX && !stepsY) {
+            // Might as well fold conversion to final info in the last step.
+            ii = info;
+        }
+        auto next = this->makeSurface(ii);
+        if (!next) {
+            callback(context, nullptr, 0);
+            return;
+        }
+        next->getCanvas()->drawImageRect(
+                src->makeImageSnapshot(), SkIRect::MakeXYWH(srcX, srcY, srcW, srcH),
+                SkRect::MakeWH((float)nextW, (float)nextH), &paint, constraint);
+        src = std::move(next);
+        srcX = srcY = 0;
+        srcW = nextW;
+        srcH = nextH;
+        constraint = SkCanvas::kFast_SrcRectConstraint;
+    }
+
     SkAutoPixmapStorage pm;
     pm.alloc(info);
-    if (this->readPixels(pm, srcX, srcY)) {
+    if (src->readPixels(pm, srcX, srcY)) {
         callback(context, pm.addr(), pm.rowBytes());
     } else {
         callback(context, nullptr, 0);
@@ -230,101 +326,8 @@
         callback(context, nullptr, 0);
         return;
     }
-    int srcW = srcRect.width();
-    int srcH = srcRect.height();
-    float sx = (float)info.width() / srcW;
-    float sy = (float)info.height() / srcH;
-    // How many bilerp/bicubic steps to do in X and Y. + means upscaling, - means downscaling.
-    int stepsX;
-    int stepsY;
-    if (rescaleQuality > kNone_SkFilterQuality) {
-        stepsX = static_cast<int>((sx > 1.f) ? std::ceil(std::log2f(sx))
-                                             : std::floor(std::log2f(sx)));
-        stepsY = static_cast<int>((sy > 1.f) ? std::ceil(std::log2f(sy))
-                                             : std::floor(std::log2f(sy)));
-    } else {
-        stepsX = sx != 1.f;
-        stepsY = sy != 1.f;
-    }
-
-    SkPaint paint;
-    paint.setBlendMode(SkBlendMode::kSrc);
-    if (stepsX < 0 || stepsY < 0) {
-        // Don't trigger MIP generation. We don't currently have a way to trigger bicubic for
-        // downscaling draws.
-        rescaleQuality = std::min(rescaleQuality, kLow_SkFilterQuality);
-    }
-    paint.setFilterQuality(rescaleQuality);
-    sk_sp<SkSurface> src(SkRef(this));
-    int srcX = srcRect.fLeft;
-    int srcY = srcRect.fTop;
-    SkCanvas::SrcRectConstraint constraint = SkCanvas::kStrict_SrcRectConstraint;
-    // Assume we should ignore the rescale linear request if the surface has no color space since
-    // it's unclear how we'd linearize from an unknown color space.
-    if (rescaleGamma == SkSurface::RescaleGamma::kLinear &&
-        this->getCanvas()->imageInfo().colorSpace() &&
-        !this->getCanvas()->imageInfo().colorSpace()->gammaIsLinear()) {
-        auto cs = this->getCanvas()->imageInfo().colorSpace()->makeLinearGamma();
-        // Promote to F16 color type to preserve precision.
-        auto ii = SkImageInfo::Make(srcW, srcH, kRGBA_F16_SkColorType,
-                                    this->getCanvas()->imageInfo().alphaType(), std::move(cs));
-        auto linearSurf = this->makeSurface(ii);
-        if (!linearSurf) {
-            // Maybe F16 isn't supported? Try again with original color type.
-            ii = ii.makeColorType(this->getCanvas()->imageInfo().colorType());
-            linearSurf = this->makeSurface(ii);
-            if (!linearSurf) {
-                callback(context, nullptr, 0);
-                return;
-            }
-        }
-        this->draw(linearSurf->getCanvas(), -srcX, -srcY, &paint);
-        src = std::move(linearSurf);
-        srcX = 0;
-        srcY = 0;
-        constraint = SkCanvas::kFast_SrcRectConstraint;
-    }
-    while (stepsX || stepsY) {
-        int nextW = info.width();
-        int nextH = info.height();
-        if (stepsX < 0) {
-            nextW = info.width() << (-stepsX - 1);
-            stepsX++;
-        } else if (stepsX != 0) {
-            if (stepsX > 1) {
-                nextW = srcW * 2;
-            }
-            --stepsX;
-        }
-        if (stepsY < 0) {
-            nextH = info.height() << (-stepsY - 1);
-            stepsY++;
-        } else if (stepsY != 0) {
-            if (stepsY > 1) {
-                nextH = srcH * 2;
-            }
-            --stepsY;
-        }
-        auto ii = src->getCanvas()->imageInfo().makeWH(nextW, nextH);
-        if (!stepsX && !stepsY) {
-            // Might as well fold conversion to final info in the last step.
-            ii = info;
-        }
-        auto next = this->makeSurface(ii);
-        if (!next) {
-            callback(context, nullptr, 0);
-            return;
-        }
-        next->getCanvas()->drawImageRect(
-                src->makeImageSnapshot(), SkIRect::MakeXYWH(srcX, srcY, srcW, srcH),
-                SkRect::MakeWH((float)nextW, (float)nextH), &paint, constraint);
-        src = std::move(next);
-        srcX = srcY = 0;
-        srcW = nextW;
-        srcH = nextH;
-        constraint = SkCanvas::kFast_SrcRectConstraint;
-    }
-    static_cast<SkSurface_Base*>(src.get())->onAsyncReadPixels(info, srcX, srcY, callback, context);
+    asSB(this)->onAsyncRescaleAndReadPixels(info, srcRect, rescaleGamma, rescaleQuality, callback,
+                                            context);
 }
 
 void SkSurface::writePixels(const SkPixmap& pmap, int x, int y) {