Add async rescale and read APIs to SkImage.

These function the same as the already existing
SkSurface APIs.

Bug: skia:10431

Change-Id: I4f1e842d8d4b72ee27bae5f8a85e499e130d420c
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/299281
Reviewed-by: Brian Osman <brianosman@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index b79a83d..b24bac8 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1410,6 +1410,8 @@
     deps = []
     public_deps = []
     sources = [
+      "tools/gpu/BackendTextureImageFactory.cpp",
+      "tools/gpu/BackendTextureImageFactory.h",
       "tools/gpu/GrContextFactory.cpp",
       "tools/gpu/GrTest.cpp",
       "tools/gpu/MemoryCache.cpp",
diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt
index 7f66cf9..ed499fc 100644
--- a/RELEASE_NOTES.txt
+++ b/RELEASE_NOTES.txt
@@ -16,6 +16,9 @@
   * Enable BackendSemaphores for the Direct3D backend.
     https://review.skia.org/298752
 
+  * Added SkImage:asyncRescaleAndReadPixels and SkImage::asyncRescaleAndReadPixelsYUV420
+    https://review.skia.org/299281
+
 * * *
 
 Milestone 85
diff --git a/gm/asyncrescaleandread.cpp b/gm/asyncrescaleandread.cpp
index f119144..754e32d 100644
--- a/gm/asyncrescaleandread.cpp
+++ b/gm/asyncrescaleandread.cpp
@@ -23,14 +23,14 @@
 namespace {
 struct AsyncContext {
     bool fCalled = false;
-    std::unique_ptr<const SkSurface::AsyncReadResult> fResult;
+    std::unique_ptr<const SkImage::AsyncReadResult> fResult;
 };
 }  // anonymous namespace
 
 // Making this a lambda in the test functions caused:
 //   "error: cannot compile this forwarded non-trivially copyable parameter yet"
 // on x86/Win/Clang bot, referring to 'result'.
-static void async_callback(void* c, std::unique_ptr<const SkSurface::AsyncReadResult> result) {
+static void async_callback(void* c, std::unique_ptr<const SkImage::AsyncReadResult> result) {
     auto context = static_cast<AsyncContext*>(c);
     context->fResult = std::move(result);
     context->fCalled = true;
@@ -38,61 +38,70 @@
 
 // Draws the image to a surface, does a asyncRescaleAndReadPixels of the image, and then sticks
 // the result in a raster image.
-static sk_sp<SkImage> do_read_and_scale(SkSurface* surface, const SkIRect& srcRect,
-                                        const SkImageInfo& ii, SkSurface::RescaleGamma rescaleGamma,
+template <typename Src>
+static sk_sp<SkImage> do_read_and_scale(Src* src,
+                                        GrContext* context,
+                                        const SkIRect& srcRect,
+                                        const SkImageInfo& ii,
+                                        SkImage::RescaleGamma rescaleGamma,
                                         SkFilterQuality quality) {
-    auto* context = new AsyncContext();
-    surface->asyncRescaleAndReadPixels(ii, srcRect, rescaleGamma, quality, async_callback, context);
-    if (auto ctx = surface->getContext()) {
-        ctx->submit();
+    auto* asyncContext = new AsyncContext();
+    src->asyncRescaleAndReadPixels(ii, srcRect, rescaleGamma, quality, async_callback,
+                                   asyncContext);
+    if (context) {
+        context->submit();
     }
-    while (!context->fCalled) {
+    while (!asyncContext->fCalled) {
         // Only GPU should actually be asynchronous.
-        SkASSERT(surface->getCanvas()->getGrContext());
-        surface->getCanvas()->getGrContext()->checkAsyncWorkCompletion();
+        SkASSERT(context);
+        context->checkAsyncWorkCompletion();
     }
-    if (!context->fResult) {
+    if (!asyncContext->fResult) {
         return nullptr;
     }
-    SkPixmap pixmap(ii, context->fResult->data(0), context->fResult->rowBytes(0));
+    SkPixmap pixmap(ii, asyncContext->fResult->data(0), asyncContext->fResult->rowBytes(0));
     auto releasePixels = [](const void*, void* c) { delete static_cast<AsyncContext*>(c); };
-    return SkImage::MakeFromRaster(pixmap, releasePixels, context);
+    return SkImage::MakeFromRaster(pixmap, releasePixels, asyncContext);
 }
 
-static sk_sp<SkImage> do_read_and_scale_yuv(SkSurface* surface, SkYUVColorSpace yuvCS,
-                                            const SkIRect& srcRect, SkISize size,
-                                            SkSurface::RescaleGamma rescaleGamma,
-                                            SkFilterQuality quality, SkScopeExit* cleanup) {
+template <typename Src>
+static sk_sp<SkImage> do_read_and_scale_yuv(Src* src,
+                                            GrContext* context,
+                                            SkYUVColorSpace yuvCS,
+                                            const SkIRect& srcRect,
+                                            SkISize size,
+                                            SkImage::RescaleGamma rescaleGamma,
+                                            SkFilterQuality quality,
+                                            SkScopeExit* cleanup) {
     SkASSERT(!(size.width() & 0b1) && !(size.height() & 0b1));
 
     SkISize uvSize = {size.width()/2, size.height()/2};
     SkImageInfo yII  = SkImageInfo::Make(size,   kGray_8_SkColorType, kPremul_SkAlphaType);
     SkImageInfo uvII = SkImageInfo::Make(uvSize, kGray_8_SkColorType, kPremul_SkAlphaType);
 
-    AsyncContext context;
-    surface->asyncRescaleAndReadPixelsYUV420(yuvCS, SkColorSpace::MakeSRGB(), srcRect, size,
-                                             rescaleGamma, quality, async_callback, &context);
-    if (auto ctx = surface->getContext()) {
-        ctx->submit();
+    AsyncContext asyncContext;
+    src->asyncRescaleAndReadPixelsYUV420(yuvCS, SkColorSpace::MakeSRGB(), srcRect, size,
+                                         rescaleGamma, quality, async_callback, &asyncContext);
+    if (context) {
+        context->submit();
     }
-    while (!context.fCalled) {
+    while (!asyncContext.fCalled) {
         // Only GPU should actually be asynchronous.
-        SkASSERT(surface->getCanvas()->getGrContext());
-        surface->getCanvas()->getGrContext()->checkAsyncWorkCompletion();
+        SkASSERT(context);
+        context->checkAsyncWorkCompletion();
     }
-    if (!context.fResult) {
+    if (!asyncContext.fResult) {
         return nullptr;
     }
-    auto* gr = surface->getCanvas()->getGrContext();
     GrBackendTexture backendTextures[3];
 
-    SkPixmap yPM(yII,  context.fResult->data(0), context.fResult->rowBytes(0));
-    SkPixmap uPM(uvII, context.fResult->data(1), context.fResult->rowBytes(1));
-    SkPixmap vPM(uvII, context.fResult->data(2), context.fResult->rowBytes(2));
+    SkPixmap yPM(yII, asyncContext.fResult->data(0), asyncContext.fResult->rowBytes(0));
+    SkPixmap uPM(uvII, asyncContext.fResult->data(1), asyncContext.fResult->rowBytes(1));
+    SkPixmap vPM(uvII, asyncContext.fResult->data(2), asyncContext.fResult->rowBytes(2));
 
-    backendTextures[0] = gr->createBackendTexture(yPM, GrRenderable::kNo, GrProtected::kNo);
-    backendTextures[1] = gr->createBackendTexture(uPM, GrRenderable::kNo, GrProtected::kNo);
-    backendTextures[2] = gr->createBackendTexture(vPM, GrRenderable::kNo, GrProtected::kNo);
+    backendTextures[0] = context->createBackendTexture(yPM, GrRenderable::kNo, GrProtected::kNo);
+    backendTextures[1] = context->createBackendTexture(uPM, GrRenderable::kNo, GrProtected::kNo);
+    backendTextures[2] = context->createBackendTexture(vPM, GrRenderable::kNo, GrProtected::kNo);
 
     SkYUVAIndex indices[4] = {
         { 0, SkColorChannel::kR},
@@ -101,25 +110,31 @@
         {-1, SkColorChannel::kR}
     };
 
-    *cleanup = {[gr, backendTextures] {
+    *cleanup = {[context, backendTextures] {
         GrFlushInfo flushInfo;
         flushInfo.fFlags = kSyncCpu_GrFlushFlag;
-        gr->flush(flushInfo);
-        gr->submit(true);
-        gr->deleteBackendTexture(backendTextures[0]);
-        gr->deleteBackendTexture(backendTextures[1]);
-        gr->deleteBackendTexture(backendTextures[2]);
+        context->flush(flushInfo);
+        context->submit(true);
+        context->deleteBackendTexture(backendTextures[0]);
+        context->deleteBackendTexture(backendTextures[1]);
+        context->deleteBackendTexture(backendTextures[2]);
     }};
 
-    return SkImage::MakeFromYUVATextures(gr, yuvCS, backendTextures, indices, size,
+    return SkImage::MakeFromYUVATextures(context, yuvCS, backendTextures, indices, size,
                                          kTopLeft_GrSurfaceOrigin, SkColorSpace::MakeSRGB());
 }
 
 // Draws a grid of rescales. The columns are none, low, and high filter quality. The rows are
 // rescale in src gamma and rescale in linear gamma.
-static skiagm::DrawResult do_rescale_grid(SkCanvas* canvas, SkSurface* surface,
-                                          const SkIRect& srcRect, SkISize newSize, bool doYUV420,
-                                          SkString* errorMsg, int pad = 0) {
+template <typename Src>
+static skiagm::DrawResult do_rescale_grid(SkCanvas* canvas,
+                                          Src* src,
+                                          GrContext* context,
+                                          const SkIRect& srcRect,
+                                          SkISize newSize,
+                                          bool doYUV420,
+                                          SkString* errorMsg,
+                                          int pad = 0) {
     if (doYUV420) {
         if (!canvas->getGrContext() || !canvas->getGrContext()->priv().asDirectContext()) {
             errorMsg->printf("YUV420 only supported on direct GPU for now.");
@@ -134,13 +149,13 @@
 
     SkYUVColorSpace yuvColorSpace = kRec601_SkYUVColorSpace;
     canvas->save();
-    for (auto gamma : {SkSurface::RescaleGamma::kSrc, SkSurface::RescaleGamma::kLinear}) {
+    for (auto gamma : {SkImage::RescaleGamma::kSrc, SkImage::RescaleGamma::kLinear}) {
         canvas->save();
         for (auto quality : {kNone_SkFilterQuality, kLow_SkFilterQuality, kHigh_SkFilterQuality}) {
             SkScopeExit cleanup;
             sk_sp<SkImage> result;
             if (doYUV420) {
-                result = do_read_and_scale_yuv(surface, yuvColorSpace, srcRect, newSize, gamma,
+                result = do_read_and_scale_yuv(src, context, yuvColorSpace, srcRect, newSize, gamma,
                                                quality, &cleanup);
                 if (!result) {
                     errorMsg->printf("YUV420 async call failed. Allowed for now.");
@@ -149,7 +164,7 @@
                 int nextCS = static_cast<int>(yuvColorSpace + 1) % (kLastEnum_SkYUVColorSpace + 1);
                 yuvColorSpace = static_cast<SkYUVColorSpace>(nextCS);
             } else {
-                result = do_read_and_scale(surface, srcRect, ii, gamma, quality);
+                result = do_read_and_scale(src, context, srcRect, ii, gamma, quality);
                 if (!result) {
                     errorMsg->printf("async read call failed.");
                     return skiagm::DrawResult::kFail;
@@ -165,9 +180,13 @@
     return skiagm::DrawResult::kOk;
 }
 
-static skiagm::DrawResult do_rescale_image_grid(SkCanvas* canvas, const char* imageFile,
-                                                const SkIRect& srcRect, SkISize newSize,
-                                                bool doYUV420, SkString* errorMsg) {
+static skiagm::DrawResult do_rescale_image_grid(SkCanvas* canvas,
+                                                const char* imageFile,
+                                                const SkIRect& srcRect,
+                                                SkISize newSize,
+                                                bool doSurface,
+                                                bool doYUV420,
+                                                SkString* errorMsg) {
     auto image = GetResourceAsImage(imageFile);
     if (!image) {
         errorMsg->printf("Could not load image file %s.", imageFile);
@@ -177,54 +196,81 @@
         *errorMsg = "Not supported on recording/vector backends.";
         return skiagm::DrawResult::kSkip;
     }
-    // Turn the image into a surface in order to call the read and rescale API
-    auto surfInfo = image->imageInfo().makeDimensions(image->dimensions());
-    auto surface = canvas->makeSurface(surfInfo);
-    if (!surface && surfInfo.colorType() == kBGRA_8888_SkColorType) {
-        surfInfo = surfInfo.makeColorType(kRGBA_8888_SkColorType);
-        surface = canvas->makeSurface(surfInfo);
-    }
-    if (!surface) {
-        *errorMsg = "Could not create surface for image.";
-        // When testing abandoned GrContext we expect surface creation to fail.
-        if (canvas->getGrContext() && canvas->getGrContext()->abandoned()) {
-            return skiagm::DrawResult::kSkip;
+    if (doSurface) {
+        // Turn the image into a surface in order to call the read and rescale API
+        auto surfInfo = image->imageInfo().makeDimensions(image->dimensions());
+        auto surface = canvas->makeSurface(surfInfo);
+        if (!surface && surfInfo.colorType() == kBGRA_8888_SkColorType) {
+            surfInfo = surfInfo.makeColorType(kRGBA_8888_SkColorType);
+            surface = canvas->makeSurface(surfInfo);
         }
-        return skiagm::DrawResult::kFail;
+        if (!surface) {
+            *errorMsg = "Could not create surface for image.";
+            // When testing abandoned GrContext we expect surface creation to fail.
+            if (canvas->getGrContext() && canvas->getGrContext()->abandoned()) {
+                return skiagm::DrawResult::kSkip;
+            }
+            return skiagm::DrawResult::kFail;
+        }
+        SkPaint paint;
+        paint.setBlendMode(SkBlendMode::kSrc);
+        surface->getCanvas()->drawImage(image, 0, 0, &paint);
+        return do_rescale_grid(canvas, surface.get(), canvas->getGrContext(), srcRect, newSize,
+                               doYUV420, errorMsg);
+    } else if (auto ctx = canvas->getGrContext()) {
+        image = image->makeTextureImage(ctx);
     }
-    SkPaint paint;
-    paint.setBlendMode(SkBlendMode::kSrc);
-    surface->getCanvas()->drawImage(image, 0, 0, &paint);
-    return do_rescale_grid(canvas, surface.get(), srcRect, newSize, doYUV420, errorMsg);
+    return do_rescale_grid(canvas, image.get(), canvas->getGrContext(), srcRect, newSize, doYUV420,
+                           errorMsg);
 }
 
-#define DEF_RESCALE_AND_READ_GM(IMAGE_FILE, TAG, SRC_RECT, W, H)                              \
-    DEF_SIMPLE_GM_CAN_FAIL(async_rescale_and_read_##TAG, canvas, errorMsg, 3 * W, 2 * H) {    \
-        ToolUtils::draw_checkerboard(canvas, SK_ColorDKGRAY, SK_ColorLTGRAY, 25);             \
-        return do_rescale_image_grid(canvas, #IMAGE_FILE, SRC_RECT, {W, H}, false, errorMsg); \
+#define DEF_RESCALE_AND_READ_SURF_GM(IMAGE_FILE, TAG, SRC_RECT, W, H)                      \
+    DEF_SIMPLE_GM_CAN_FAIL(async_rescale_and_read_##TAG, canvas, errorMsg, 3 * W, 2 * H) { \
+        ToolUtils::draw_checkerboard(canvas, SK_ColorDKGRAY, SK_ColorLTGRAY, 25);          \
+        return do_rescale_image_grid(canvas, #IMAGE_FILE, SRC_RECT, {W, H}, true, false,   \
+                                     errorMsg);                                            \
     }
 
-#define DEF_RESCALE_AND_READ_YUV_GM(IMAGE_FILE, TAG, SRC_RECT, W, H)                              \
-    DEF_SIMPLE_GM_CAN_FAIL(async_rescale_and_read_yuv420_##TAG, canvas, errorMsg, 3 * W, 2 * H) { \
-        ToolUtils::draw_checkerboard(canvas, SK_ColorDKGRAY, SK_ColorLTGRAY, 25);                 \
-        return do_rescale_image_grid(canvas, #IMAGE_FILE, SRC_RECT, {W, H}, true, errorMsg);      \
+#define DEF_RESCALE_AND_READ_YUV_SURF_GM(IMAGE_FILE, TAG, SRC_RECT, W, H)                          \
+    DEF_SIMPLE_GM_CAN_FAIL(async_rescale_and_read_yuv420_##TAG, canvas, errorMsg, 3 * W, 2 * H) {  \
+        ToolUtils::draw_checkerboard(canvas, SK_ColorDKGRAY, SK_ColorLTGRAY, 25);                  \
+        return do_rescale_image_grid(canvas, #IMAGE_FILE, SRC_RECT, {W, H}, true, true, errorMsg); \
     }
 
-DEF_RESCALE_AND_READ_YUV_GM(images/yellow_rose.webp, rose, SkIRect::MakeXYWH(50, 5, 200, 150),
-                            410, 376)
+#define DEF_RESCALE_AND_READ_IMG_GM(IMAGE_FILE, TAG, SRC_RECT, W, H)                       \
+    DEF_SIMPLE_GM_CAN_FAIL(async_rescale_and_read_##TAG, canvas, errorMsg, 3 * W, 2 * H) { \
+        ToolUtils::draw_checkerboard(canvas, SK_ColorDKGRAY, SK_ColorLTGRAY, 25);          \
+        return do_rescale_image_grid(canvas, #IMAGE_FILE, SRC_RECT, {W, H}, false, false,  \
+                                     errorMsg);                                            \
+    }
 
-DEF_RESCALE_AND_READ_GM(images/yellow_rose.webp, rose, SkIRect::MakeXYWH(100, 20, 100, 100),
-                        410, 410)
+#define DEF_RESCALE_AND_READ_YUV_IMG_GM(IMAGE_FILE, TAG, SRC_RECT, W, H)                           \
+    DEF_SIMPLE_GM_CAN_FAIL(async_rescale_and_read_yuv420_##TAG, canvas, errorMsg, 3 * W, 2 * H) {  \
+        ToolUtils::draw_checkerboard(canvas, SK_ColorDKGRAY, SK_ColorLTGRAY, 25);                  \
+        return do_rescale_image_grid(canvas, #IMAGE_FILE, SRC_RECT, {W, H}, true, true, errorMsg); \
+    }
 
-DEF_RESCALE_AND_READ_GM(images/dog.jpg, dog_down, SkIRect::MakeXYWH(0, 10, 180, 150), 45, 45)
-DEF_RESCALE_AND_READ_GM(images/dog.jpg, dog_up, SkIRect::MakeWH(180, 180), 800, 400)
+DEF_RESCALE_AND_READ_YUV_SURF_GM(
+        images/yellow_rose.webp, rose, SkIRect::MakeXYWH(50, 5, 200, 150), 410, 376)
 
-DEF_RESCALE_AND_READ_GM(images/text.png, text_down, SkIRect::MakeWH(637, 105), (int)(0.7 * 637),
-                        (int)(0.7 * 105))
-DEF_RESCALE_AND_READ_GM(images/text.png, text_up, SkIRect::MakeWH(637, 105), (int)(1.2 * 637),
-                        (int)(1.2 * 105))
-DEF_RESCALE_AND_READ_GM(images/text.png, text_up_large, SkIRect::MakeXYWH(300, 0, 300, 105),
-                        (int)(2.4 * 300), (int)(2.4 * 105))
+DEF_RESCALE_AND_READ_YUV_IMG_GM(
+        images/yellow_rose.webp, rose_down, SkIRect::MakeXYWH(50, 5, 200, 150), 106, 60)
+
+DEF_RESCALE_AND_READ_SURF_GM(
+        images/yellow_rose.webp, rose, SkIRect::MakeXYWH(100, 20, 100, 100), 410, 410)
+
+DEF_RESCALE_AND_READ_SURF_GM(images/dog.jpg, dog_down, SkIRect::MakeXYWH(0, 10, 180, 150), 45, 45)
+DEF_RESCALE_AND_READ_IMG_GM(images/dog.jpg, dog_up, SkIRect::MakeWH(180, 180), 800, 400)
+
+DEF_RESCALE_AND_READ_IMG_GM(
+        images/text.png, text_down, SkIRect::MakeWH(637, 105), (int)(0.7 * 637), (int)(0.7 * 105))
+DEF_RESCALE_AND_READ_SURF_GM(
+        images/text.png, text_up, SkIRect::MakeWH(637, 105), (int)(1.2 * 637), (int)(1.2 * 105))
+DEF_RESCALE_AND_READ_IMG_GM(images/text.png,
+                            text_up_large,
+                            SkIRect::MakeXYWH(300, 0, 300, 105),
+                            (int)(2.4 * 300),
+                            (int)(2.4 * 105))
 
 // Exercises non-scaling YUV420. Reads from the original canvas's surface in order to
 // exercise case where source surface is not a texture (in glbert config).
@@ -244,8 +290,8 @@
 
     SkScopeExit scopeExit;
     auto yuvImage = do_read_and_scale_yuv(
-            surface, kRec601_SkYUVColorSpace, SkIRect::MakeWH(400, 300), {400, 300},
-            SkSurface::RescaleGamma::kSrc, kNone_SkFilterQuality, &scopeExit);
+            surface, surface->getContext(), kRec601_SkYUVColorSpace, SkIRect::MakeWH(400, 300),
+            {400, 300}, SkImage::RescaleGamma::kSrc, kNone_SkFilterQuality, &scopeExit);
 
     canvas->clear(SK_ColorWHITE);
     canvas->drawImage(yuvImage.get(), 0, 0);
@@ -283,13 +329,17 @@
     canvas->translate(kPad, kPad);
     skiagm::DrawResult result;
     SkISize downSize = {static_cast<int>(kInner/2),  static_cast<int>(kInner / 2)};
-    result = do_rescale_grid(canvas, surface.get(), srcRect, downSize, false, errorMsg, kPad);
+    GrContext* context = canvas->getGrContext();
+    result = do_rescale_grid(canvas, surface.get(), context, srcRect, downSize, false, errorMsg,
+                             kPad);
+
     if (result != skiagm::DrawResult::kOk) {
         return result;
     }
     canvas->translate(0, 4 * downSize.height());
     SkISize upSize = {static_cast<int>(kInner * 3.5), static_cast<int>(kInner * 4.6)};
-    result = do_rescale_grid(canvas, surface.get(), srcRect, upSize, false, errorMsg, kPad);
+    result =
+            do_rescale_grid(canvas, surface.get(), context, srcRect, upSize, false, errorMsg, kPad);
     if (result != skiagm::DrawResult::kOk) {
         return result;
     }
diff --git a/gn/core.gni b/gn/core.gni
index 7f2d32d..fab22d3 100644
--- a/gn/core.gni
+++ b/gn/core.gni
@@ -418,6 +418,8 @@
   "$_src/image/SkImage_Lazy.cpp",
   "$_src/image/SkImage_Lazy.h",
   "$_src/image/SkImage_Raster.cpp",
+  "$_src/image/SkRescaleAndReadPixels.cpp",
+  "$_src/image/SkRescaleAndReadPixels.h",
   "$_src/image/SkSurface.cpp",
   "$_src/image/SkSurface_Base.h",
 
diff --git a/include/core/SkImage.h b/include/core/SkImage.h
index 8e2db9d..341ed02 100644
--- a/include/core/SkImage.h
+++ b/include/core/SkImage.h
@@ -958,6 +958,108 @@
     bool readPixels(const SkPixmap& dst, int srcX, int srcY,
                     CachingHint cachingHint = kAllow_CachingHint) const;
 
+    /** The result from asyncRescaleAndReadPixels() or asyncRescaleAndReadPixelsYUV420(). */
+    class AsyncReadResult {
+    public:
+        AsyncReadResult(const AsyncReadResult&) = delete;
+        AsyncReadResult(AsyncReadResult&&) = delete;
+        AsyncReadResult& operator=(const AsyncReadResult&) = delete;
+        AsyncReadResult& operator=(AsyncReadResult&&) = delete;
+
+        virtual ~AsyncReadResult() = default;
+        virtual int count() const = 0;
+        virtual const void* data(int i) const = 0;
+        virtual size_t rowBytes(int i) const = 0;
+
+    protected:
+        AsyncReadResult() = default;
+    };
+
+    /** Client-provided context that is passed to client-provided ReadPixelsContext. */
+    using ReadPixelsContext = void*;
+
+    /**  Client-provided callback to asyncRescaleAndReadPixels() or
+         asyncRescaleAndReadPixelsYUV420() that is called when read result is ready or on failure.
+     */
+    using ReadPixelsCallback = void(ReadPixelsContext, std::unique_ptr<const AsyncReadResult>);
+
+    enum class RescaleGamma : bool { kSrc, kLinear };
+
+    /** Makes image pixel data available to caller, possibly asynchronously. It can also rescale
+        the image pixels.
+
+        Currently asynchronous reads are only supported on the GPU backend and only when the
+        underlying 3D API supports transfer buffers and CPU/GPU synchronization primitives. In all
+        other cases this operates synchronously.
+
+        Data is read from the source sub-rectangle, is optionally converted to a linear gamma, is
+        rescaled to the size indicated by 'info', is then converted to the color space, color type,
+        and alpha type of 'info'. A 'srcRect' that is not contained by the bounds of the image
+        causes failure.
+
+        When the pixel data is ready the caller's ReadPixelsCallback is called with a
+        AsyncReadResult containing pixel data in the requested color type, alpha type, and color
+        space. The AsyncReadResult will have count() == 1. Upon failure the callback is called with
+        nullptr for AsyncReadResult. For a GPU image this flushes work but a submit must occur to
+        guarantee a finite time before the callback is called.
+
+        The data is valid for the lifetime of AsyncReadResult with the exception that if the SkImage
+        is GPU-backed the data is immediately invalidated if the GrContext is abandoned or
+        destroyed.
+
+        @param info            info of the requested pixels
+        @param srcRect         subrectangle of image to read
+        @param rescaleGamma    controls whether rescaling is done in the image's gamma or whether
+                               the source data is transformed to a linear gamma before rescaling.
+        @param rescaleQuality  controls the quality (and cost) of the rescaling
+        @param callback        function to call with result of the read
+        @param context         passed to callback
+    */
+    void asyncRescaleAndReadPixels(const SkImageInfo& info,
+                                   const SkIRect& srcRect,
+                                   RescaleGamma rescaleGamma,
+                                   SkFilterQuality rescaleQuality,
+                                   ReadPixelsCallback callback,
+                                   ReadPixelsContext context);
+
+    /**
+        Similar to asyncRescaleAndReadPixels but performs an additional conversion to YUV. The
+        RGB->YUV conversion is controlled by 'yuvColorSpace'. The YUV data is returned as three
+        planes ordered y, u, v. The u and v planes are half the width and height of the resized
+        rectangle. The y, u, and v values are single bytes. Currently this fails if 'dstSize'
+        width and height are not even. A 'srcRect' that is not contained by the bounds of the
+        image causes failure.
+
+        When the pixel data is ready the caller's ReadPixelsCallback is called with a
+        AsyncReadResult containing the planar data. The AsyncReadResult will have count() == 3.
+        Upon failure the callback is called with nullptr for AsyncReadResult. For a GPU image this
+        flushes work but a submit must occur to guarantee a finite time before the callback is
+        called.
+
+        The data is valid for the lifetime of AsyncReadResult with the exception that if the SkImage
+        is GPU-backed the data is immediately invalidated if the GrContext is abandoned or
+        destroyed.
+
+        @param yuvColorSpace  The transformation from RGB to YUV. Applied to the resized image
+                              after it is converted to dstColorSpace.
+        @param dstColorSpace  The color space to convert the resized image to, after rescaling.
+        @param srcRect        The portion of the image to rescale and convert to YUV planes.
+        @param dstSize        The size to rescale srcRect to
+        @param rescaleGamma   controls whether rescaling is done in the image's gamma or whether
+                              the source data is transformed to a linear gamma before rescaling.
+        @param rescaleQuality controls the quality (and cost) of the rescaling
+        @param callback       function to call with the planar read result
+        @param context        passed to callback
+     */
+    void asyncRescaleAndReadPixelsYUV420(SkYUVColorSpace yuvColorSpace,
+                                         sk_sp<SkColorSpace> dstColorSpace,
+                                         const SkIRect& srcRect,
+                                         const SkISize& dstSize,
+                                         RescaleGamma rescaleGamma,
+                                         SkFilterQuality rescaleQuality,
+                                         ReadPixelsCallback callback,
+                                         ReadPixelsContext context);
+
     /** Copies SkImage to dst, scaling pixels to fit dst.width() and dst.height(), and
         converting pixels to match dst.colorType() and dst.alphaType(). Returns true if
         pixels are copied. Returns false if dst.addr() is nullptr, or dst.rowBytes() is
diff --git a/include/core/SkSurface.h b/include/core/SkSurface.h
index 14a7117..f32a7e0 100644
--- a/include/core/SkSurface.h
+++ b/include/core/SkSurface.h
@@ -769,22 +769,7 @@
     */
     bool readPixels(const SkBitmap& dst, int srcX, int srcY);
 
-    /** The result from asyncRescaleAndReadPixels() or asyncRescaleAndReadPixelsYUV420(). */
-    class AsyncReadResult {
-    public:
-        AsyncReadResult(const AsyncReadResult&) = delete;
-        AsyncReadResult(AsyncReadResult&&) = delete;
-        AsyncReadResult& operator=(const AsyncReadResult&) = delete;
-        AsyncReadResult& operator=(AsyncReadResult&&) = delete;
-
-        virtual ~AsyncReadResult() = default;
-        virtual int count() const = 0;
-        virtual const void* data(int i) const = 0;
-        virtual size_t rowBytes(int i) const = 0;
-
-    protected:
-        AsyncReadResult() = default;
-    };
+    using AsyncReadResult = SkImage::AsyncReadResult;
 
     /** Client-provided context that is passed to client-provided ReadPixelsContext. */
     using ReadPixelsContext = void*;
@@ -797,7 +782,7 @@
     /** Controls the gamma that rescaling occurs in for asyncRescaleAndReadPixels() and
         asyncRescaleAndReadPixelsYUV420().
      */
-    enum RescaleGamma : bool { kSrc, kLinear };
+    using RescaleGamma = SkImage::RescaleGamma;
 
     /** Makes surface pixel data available to caller, possibly asynchronously. It can also rescale
         the surface pixels.
@@ -829,9 +814,12 @@
         @param callback        function to call with result of the read
         @param context         passed to callback
      */
-    void asyncRescaleAndReadPixels(const SkImageInfo& info, const SkIRect& srcRect,
-                                   RescaleGamma rescaleGamma, SkFilterQuality rescaleQuality,
-                                   ReadPixelsCallback callback, ReadPixelsContext context);
+    void asyncRescaleAndReadPixels(const SkImageInfo& info,
+                                   const SkIRect& srcRect,
+                                   RescaleGamma rescaleGamma,
+                                   SkFilterQuality rescaleQuality,
+                                   ReadPixelsCallback callback,
+                                   ReadPixelsContext context);
 
     /**
         Similar to asyncRescaleAndReadPixels but performs an additional conversion to YUV. The
@@ -869,7 +857,7 @@
                                          RescaleGamma rescaleGamma,
                                          SkFilterQuality rescaleQuality,
                                          ReadPixelsCallback callback,
-                                         ReadPixelsContext);
+                                         ReadPixelsContext context);
 
     /** Copies SkRect of pixels from the src SkPixmap to the SkSurface.
 
diff --git a/infra/bots/gen_tasks_logic/dm_flags.go b/infra/bots/gen_tasks_logic/dm_flags.go
index c79faac..ab859ac 100644
--- a/infra/bots/gen_tasks_logic/dm_flags.go
+++ b/infra/bots/gen_tasks_logic/dm_flags.go
@@ -918,7 +918,7 @@
 		match = append(match, "~WritePixelsNonTextureMSAA_Gpu")
 	}
 
-        if b.extraConfig("Direct3D") {
+	if b.extraConfig("Direct3D") {
 		// skia:9935
 		match = append(match, "~^ColorTypeBackendAllocationTest$")
 		match = append(match, "~^CompressedBackendAllocationTest$")
@@ -933,10 +933,13 @@
 		match = append(match, "~^TextureIdleStateTest$")
 		match = append(match, "~^TextureProxyTest$")
 	}
-
-        if b.extraConfig("Direct3D") && b.gpu("RadeonHD7770") && b.matchOs("Win") {
+	if b.extraConfig("Direct3D") && b.matchOs("Win") {
 		// skia:9935
-		match = append(match, "~^AsyncReadPixels$")
+		match = append(match, "~^ImageAsyncReadPixels$")
+	}
+	if b.extraConfig("Direct3D") && b.gpu("RadeonHD7770") && b.matchOs("Win") {
+		// skia:9935
+		match = append(match, "~^SurfaceAsyncReadPixels$")
 		match = append(match, "~^MorphologyFilterRadiusWithMirrorCTM_Gpu$")
 		match = append(match, "~^ReadPixels_Gpu$")
 		match = append(match, "~^ReadPixels_Texture$")
diff --git a/infra/bots/tasks.json b/infra/bots/tasks.json
index bad6432..b9f92f8 100755
--- a/infra/bots/tasks.json
+++ b/infra/bots/tasks.json
@@ -47943,7 +47943,7 @@
         "skia/infra/bots/run_recipe.py",
         "${ISOLATED_OUTDIR}",
         "test",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Debug-All-Direct3D\",\"dm_flags\":\"[\\\"dm\\\",\\\"--nameByHash\\\",\\\"--key\\\",\\\"arch\\\",\\\"x86_64\\\",\\\"compiler\\\",\\\"Clang\\\",\\\"configuration\\\",\\\"Debug\\\",\\\"cpu_or_gpu\\\",\\\"GPU\\\",\\\"cpu_or_gpu_value\\\",\\\"RadeonHD7770\\\",\\\"extra_config\\\",\\\"Direct3D\\\",\\\"model\\\",\\\"ShuttleA\\\",\\\"os\\\",\\\"Win10\\\",\\\"style\\\",\\\"default\\\",\\\"--randomProcessorTest\\\",\\\"--nocpu\\\",\\\"--config\\\",\\\"d3d\\\",\\\"--src\\\",\\\"tests\\\",\\\"gm\\\",\\\"image\\\",\\\"colorImage\\\",\\\"svg\\\",\\\"--blacklist\\\",\\\"_\\\",\\\"svg\\\",\\\"_\\\",\\\"svgparse_\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"pal8os2v2.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"pal8os2v2-16.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rgba32abf.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rgb24prof.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rgb24lprof.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"8bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"4bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"32bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"24bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"testimgari.jpg\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rle8-height-negative.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rle4-height-negative.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"error\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\"interlaced1.png\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\"interlaced2.png\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\"interlaced3.png\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".arw\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".cr2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".dng\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".nef\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".nrw\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".orf\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".raf\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".rw2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".pef\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".srw\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".ARW\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".CR2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".DNG\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".NEF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".NRW\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".ORF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".RAF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".RW2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".PEF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".SRW\\\",\\\"--match\\\",\\\"~^ColorTypeBackendAllocationTest$\\\",\\\"~^CompressedBackendAllocationTest$\\\",\\\"~^DDLSkSurfaceFlush$\\\",\\\"~^GrBackendTextureImageMipMappedTest$\\\",\\\"~^GrMeshTest$\\\",\\\"~^GrSurfaceRenderability$\\\",\\\"~^GrTextureMipMapInvalidationTest$\\\",\\\"~^PremulAlphaRoundTrip_Gpu$\\\",\\\"~^ReplaceSurfaceBackendTexture$\\\",\\\"~^SkImage_makeTextureImage$\\\",\\\"~^TextureIdleStateTest$\\\",\\\"~^TextureProxyTest$\\\",\\\"~^AsyncReadPixels$\\\",\\\"~^MorphologyFilterRadiusWithMirrorCTM_Gpu$\\\",\\\"~^ReadPixels_Gpu$\\\",\\\"~^ReadPixels_Texture$\\\",\\\"--nonativeFonts\\\",\\\"--verbose\\\"]\",\"dm_properties\":\"{\\\"buildbucket_build_id\\\":\\\"<(BUILDBUCKET_BUILD_ID)\\\",\\\"builder\\\":\\\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Debug-All-Direct3D\\\",\\\"gitHash\\\":\\\"<(REVISION)\\\",\\\"issue\\\":\\\"<(ISSUE)\\\",\\\"patch_storage\\\":\\\"<(PATCH_STORAGE)\\\",\\\"patchset\\\":\\\"<(PATCHSET)\\\",\\\"swarming_bot_id\\\":\\\"${SWARMING_BOT_ID}\\\",\\\"swarming_task_id\\\":\\\"${SWARMING_TASK_ID}\\\",\\\"task_id\\\":\\\"<(TASK_ID)\\\"}\",\"do_upload\":\"true\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"images\":\"true\",\"patch_issue\":\"<(ISSUE_INT)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET_INT)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"resources\":\"true\",\"revision\":\"<(REVISION)\",\"skps\":\"true\",\"svgs\":\"true\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Debug-All-Direct3D\",\"dm_flags\":\"[\\\"dm\\\",\\\"--nameByHash\\\",\\\"--key\\\",\\\"arch\\\",\\\"x86_64\\\",\\\"compiler\\\",\\\"Clang\\\",\\\"configuration\\\",\\\"Debug\\\",\\\"cpu_or_gpu\\\",\\\"GPU\\\",\\\"cpu_or_gpu_value\\\",\\\"RadeonHD7770\\\",\\\"extra_config\\\",\\\"Direct3D\\\",\\\"model\\\",\\\"ShuttleA\\\",\\\"os\\\",\\\"Win10\\\",\\\"style\\\",\\\"default\\\",\\\"--randomProcessorTest\\\",\\\"--nocpu\\\",\\\"--config\\\",\\\"d3d\\\",\\\"--src\\\",\\\"tests\\\",\\\"gm\\\",\\\"image\\\",\\\"colorImage\\\",\\\"svg\\\",\\\"--blacklist\\\",\\\"_\\\",\\\"svg\\\",\\\"_\\\",\\\"svgparse_\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"pal8os2v2.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"pal8os2v2-16.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rgba32abf.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rgb24prof.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rgb24lprof.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"8bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"4bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"32bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"24bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"testimgari.jpg\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rle8-height-negative.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rle4-height-negative.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"error\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\"interlaced1.png\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\"interlaced2.png\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\"interlaced3.png\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".arw\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".cr2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".dng\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".nef\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".nrw\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".orf\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".raf\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".rw2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".pef\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".srw\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".ARW\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".CR2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".DNG\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".NEF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".NRW\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".ORF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".RAF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".RW2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".PEF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".SRW\\\",\\\"--match\\\",\\\"~^ColorTypeBackendAllocationTest$\\\",\\\"~^CompressedBackendAllocationTest$\\\",\\\"~^DDLSkSurfaceFlush$\\\",\\\"~^GrBackendTextureImageMipMappedTest$\\\",\\\"~^GrMeshTest$\\\",\\\"~^GrSurfaceRenderability$\\\",\\\"~^GrTextureMipMapInvalidationTest$\\\",\\\"~^PremulAlphaRoundTrip_Gpu$\\\",\\\"~^ReplaceSurfaceBackendTexture$\\\",\\\"~^SkImage_makeTextureImage$\\\",\\\"~^TextureIdleStateTest$\\\",\\\"~^TextureProxyTest$\\\",\\\"~^ImageAsyncReadPixels$\\\",\\\"~^SurfaceAsyncReadPixels$\\\",\\\"~^MorphologyFilterRadiusWithMirrorCTM_Gpu$\\\",\\\"~^ReadPixels_Gpu$\\\",\\\"~^ReadPixels_Texture$\\\",\\\"--nonativeFonts\\\",\\\"--verbose\\\"]\",\"dm_properties\":\"{\\\"buildbucket_build_id\\\":\\\"<(BUILDBUCKET_BUILD_ID)\\\",\\\"builder\\\":\\\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Debug-All-Direct3D\\\",\\\"gitHash\\\":\\\"<(REVISION)\\\",\\\"issue\\\":\\\"<(ISSUE)\\\",\\\"patch_storage\\\":\\\"<(PATCH_STORAGE)\\\",\\\"patchset\\\":\\\"<(PATCHSET)\\\",\\\"swarming_bot_id\\\":\\\"${SWARMING_BOT_ID}\\\",\\\"swarming_task_id\\\":\\\"${SWARMING_TASK_ID}\\\",\\\"task_id\\\":\\\"<(TASK_ID)\\\"}\",\"do_upload\":\"true\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"images\":\"true\",\"patch_issue\":\"<(ISSUE_INT)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET_INT)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"resources\":\"true\",\"revision\":\"<(REVISION)\",\"skps\":\"true\",\"svgs\":\"true\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "skia"
       ],
       "dependencies": [
@@ -48275,7 +48275,7 @@
         "skia/infra/bots/run_recipe.py",
         "${ISOLATED_OUTDIR}",
         "test",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All-Direct3D\",\"dm_flags\":\"[\\\"dm\\\",\\\"--nameByHash\\\",\\\"--key\\\",\\\"arch\\\",\\\"x86_64\\\",\\\"compiler\\\",\\\"Clang\\\",\\\"configuration\\\",\\\"Release\\\",\\\"cpu_or_gpu\\\",\\\"GPU\\\",\\\"cpu_or_gpu_value\\\",\\\"RadeonHD7770\\\",\\\"extra_config\\\",\\\"Direct3D\\\",\\\"model\\\",\\\"ShuttleA\\\",\\\"os\\\",\\\"Win10\\\",\\\"style\\\",\\\"default\\\",\\\"--randomProcessorTest\\\",\\\"--nocpu\\\",\\\"--config\\\",\\\"d3d\\\",\\\"--src\\\",\\\"tests\\\",\\\"gm\\\",\\\"image\\\",\\\"colorImage\\\",\\\"svg\\\",\\\"--blacklist\\\",\\\"_\\\",\\\"svg\\\",\\\"_\\\",\\\"svgparse_\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"pal8os2v2.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"pal8os2v2-16.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rgba32abf.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rgb24prof.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rgb24lprof.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"8bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"4bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"32bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"24bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"testimgari.jpg\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rle8-height-negative.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rle4-height-negative.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"error\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\"interlaced1.png\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\"interlaced2.png\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\"interlaced3.png\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".arw\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".cr2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".dng\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".nef\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".nrw\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".orf\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".raf\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".rw2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".pef\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".srw\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".ARW\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".CR2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".DNG\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".NEF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".NRW\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".ORF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".RAF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".RW2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".PEF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".SRW\\\",\\\"--match\\\",\\\"~^ColorTypeBackendAllocationTest$\\\",\\\"~^CompressedBackendAllocationTest$\\\",\\\"~^DDLSkSurfaceFlush$\\\",\\\"~^GrBackendTextureImageMipMappedTest$\\\",\\\"~^GrMeshTest$\\\",\\\"~^GrSurfaceRenderability$\\\",\\\"~^GrTextureMipMapInvalidationTest$\\\",\\\"~^PremulAlphaRoundTrip_Gpu$\\\",\\\"~^ReplaceSurfaceBackendTexture$\\\",\\\"~^SkImage_makeTextureImage$\\\",\\\"~^TextureIdleStateTest$\\\",\\\"~^TextureProxyTest$\\\",\\\"~^AsyncReadPixels$\\\",\\\"~^MorphologyFilterRadiusWithMirrorCTM_Gpu$\\\",\\\"~^ReadPixels_Gpu$\\\",\\\"~^ReadPixels_Texture$\\\",\\\"--nonativeFonts\\\",\\\"--verbose\\\"]\",\"dm_properties\":\"{\\\"buildbucket_build_id\\\":\\\"<(BUILDBUCKET_BUILD_ID)\\\",\\\"builder\\\":\\\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All-Direct3D\\\",\\\"gitHash\\\":\\\"<(REVISION)\\\",\\\"issue\\\":\\\"<(ISSUE)\\\",\\\"patch_storage\\\":\\\"<(PATCH_STORAGE)\\\",\\\"patchset\\\":\\\"<(PATCHSET)\\\",\\\"swarming_bot_id\\\":\\\"${SWARMING_BOT_ID}\\\",\\\"swarming_task_id\\\":\\\"${SWARMING_TASK_ID}\\\",\\\"task_id\\\":\\\"<(TASK_ID)\\\"}\",\"do_upload\":\"true\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"images\":\"true\",\"patch_issue\":\"<(ISSUE_INT)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET_INT)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"resources\":\"true\",\"revision\":\"<(REVISION)\",\"skps\":\"true\",\"svgs\":\"true\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All-Direct3D\",\"dm_flags\":\"[\\\"dm\\\",\\\"--nameByHash\\\",\\\"--key\\\",\\\"arch\\\",\\\"x86_64\\\",\\\"compiler\\\",\\\"Clang\\\",\\\"configuration\\\",\\\"Release\\\",\\\"cpu_or_gpu\\\",\\\"GPU\\\",\\\"cpu_or_gpu_value\\\",\\\"RadeonHD7770\\\",\\\"extra_config\\\",\\\"Direct3D\\\",\\\"model\\\",\\\"ShuttleA\\\",\\\"os\\\",\\\"Win10\\\",\\\"style\\\",\\\"default\\\",\\\"--randomProcessorTest\\\",\\\"--nocpu\\\",\\\"--config\\\",\\\"d3d\\\",\\\"--src\\\",\\\"tests\\\",\\\"gm\\\",\\\"image\\\",\\\"colorImage\\\",\\\"svg\\\",\\\"--blacklist\\\",\\\"_\\\",\\\"svg\\\",\\\"_\\\",\\\"svgparse_\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"pal8os2v2.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"pal8os2v2-16.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rgba32abf.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rgb24prof.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rgb24lprof.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"8bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"4bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"32bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"24bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"testimgari.jpg\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rle8-height-negative.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rle4-height-negative.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"error\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\"interlaced1.png\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\"interlaced2.png\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\"interlaced3.png\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".arw\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".cr2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".dng\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".nef\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".nrw\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".orf\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".raf\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".rw2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".pef\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".srw\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".ARW\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".CR2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".DNG\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".NEF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".NRW\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".ORF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".RAF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".RW2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".PEF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".SRW\\\",\\\"--match\\\",\\\"~^ColorTypeBackendAllocationTest$\\\",\\\"~^CompressedBackendAllocationTest$\\\",\\\"~^DDLSkSurfaceFlush$\\\",\\\"~^GrBackendTextureImageMipMappedTest$\\\",\\\"~^GrMeshTest$\\\",\\\"~^GrSurfaceRenderability$\\\",\\\"~^GrTextureMipMapInvalidationTest$\\\",\\\"~^PremulAlphaRoundTrip_Gpu$\\\",\\\"~^ReplaceSurfaceBackendTexture$\\\",\\\"~^SkImage_makeTextureImage$\\\",\\\"~^TextureIdleStateTest$\\\",\\\"~^TextureProxyTest$\\\",\\\"~^ImageAsyncReadPixels$\\\",\\\"~^SurfaceAsyncReadPixels$\\\",\\\"~^MorphologyFilterRadiusWithMirrorCTM_Gpu$\\\",\\\"~^ReadPixels_Gpu$\\\",\\\"~^ReadPixels_Texture$\\\",\\\"--nonativeFonts\\\",\\\"--verbose\\\"]\",\"dm_properties\":\"{\\\"buildbucket_build_id\\\":\\\"<(BUILDBUCKET_BUILD_ID)\\\",\\\"builder\\\":\\\"Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All-Direct3D\\\",\\\"gitHash\\\":\\\"<(REVISION)\\\",\\\"issue\\\":\\\"<(ISSUE)\\\",\\\"patch_storage\\\":\\\"<(PATCH_STORAGE)\\\",\\\"patchset\\\":\\\"<(PATCHSET)\\\",\\\"swarming_bot_id\\\":\\\"${SWARMING_BOT_ID}\\\",\\\"swarming_task_id\\\":\\\"${SWARMING_TASK_ID}\\\",\\\"task_id\\\":\\\"<(TASK_ID)\\\"}\",\"do_upload\":\"true\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"images\":\"true\",\"patch_issue\":\"<(ISSUE_INT)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET_INT)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"resources\":\"true\",\"revision\":\"<(REVISION)\",\"skps\":\"true\",\"svgs\":\"true\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "skia"
       ],
       "dependencies": [
@@ -49022,7 +49022,7 @@
         "skia/infra/bots/run_recipe.py",
         "${ISOLATED_OUTDIR}",
         "test",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Debug-All-Direct3D\",\"dm_flags\":\"[\\\"dm\\\",\\\"--nameByHash\\\",\\\"--key\\\",\\\"arch\\\",\\\"x86_64\\\",\\\"compiler\\\",\\\"MSVC\\\",\\\"configuration\\\",\\\"Debug\\\",\\\"cpu_or_gpu\\\",\\\"GPU\\\",\\\"cpu_or_gpu_value\\\",\\\"QuadroP400\\\",\\\"extra_config\\\",\\\"Direct3D\\\",\\\"model\\\",\\\"Golo\\\",\\\"os\\\",\\\"Win10\\\",\\\"style\\\",\\\"default\\\",\\\"--randomProcessorTest\\\",\\\"--nocpu\\\",\\\"--config\\\",\\\"d3d\\\",\\\"--src\\\",\\\"tests\\\",\\\"gm\\\",\\\"image\\\",\\\"colorImage\\\",\\\"svg\\\",\\\"--blacklist\\\",\\\"_\\\",\\\"svg\\\",\\\"_\\\",\\\"svgparse_\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"pal8os2v2.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"pal8os2v2-16.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rgba32abf.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rgb24prof.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rgb24lprof.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"8bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"4bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"32bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"24bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"testimgari.jpg\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rle8-height-negative.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rle4-height-negative.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"error\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\"interlaced1.png\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\"interlaced2.png\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\"interlaced3.png\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".arw\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".cr2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".dng\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".nef\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".nrw\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".orf\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".raf\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".rw2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".pef\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".srw\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".ARW\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".CR2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".DNG\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".NEF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".NRW\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".ORF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".RAF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".RW2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".PEF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".SRW\\\",\\\"--match\\\",\\\"~^ColorTypeBackendAllocationTest$\\\",\\\"~^CompressedBackendAllocationTest$\\\",\\\"~^DDLSkSurfaceFlush$\\\",\\\"~^GrBackendTextureImageMipMappedTest$\\\",\\\"~^GrMeshTest$\\\",\\\"~^GrSurfaceRenderability$\\\",\\\"~^GrTextureMipMapInvalidationTest$\\\",\\\"~^PremulAlphaRoundTrip_Gpu$\\\",\\\"~^ReplaceSurfaceBackendTexture$\\\",\\\"~^SkImage_makeTextureImage$\\\",\\\"~^TextureIdleStateTest$\\\",\\\"~^TextureProxyTest$\\\",\\\"--nonativeFonts\\\",\\\"--verbose\\\"]\",\"dm_properties\":\"{\\\"buildbucket_build_id\\\":\\\"<(BUILDBUCKET_BUILD_ID)\\\",\\\"builder\\\":\\\"Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Debug-All-Direct3D\\\",\\\"gitHash\\\":\\\"<(REVISION)\\\",\\\"issue\\\":\\\"<(ISSUE)\\\",\\\"patch_storage\\\":\\\"<(PATCH_STORAGE)\\\",\\\"patchset\\\":\\\"<(PATCHSET)\\\",\\\"swarming_bot_id\\\":\\\"${SWARMING_BOT_ID}\\\",\\\"swarming_task_id\\\":\\\"${SWARMING_TASK_ID}\\\",\\\"task_id\\\":\\\"<(TASK_ID)\\\"}\",\"do_upload\":\"true\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"images\":\"true\",\"patch_issue\":\"<(ISSUE_INT)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET_INT)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"resources\":\"true\",\"revision\":\"<(REVISION)\",\"skps\":\"true\",\"svgs\":\"true\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Debug-All-Direct3D\",\"dm_flags\":\"[\\\"dm\\\",\\\"--nameByHash\\\",\\\"--key\\\",\\\"arch\\\",\\\"x86_64\\\",\\\"compiler\\\",\\\"MSVC\\\",\\\"configuration\\\",\\\"Debug\\\",\\\"cpu_or_gpu\\\",\\\"GPU\\\",\\\"cpu_or_gpu_value\\\",\\\"QuadroP400\\\",\\\"extra_config\\\",\\\"Direct3D\\\",\\\"model\\\",\\\"Golo\\\",\\\"os\\\",\\\"Win10\\\",\\\"style\\\",\\\"default\\\",\\\"--randomProcessorTest\\\",\\\"--nocpu\\\",\\\"--config\\\",\\\"d3d\\\",\\\"--src\\\",\\\"tests\\\",\\\"gm\\\",\\\"image\\\",\\\"colorImage\\\",\\\"svg\\\",\\\"--blacklist\\\",\\\"_\\\",\\\"svg\\\",\\\"_\\\",\\\"svgparse_\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"pal8os2v2.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"pal8os2v2-16.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rgba32abf.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rgb24prof.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rgb24lprof.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"8bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"4bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"32bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"24bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"testimgari.jpg\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rle8-height-negative.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rle4-height-negative.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"error\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\"interlaced1.png\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\"interlaced2.png\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\"interlaced3.png\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".arw\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".cr2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".dng\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".nef\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".nrw\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".orf\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".raf\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".rw2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".pef\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".srw\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".ARW\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".CR2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".DNG\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".NEF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".NRW\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".ORF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".RAF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".RW2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".PEF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".SRW\\\",\\\"--match\\\",\\\"~^ColorTypeBackendAllocationTest$\\\",\\\"~^CompressedBackendAllocationTest$\\\",\\\"~^DDLSkSurfaceFlush$\\\",\\\"~^GrBackendTextureImageMipMappedTest$\\\",\\\"~^GrMeshTest$\\\",\\\"~^GrSurfaceRenderability$\\\",\\\"~^GrTextureMipMapInvalidationTest$\\\",\\\"~^PremulAlphaRoundTrip_Gpu$\\\",\\\"~^ReplaceSurfaceBackendTexture$\\\",\\\"~^SkImage_makeTextureImage$\\\",\\\"~^TextureIdleStateTest$\\\",\\\"~^TextureProxyTest$\\\",\\\"~^ImageAsyncReadPixels$\\\",\\\"--nonativeFonts\\\",\\\"--verbose\\\"]\",\"dm_properties\":\"{\\\"buildbucket_build_id\\\":\\\"<(BUILDBUCKET_BUILD_ID)\\\",\\\"builder\\\":\\\"Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Debug-All-Direct3D\\\",\\\"gitHash\\\":\\\"<(REVISION)\\\",\\\"issue\\\":\\\"<(ISSUE)\\\",\\\"patch_storage\\\":\\\"<(PATCH_STORAGE)\\\",\\\"patchset\\\":\\\"<(PATCHSET)\\\",\\\"swarming_bot_id\\\":\\\"${SWARMING_BOT_ID}\\\",\\\"swarming_task_id\\\":\\\"${SWARMING_TASK_ID}\\\",\\\"task_id\\\":\\\"<(TASK_ID)\\\"}\",\"do_upload\":\"true\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"images\":\"true\",\"patch_issue\":\"<(ISSUE_INT)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET_INT)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"resources\":\"true\",\"revision\":\"<(REVISION)\",\"skps\":\"true\",\"svgs\":\"true\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "skia"
       ],
       "dependencies": [
@@ -49271,7 +49271,7 @@
         "skia/infra/bots/run_recipe.py",
         "${ISOLATED_OUTDIR}",
         "test",
-        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Release-All-Direct3D\",\"dm_flags\":\"[\\\"dm\\\",\\\"--nameByHash\\\",\\\"--key\\\",\\\"arch\\\",\\\"x86_64\\\",\\\"compiler\\\",\\\"MSVC\\\",\\\"configuration\\\",\\\"Release\\\",\\\"cpu_or_gpu\\\",\\\"GPU\\\",\\\"cpu_or_gpu_value\\\",\\\"QuadroP400\\\",\\\"extra_config\\\",\\\"Direct3D\\\",\\\"model\\\",\\\"Golo\\\",\\\"os\\\",\\\"Win10\\\",\\\"style\\\",\\\"default\\\",\\\"--randomProcessorTest\\\",\\\"--nocpu\\\",\\\"--config\\\",\\\"d3d\\\",\\\"--src\\\",\\\"tests\\\",\\\"gm\\\",\\\"image\\\",\\\"colorImage\\\",\\\"svg\\\",\\\"--blacklist\\\",\\\"_\\\",\\\"svg\\\",\\\"_\\\",\\\"svgparse_\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"pal8os2v2.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"pal8os2v2-16.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rgba32abf.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rgb24prof.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rgb24lprof.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"8bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"4bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"32bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"24bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"testimgari.jpg\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rle8-height-negative.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rle4-height-negative.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"error\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\"interlaced1.png\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\"interlaced2.png\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\"interlaced3.png\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".arw\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".cr2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".dng\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".nef\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".nrw\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".orf\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".raf\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".rw2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".pef\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".srw\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".ARW\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".CR2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".DNG\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".NEF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".NRW\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".ORF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".RAF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".RW2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".PEF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".SRW\\\",\\\"--match\\\",\\\"~^ColorTypeBackendAllocationTest$\\\",\\\"~^CompressedBackendAllocationTest$\\\",\\\"~^DDLSkSurfaceFlush$\\\",\\\"~^GrBackendTextureImageMipMappedTest$\\\",\\\"~^GrMeshTest$\\\",\\\"~^GrSurfaceRenderability$\\\",\\\"~^GrTextureMipMapInvalidationTest$\\\",\\\"~^PremulAlphaRoundTrip_Gpu$\\\",\\\"~^ReplaceSurfaceBackendTexture$\\\",\\\"~^SkImage_makeTextureImage$\\\",\\\"~^TextureIdleStateTest$\\\",\\\"~^TextureProxyTest$\\\",\\\"--nonativeFonts\\\",\\\"--verbose\\\"]\",\"dm_properties\":\"{\\\"buildbucket_build_id\\\":\\\"<(BUILDBUCKET_BUILD_ID)\\\",\\\"builder\\\":\\\"Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Release-All-Direct3D\\\",\\\"gitHash\\\":\\\"<(REVISION)\\\",\\\"issue\\\":\\\"<(ISSUE)\\\",\\\"patch_storage\\\":\\\"<(PATCH_STORAGE)\\\",\\\"patchset\\\":\\\"<(PATCHSET)\\\",\\\"swarming_bot_id\\\":\\\"${SWARMING_BOT_ID}\\\",\\\"swarming_task_id\\\":\\\"${SWARMING_TASK_ID}\\\",\\\"task_id\\\":\\\"<(TASK_ID)\\\"}\",\"do_upload\":\"true\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"images\":\"true\",\"patch_issue\":\"<(ISSUE_INT)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET_INT)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"resources\":\"true\",\"revision\":\"<(REVISION)\",\"skps\":\"true\",\"svgs\":\"true\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Release-All-Direct3D\",\"dm_flags\":\"[\\\"dm\\\",\\\"--nameByHash\\\",\\\"--key\\\",\\\"arch\\\",\\\"x86_64\\\",\\\"compiler\\\",\\\"MSVC\\\",\\\"configuration\\\",\\\"Release\\\",\\\"cpu_or_gpu\\\",\\\"GPU\\\",\\\"cpu_or_gpu_value\\\",\\\"QuadroP400\\\",\\\"extra_config\\\",\\\"Direct3D\\\",\\\"model\\\",\\\"Golo\\\",\\\"os\\\",\\\"Win10\\\",\\\"style\\\",\\\"default\\\",\\\"--randomProcessorTest\\\",\\\"--nocpu\\\",\\\"--config\\\",\\\"d3d\\\",\\\"--src\\\",\\\"tests\\\",\\\"gm\\\",\\\"image\\\",\\\"colorImage\\\",\\\"svg\\\",\\\"--blacklist\\\",\\\"_\\\",\\\"svg\\\",\\\"_\\\",\\\"svgparse_\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"pal8os2v2.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"pal8os2v2-16.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rgba32abf.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rgb24prof.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rgb24lprof.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"8bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"4bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"32bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"24bpp-pixeldata-cropped.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"testimgari.jpg\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rle8-height-negative.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"rle4-height-negative.bmp\\\",\\\"_\\\",\\\"image\\\",\\\"gen_platf\\\",\\\"error\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\"interlaced1.png\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\"interlaced2.png\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\"interlaced3.png\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".arw\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".cr2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".dng\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".nef\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".nrw\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".orf\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".raf\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".rw2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".pef\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".srw\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".ARW\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".CR2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".DNG\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".NEF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".NRW\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".ORF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".RAF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".RW2\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".PEF\\\",\\\"_\\\",\\\"image\\\",\\\"_\\\",\\\".SRW\\\",\\\"--match\\\",\\\"~^ColorTypeBackendAllocationTest$\\\",\\\"~^CompressedBackendAllocationTest$\\\",\\\"~^DDLSkSurfaceFlush$\\\",\\\"~^GrBackendTextureImageMipMappedTest$\\\",\\\"~^GrMeshTest$\\\",\\\"~^GrSurfaceRenderability$\\\",\\\"~^GrTextureMipMapInvalidationTest$\\\",\\\"~^PremulAlphaRoundTrip_Gpu$\\\",\\\"~^ReplaceSurfaceBackendTexture$\\\",\\\"~^SkImage_makeTextureImage$\\\",\\\"~^TextureIdleStateTest$\\\",\\\"~^TextureProxyTest$\\\",\\\"~^ImageAsyncReadPixels$\\\",\\\"--nonativeFonts\\\",\\\"--verbose\\\"]\",\"dm_properties\":\"{\\\"buildbucket_build_id\\\":\\\"<(BUILDBUCKET_BUILD_ID)\\\",\\\"builder\\\":\\\"Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Release-All-Direct3D\\\",\\\"gitHash\\\":\\\"<(REVISION)\\\",\\\"issue\\\":\\\"<(ISSUE)\\\",\\\"patch_storage\\\":\\\"<(PATCH_STORAGE)\\\",\\\"patchset\\\":\\\"<(PATCHSET)\\\",\\\"swarming_bot_id\\\":\\\"${SWARMING_BOT_ID}\\\",\\\"swarming_task_id\\\":\\\"${SWARMING_TASK_ID}\\\",\\\"task_id\\\":\\\"<(TASK_ID)\\\"}\",\"do_upload\":\"true\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"images\":\"true\",\"patch_issue\":\"<(ISSUE_INT)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET_INT)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"resources\":\"true\",\"revision\":\"<(REVISION)\",\"skps\":\"true\",\"svgs\":\"true\",\"swarm_out_dir\":\"test\",\"task_id\":\"<(TASK_ID)\"}",
         "skia"
       ],
       "dependencies": [
diff --git a/src/gpu/GrRenderTargetContext.cpp b/src/gpu/GrRenderTargetContext.cpp
index c4e3a13..144b685 100644
--- a/src/gpu/GrRenderTargetContext.cpp
+++ b/src/gpu/GrRenderTargetContext.cpp
@@ -23,12 +23,10 @@
 #include "src/core/SkMatrixProvider.h"
 #include "src/core/SkRRectPriv.h"
 #include "src/core/SkSurfacePriv.h"
-#include "src/core/SkYUVMath.h"
 #include "src/gpu/GrAppliedClip.h"
 #include "src/gpu/GrAuditTrail.h"
 #include "src/gpu/GrBlurUtils.h"
 #include "src/gpu/GrCaps.h"
-#include "src/gpu/GrClientMappedBufferManager.h"
 #include "src/gpu/GrClip.h"
 #include "src/gpu/GrColor.h"
 #include "src/gpu/GrContextPriv.h"
@@ -50,7 +48,6 @@
 #include "src/gpu/SkGr.h"
 #include "src/gpu/effects/GrBicubicEffect.h"
 #include "src/gpu/effects/GrRRectEffect.h"
-#include "src/gpu/effects/generated/GrColorMatrixFragmentProcessor.h"
 #include "src/gpu/geometry/GrQuad.h"
 #include "src/gpu/geometry/GrQuadUtils.h"
 #include "src/gpu/geometry/GrStyledShape.h"
@@ -1716,526 +1713,6 @@
     this->addOp(std::move(op));
 }
 
-void GrRenderTargetContext::asyncRescaleAndReadPixels(
-        const SkImageInfo& info, const SkIRect& srcRect, SkSurface::RescaleGamma rescaleGamma,
-        SkFilterQuality rescaleQuality, ReadPixelsCallback callback, ReadPixelsContext context) {
-    auto direct = fContext->priv().asDirectContext();
-    if (!direct) {
-        callback(context, nullptr);
-        return;
-    }
-    if (this->asRenderTargetProxy()->wrapsVkSecondaryCB()) {
-        callback(context, nullptr);
-        return;
-    }
-    if (this->asRenderTargetProxy()->framebufferOnly()) {
-        callback(context, nullptr);
-        return;
-    }
-    auto dstCT = SkColorTypeToGrColorType(info.colorType());
-    if (dstCT == GrColorType::kUnknown) {
-        callback(context, nullptr);
-        return;
-    }
-    bool needsRescale = srcRect.width() != info.width() || srcRect.height() != info.height();
-    auto colorTypeOfFinalContext = this->colorInfo().colorType();
-    auto backendFormatOfFinalContext = this->asSurfaceProxy()->backendFormat();
-    if (needsRescale) {
-        colorTypeOfFinalContext = dstCT;
-        backendFormatOfFinalContext = this->caps()->getDefaultBackendFormat(dstCT,
-                                                                            GrRenderable::kYes);
-    }
-    auto readInfo = this->caps()->supportedReadPixelsColorType(colorTypeOfFinalContext,
-                                                               backendFormatOfFinalContext, dstCT);
-    // Fail if we can't read from the source surface's color type.
-    if (readInfo.fColorType == GrColorType::kUnknown) {
-        callback(context, nullptr);
-        return;
-    }
-    // Fail if read color type does not have all of dstCT's color channels and those missing color
-    // channels are in the src.
-    uint32_t dstChannels = GrColorTypeChannelFlags(dstCT);
-    uint32_t legalReadChannels = GrColorTypeChannelFlags(readInfo.fColorType);
-    uint32_t srcChannels = GrColorTypeChannelFlags(this->colorInfo().colorType());
-    if ((~legalReadChannels & dstChannels) & srcChannels) {
-        callback(context, nullptr);
-        return;
-    }
-
-    std::unique_ptr<GrRenderTargetContext> tempRTC;
-    int x = srcRect.fLeft;
-    int y = srcRect.fTop;
-    if (needsRescale) {
-        tempRTC = this->rescale(info, kTopLeft_GrSurfaceOrigin, srcRect, rescaleGamma,
-                                rescaleQuality);
-        if (!tempRTC) {
-            callback(context, nullptr);
-            return;
-        }
-        SkASSERT(SkColorSpace::Equals(tempRTC->colorInfo().colorSpace(), info.colorSpace()));
-        SkASSERT(tempRTC->origin() == kTopLeft_GrSurfaceOrigin);
-        x = y = 0;
-    } else {
-        sk_sp<GrColorSpaceXform> xform = GrColorSpaceXform::Make(this->colorInfo().colorSpace(),
-                                                                 this->colorInfo().alphaType(),
-                                                                 info.colorSpace(),
-                                                                 info.alphaType());
-        // Insert a draw to a temporary surface if we need to do a y-flip or color space conversion.
-        if (this->origin() == kBottomLeft_GrSurfaceOrigin || xform) {
-            // We flip or color convert by drawing and we don't currently support drawing to
-            // kPremul.
-            if (info.alphaType() == kUnpremul_SkAlphaType) {
-                callback(context, nullptr);
-                return;
-            }
-            GrSurfaceProxyView texProxyView = this->readSurfaceView();
-            SkRect srcRectToDraw = SkRect::Make(srcRect);
-            // If the src is not texturable first try to make a copy to a texture.
-            if (!texProxyView.asTextureProxy()) {
-                texProxyView =
-                        GrSurfaceProxyView::Copy(fContext, texProxyView, GrMipMapped::kNo, srcRect,
-                                                 SkBackingFit::kApprox, SkBudgeted::kNo);
-                if (!texProxyView) {
-                    callback(context, nullptr);
-                    return;
-                }
-                SkASSERT(texProxyView.asTextureProxy());
-                srcRectToDraw = SkRect::MakeWH(srcRect.width(), srcRect.height());
-            }
-            tempRTC = GrRenderTargetContext::Make(
-                    direct, this->colorInfo().colorType(), info.refColorSpace(),
-                    SkBackingFit::kApprox, srcRect.size(), 1, GrMipMapped::kNo, GrProtected::kNo,
-                    kTopLeft_GrSurfaceOrigin);
-            if (!tempRTC) {
-                callback(context, nullptr);
-                return;
-            }
-            tempRTC->drawTexture(nullptr, std::move(texProxyView), this->colorInfo().alphaType(),
-                                 GrSamplerState::Filter::kNearest, SkBlendMode::kSrc,
-                                 SK_PMColor4fWHITE, srcRectToDraw,
-                                 SkRect::MakeWH(srcRect.width(), srcRect.height()), GrAA::kNo,
-                                 GrQuadAAFlags::kNone, SkCanvas::kFast_SrcRectConstraint,
-                                 SkMatrix::I(), std::move(xform));
-            x = y = 0;
-        }
-    }
-    auto rtc = tempRTC ? tempRTC.get() : this;
-    return rtc->asyncReadPixels(SkIRect::MakeXYWH(x, y, info.width(), info.height()),
-                                info.colorType(), callback, context);
-}
-
-class GrRenderTargetContext::AsyncReadResult : public SkSurface::AsyncReadResult {
-public:
-    AsyncReadResult(uint32_t inboxID) : fInboxID(inboxID) {}
-    ~AsyncReadResult() override {
-        for (int i = 0; i < fPlanes.count(); ++i) {
-            if (!fPlanes[i].fMappedBuffer) {
-                delete[] static_cast<const char*>(fPlanes[i].fData);
-            } else {
-                GrClientMappedBufferManager::BufferFinishedMessageBus::Post(
-                        {std::move(fPlanes[i].fMappedBuffer), fInboxID});
-            }
-        }
-    }
-
-    int count() const override { return fPlanes.count(); }
-    const void* data(int i) const override { return fPlanes[i].fData; }
-    size_t rowBytes(int i) const override { return fPlanes[i].fRowBytes; }
-
-    bool addTransferResult(const PixelTransferResult& result,
-                           SkISize dimensions,
-                           size_t rowBytes,
-                           GrClientMappedBufferManager* manager) {
-        SkASSERT(!result.fTransferBuffer->isMapped());
-        const void* mappedData = result.fTransferBuffer->map();
-        if (!mappedData) {
-            return false;
-        }
-        if (result.fPixelConverter) {
-            std::unique_ptr<char[]> convertedData(new char[rowBytes * dimensions.height()]);
-            result.fPixelConverter(convertedData.get(), mappedData);
-            this->addCpuPlane(std::move(convertedData), rowBytes);
-            result.fTransferBuffer->unmap();
-        } else {
-            manager->insert(result.fTransferBuffer);
-            this->addMappedPlane(mappedData, rowBytes, std::move(result.fTransferBuffer));
-        }
-        return true;
-    }
-
-    void addCpuPlane(std::unique_ptr<const char[]> data, size_t rowBytes) {
-        SkASSERT(data);
-        SkASSERT(rowBytes > 0);
-        fPlanes.emplace_back(data.release(), rowBytes, nullptr);
-    }
-
-private:
-    void addMappedPlane(const void* data, size_t rowBytes, sk_sp<GrGpuBuffer> mappedBuffer) {
-        SkASSERT(data);
-        SkASSERT(rowBytes > 0);
-        SkASSERT(mappedBuffer);
-        SkASSERT(mappedBuffer->isMapped());
-        fPlanes.emplace_back(data, rowBytes, std::move(mappedBuffer));
-    }
-
-    struct Plane {
-        Plane(const void* data, size_t rowBytes, sk_sp<GrGpuBuffer> buffer)
-                : fData(data), fRowBytes(rowBytes), fMappedBuffer(std::move(buffer)) {}
-        const void* fData;
-        size_t fRowBytes;
-        // If this is null then fData is heap alloc and must be delete[]ed as const char[].
-        sk_sp<GrGpuBuffer> fMappedBuffer;
-    };
-    SkSTArray<3, Plane> fPlanes;
-    uint32_t fInboxID;
-};
-
-void GrRenderTargetContext::asyncReadPixels(const SkIRect& rect, SkColorType colorType,
-                                            ReadPixelsCallback callback,
-                                            ReadPixelsContext context) {
-    SkASSERT(rect.fLeft >= 0 && rect.fRight <= this->width());
-    SkASSERT(rect.fTop >= 0 && rect.fBottom <= this->height());
-
-    if (this->asSurfaceProxy()->isProtected() == GrProtected::kYes) {
-        callback(context, nullptr);
-        return;
-    }
-
-    auto directContext = fContext->priv().asDirectContext();
-    SkASSERT(directContext);
-    auto mappedBufferManager = directContext->priv().clientMappedBufferManager();
-
-    auto transferResult = this->transferPixels(SkColorTypeToGrColorType(colorType), rect);
-
-    if (!transferResult.fTransferBuffer) {
-        auto ii = SkImageInfo::Make(rect.size(), colorType,
-                                    this->colorInfo().alphaType(),
-                                    this->colorInfo().refColorSpace());
-        auto result = std::make_unique<AsyncReadResult>(0);
-        std::unique_ptr<char[]> data(new char[ii.computeMinByteSize()]);
-        SkPixmap pm(ii, data.get(), ii.minRowBytes());
-        result->addCpuPlane(std::move(data), pm.rowBytes());
-
-        if (!this->readPixels(ii, pm.writable_addr(), pm.rowBytes(), {rect.fLeft, rect.fTop})) {
-            callback(context, nullptr);
-            return;
-        }
-        callback(context, std::move(result));
-        return;
-    }
-
-    struct FinishContext {
-        ReadPixelsCallback* fClientCallback;
-        ReadPixelsContext fClientContext;
-        SkISize fSize;
-        SkColorType fColorType;
-        GrClientMappedBufferManager* fMappedBufferManager;
-        PixelTransferResult fTransferResult;
-    };
-    // Assumption is that the caller would like to flush. We could take a parameter or require an
-    // explicit flush from the caller. We'd have to have a way to defer attaching the finish
-    // callback to GrGpu until after the next flush that flushes our op list, though.
-    auto* finishContext = new FinishContext{callback,
-                                            context,
-                                            rect.size(),
-                                            colorType,
-                                            mappedBufferManager,
-                                            std::move(transferResult)};
-    auto finishCallback = [](GrGpuFinishedContext c) {
-        const auto* context = reinterpret_cast<const FinishContext*>(c);
-        auto result = std::make_unique<AsyncReadResult>(context->fMappedBufferManager->inboxID());
-        size_t rowBytes = context->fSize.width() * SkColorTypeBytesPerPixel(context->fColorType);
-        if (!result->addTransferResult(context->fTransferResult, context->fSize, rowBytes,
-                                       context->fMappedBufferManager)) {
-            result.reset();
-        }
-        (*context->fClientCallback)(context->fClientContext, std::move(result));
-        delete context;
-    };
-    GrFlushInfo flushInfo;
-    flushInfo.fFinishedContext = finishContext;
-    flushInfo.fFinishedProc = finishCallback;
-    this->flush(SkSurface::BackendSurfaceAccess::kNoAccess, flushInfo, nullptr);
-}
-
-void GrRenderTargetContext::asyncRescaleAndReadPixelsYUV420(SkYUVColorSpace yuvColorSpace,
-                                                            sk_sp<SkColorSpace> dstColorSpace,
-                                                            const SkIRect& srcRect,
-                                                            SkISize dstSize,
-                                                            RescaleGamma rescaleGamma,
-                                                            SkFilterQuality rescaleQuality,
-                                                            ReadPixelsCallback callback,
-                                                            ReadPixelsContext context) {
-    SkASSERT(srcRect.fLeft >= 0 && srcRect.fRight <= this->width());
-    SkASSERT(srcRect.fTop >= 0 && srcRect.fBottom <= this->height());
-    SkASSERT(!dstSize.isZero());
-    SkASSERT((dstSize.width() % 2 == 0) && (dstSize.height() % 2 == 0));
-
-    auto direct = fContext->priv().asDirectContext();
-    if (!direct) {
-        callback(context, nullptr);
-        return;
-    }
-    if (this->asRenderTargetProxy()->wrapsVkSecondaryCB()) {
-        callback(context, nullptr);
-        return;
-    }
-    if (this->asRenderTargetProxy()->framebufferOnly()) {
-        callback(context, nullptr);
-        return;
-    }
-    if (this->asSurfaceProxy()->isProtected() == GrProtected::kYes) {
-        callback(context, nullptr);
-        return;
-    }
-    int x = srcRect.fLeft;
-    int y = srcRect.fTop;
-    bool needsRescale = srcRect.size() != dstSize;
-    GrSurfaceProxyView srcView;
-    if (needsRescale) {
-        // We assume the caller wants kPremul. There is no way to indicate a preference.
-        auto info = SkImageInfo::Make(dstSize, kRGBA_8888_SkColorType, kPremul_SkAlphaType,
-                                      dstColorSpace);
-        // TODO: Incorporate the YUV conversion into last pass of rescaling.
-        auto tempRTC = this->rescale(info, kTopLeft_GrSurfaceOrigin, srcRect, rescaleGamma,
-                                     rescaleQuality);
-        if (!tempRTC) {
-            callback(context, nullptr);
-            return;
-        }
-        SkASSERT(SkColorSpace::Equals(tempRTC->colorInfo().colorSpace(), info.colorSpace()));
-        SkASSERT(tempRTC->origin() == kTopLeft_GrSurfaceOrigin);
-        x = y = 0;
-        srcView = tempRTC->readSurfaceView();
-    } else {
-        srcView = this->readSurfaceView();
-        if (!srcView.asTextureProxy()) {
-            srcView = GrSurfaceProxyView::Copy(fContext, std::move(srcView), GrMipMapped::kNo,
-                                               srcRect, SkBackingFit::kApprox, SkBudgeted::kYes);
-            if (!srcView) {
-                // If we can't get a texture copy of the contents then give up.
-                callback(context, nullptr);
-                return;
-            }
-            SkASSERT(srcView.asTextureProxy());
-            x = y = 0;
-        }
-        // We assume the caller wants kPremul. There is no way to indicate a preference.
-        sk_sp<GrColorSpaceXform> xform = GrColorSpaceXform::Make(
-                this->colorInfo().colorSpace(), this->colorInfo().alphaType(), dstColorSpace.get(),
-                kPremul_SkAlphaType);
-        if (xform) {
-            SkRect srcRectToDraw = SkRect::MakeXYWH(x, y, srcRect.width(), srcRect.height());
-            auto tempRTC = GrRenderTargetContext::Make(
-                    direct, this->colorInfo().colorType(), dstColorSpace, SkBackingFit::kApprox,
-                    dstSize, 1, GrMipMapped::kNo, GrProtected::kNo, kTopLeft_GrSurfaceOrigin);
-            if (!tempRTC) {
-                callback(context, nullptr);
-                return;
-            }
-            tempRTC->drawTexture(nullptr, std::move(srcView), this->colorInfo().alphaType(),
-                                 GrSamplerState::Filter::kNearest, SkBlendMode::kSrc,
-                                 SK_PMColor4fWHITE, srcRectToDraw, SkRect::Make(srcRect.size()),
-                                 GrAA::kNo, GrQuadAAFlags::kNone, SkCanvas::kFast_SrcRectConstraint,
-                                 SkMatrix::I(), std::move(xform));
-            srcView = tempRTC->readSurfaceView();
-            SkASSERT(srcView.asTextureProxy());
-            x = y = 0;
-        }
-    }
-
-    auto yRTC = GrRenderTargetContext::MakeWithFallback(
-            direct, GrColorType::kAlpha_8, dstColorSpace, SkBackingFit::kApprox, dstSize, 1,
-            GrMipMapped::kNo, GrProtected::kNo, kTopLeft_GrSurfaceOrigin);
-    int halfW = dstSize.width()/2;
-    int halfH = dstSize.height()/2;
-    auto uRTC = GrRenderTargetContext::MakeWithFallback(
-            direct, GrColorType::kAlpha_8, dstColorSpace, SkBackingFit::kApprox, {halfW, halfH}, 1,
-            GrMipMapped::kNo, GrProtected::kNo, kTopLeft_GrSurfaceOrigin);
-    auto vRTC = GrRenderTargetContext::MakeWithFallback(
-            direct, GrColorType::kAlpha_8, dstColorSpace, SkBackingFit::kApprox, {halfW, halfH}, 1,
-            GrMipMapped::kNo, GrProtected::kNo, kTopLeft_GrSurfaceOrigin);
-    if (!yRTC || !uRTC || !vRTC) {
-        callback(context, nullptr);
-        return;
-    }
-
-    float baseM[20];
-    SkColorMatrix_RGB2YUV(yuvColorSpace, baseM);
-
-    // TODO: Use one transfer buffer for all three planes to reduce map/unmap cost?
-
-    auto texMatrix = SkMatrix::Translate(x, y);
-
-    SkRect dstRectY = SkRect::Make(dstSize);
-    SkRect dstRectUV = SkRect::MakeWH(halfW, halfH);
-
-    bool doSynchronousRead = !this->caps()->transferFromSurfaceToBufferSupport();
-    PixelTransferResult yTransfer, uTransfer, vTransfer;
-
-    // This matrix generates (r,g,b,a) = (0, 0, 0, y)
-    float yM[20];
-    std::fill_n(yM, 15, 0.f);
-    std::copy_n(baseM + 0, 5, yM + 15);
-    GrPaint yPaint;
-    auto yTexFP = GrTextureEffect::Make(srcView, this->colorInfo().alphaType(), texMatrix);
-    auto yColFP = GrColorMatrixFragmentProcessor::Make(std::move(yTexFP), yM,
-                                                       /*unpremulInput=*/false,
-                                                       /*clampRGBOutput=*/true,
-                                                       /*premulOutput=*/false);
-    yPaint.addColorFragmentProcessor(std::move(yColFP));
-    yPaint.setPorterDuffXPFactory(SkBlendMode::kSrc);
-    yRTC->fillRectToRect(nullptr, std::move(yPaint), GrAA::kNo, SkMatrix::I(),
-                         dstRectY, dstRectY);
-    if (!doSynchronousRead) {
-        yTransfer = yRTC->transferPixels(GrColorType::kAlpha_8,
-                                         SkIRect::MakeWH(yRTC->width(), yRTC->height()));
-        if (!yTransfer.fTransferBuffer) {
-            callback(context, nullptr);
-            return;
-        }
-    }
-
-    texMatrix.preScale(2.f, 2.f);
-    // This matrix generates (r,g,b,a) = (0, 0, 0, u)
-    float uM[20];
-    std::fill_n(uM, 15, 0.f);
-    std::copy_n(baseM + 5, 5, uM + 15);
-    GrPaint uPaint;
-    auto uTexFP = GrTextureEffect::Make(srcView, this->colorInfo().alphaType(), texMatrix,
-                                        GrSamplerState::Filter::kBilerp);
-    auto uColFP = GrColorMatrixFragmentProcessor::Make(std::move(uTexFP), uM,
-                                                       /*unpremulInput=*/false,
-                                                       /*clampRGBOutput=*/true,
-                                                       /*premulOutput=*/false);
-    uPaint.addColorFragmentProcessor(std::move(uColFP));
-    uPaint.setPorterDuffXPFactory(SkBlendMode::kSrc);
-    uRTC->fillRectToRect(nullptr, std::move(uPaint), GrAA::kNo, SkMatrix::I(),
-                         dstRectUV, dstRectUV);
-    if (!doSynchronousRead) {
-        uTransfer = uRTC->transferPixels(GrColorType::kAlpha_8,
-                                         SkIRect::MakeWH(uRTC->width(), uRTC->height()));
-        if (!uTransfer.fTransferBuffer) {
-            callback(context, nullptr);
-            return;
-        }
-    }
-
-    // This matrix generates (r,g,b,a) = (0, 0, 0, v)
-    float vM[20];
-    std::fill_n(vM, 15, 0.f);
-    std::copy_n(baseM + 10, 5, vM + 15);
-    GrPaint vPaint;
-    auto vTexFP = GrTextureEffect::Make(std::move(srcView), this->colorInfo().alphaType(),
-                                        texMatrix, GrSamplerState::Filter::kBilerp);
-    auto vColFP = GrColorMatrixFragmentProcessor::Make(std::move(vTexFP), vM,
-                                                       /*unpremulInput=*/false,
-                                                       /*clampRGBOutput=*/true,
-                                                       /*premulOutput=*/false);
-    vPaint.addColorFragmentProcessor(std::move(vColFP));
-    vPaint.setPorterDuffXPFactory(SkBlendMode::kSrc);
-    vRTC->fillRectToRect(nullptr, std::move(vPaint), GrAA::kNo, SkMatrix::I(),
-                         dstRectUV, dstRectUV);
-    if (!doSynchronousRead) {
-        vTransfer = vRTC->transferPixels(GrColorType::kAlpha_8,
-                                         SkIRect::MakeWH(vRTC->width(), vRTC->height()));
-        if (!vTransfer.fTransferBuffer) {
-            callback(context, nullptr);
-            return;
-        }
-    }
-
-    if (doSynchronousRead) {
-        GrImageInfo yInfo(GrColorType::kAlpha_8, kPremul_SkAlphaType, nullptr, dstSize);
-        GrImageInfo uvInfo = yInfo.makeWH(halfW, halfH);
-        size_t yRB  = yInfo.minRowBytes();
-        size_t uvRB = uvInfo.minRowBytes();
-        std::unique_ptr<char[]> y(new char[yRB * yInfo.height()]);
-        std::unique_ptr<char[]> u(new char[uvRB*uvInfo.height()]);
-        std::unique_ptr<char[]> v(new char[uvRB*uvInfo.height()]);
-        if (!yRTC->readPixels(yInfo,  y.get(), yRB,  {0, 0}, direct) ||
-            !uRTC->readPixels(uvInfo, u.get(), uvRB, {0, 0}, direct) ||
-            !vRTC->readPixels(uvInfo, v.get(), uvRB, {0, 0}, direct)) {
-            callback(context, nullptr);
-            return;
-        }
-        auto result = std::make_unique<AsyncReadResult>(direct->priv().contextID());
-        result->addCpuPlane(std::move(y), yRB );
-        result->addCpuPlane(std::move(u), uvRB);
-        result->addCpuPlane(std::move(v), uvRB);
-        callback(context, std::move(result));
-        return;
-    }
-
-    struct FinishContext {
-        ReadPixelsCallback* fClientCallback;
-        ReadPixelsContext fClientContext;
-        GrClientMappedBufferManager* fMappedBufferManager;
-        SkISize fSize;
-        PixelTransferResult fYTransfer;
-        PixelTransferResult fUTransfer;
-        PixelTransferResult fVTransfer;
-    };
-    // Assumption is that the caller would like to flush. We could take a parameter or require an
-    // explicit flush from the caller. We'd have to have a way to defer attaching the finish
-    // callback to GrGpu until after the next flush that flushes our op list, though.
-    auto* finishContext = new FinishContext{callback,
-                                            context,
-                                            direct->priv().clientMappedBufferManager(),
-                                            dstSize,
-                                            std::move(yTransfer),
-                                            std::move(uTransfer),
-                                            std::move(vTransfer)};
-    auto finishCallback = [](GrGpuFinishedContext c) {
-        const auto* context = reinterpret_cast<const FinishContext*>(c);
-        auto result = std::make_unique<AsyncReadResult>(context->fMappedBufferManager->inboxID());
-        auto manager = context->fMappedBufferManager;
-        size_t rowBytes = SkToSizeT(context->fSize.width());
-        if (!result->addTransferResult(context->fYTransfer, context->fSize, rowBytes, manager)) {
-            (*context->fClientCallback)(context->fClientContext, nullptr);
-            delete context;
-            return;
-        }
-        rowBytes /= 2;
-        SkISize uvSize = {context->fSize.width()/2, context->fSize.height()/2};
-        if (!result->addTransferResult(context->fUTransfer, uvSize, rowBytes, manager)) {
-            (*context->fClientCallback)(context->fClientContext, nullptr);
-            delete context;
-            return;
-        }
-        if (!result->addTransferResult(context->fVTransfer, uvSize, rowBytes, manager)) {
-            (*context->fClientCallback)(context->fClientContext, nullptr);
-            delete context;
-            return;
-        }
-        (*context->fClientCallback)(context->fClientContext, std::move(result));
-        delete context;
-    };
-    GrFlushInfo flushInfo;
-    flushInfo.fFinishedContext = finishContext;
-    flushInfo.fFinishedProc = finishCallback;
-    this->flush(SkSurface::BackendSurfaceAccess::kNoAccess, flushInfo, nullptr);
-}
-
-GrSemaphoresSubmitted GrRenderTargetContext::flush(SkSurface::BackendSurfaceAccess access,
-                                                   const GrFlushInfo& info,
-                                                   const GrBackendSurfaceMutableState* newState) {
-    ASSERT_SINGLE_OWNER
-    if (fContext->priv().abandoned()) {
-        if (info.fSubmittedProc) {
-            info.fSubmittedProc(info.fSubmittedContext, false);
-        }
-        if (info.fFinishedProc) {
-            info.fFinishedProc(info.fFinishedContext);
-        }
-        return GrSemaphoresSubmitted::kNo;
-    }
-    SkDEBUGCODE(this->validate();)
-    GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "flush", fContext);
-
-    return this->drawingManager()->flushSurface(this->asSurfaceProxy(), access, info, newState);
-}
-
 bool GrRenderTargetContext::waitOnSemaphores(int numSemaphores,
                                              const GrBackendSemaphore waitSemaphores[]) {
     ASSERT_SINGLE_OWNER
diff --git a/src/gpu/GrRenderTargetContext.h b/src/gpu/GrRenderTargetContext.h
index 54dfedb..9d77829 100644
--- a/src/gpu/GrRenderTargetContext.h
+++ b/src/gpu/GrRenderTargetContext.h
@@ -524,31 +524,6 @@
      */
     void drawDrawable(std::unique_ptr<SkDrawable::GpuDrawHandler>, const SkRect& bounds);
 
-    using ReadPixelsCallback = SkSurface::ReadPixelsCallback;
-    using ReadPixelsContext = SkSurface::ReadPixelsContext;
-    using RescaleGamma = SkSurface::RescaleGamma;
-
-    // GPU implementation for SkSurface::asyncRescaleAndReadPixels.
-    void asyncRescaleAndReadPixels(const SkImageInfo& info, const SkIRect& srcRect,
-                                   RescaleGamma rescaleGamma, SkFilterQuality rescaleQuality,
-                                   ReadPixelsCallback callback, ReadPixelsContext context);
-    // GPU implementation for SkSurface::asyncRescaleAndReadPixelsYUV420.
-    void asyncRescaleAndReadPixelsYUV420(SkYUVColorSpace yuvColorSpace,
-                                         sk_sp<SkColorSpace> dstColorSpace,
-                                         const SkIRect& srcRect,
-                                         SkISize dstSize,
-                                         RescaleGamma rescaleGamma,
-                                         SkFilterQuality rescaleQuality,
-                                         ReadPixelsCallback callback,
-                                         ReadPixelsContext context);
-
-    /**
-     * After this returns any pending surface IO will be issued to the backend 3D API and
-     * if the surface has MSAA it will be resolved.
-     */
-    GrSemaphoresSubmitted flush(SkSurface::BackendSurfaceAccess access, const GrFlushInfo&,
-                                const GrBackendSurfaceMutableState*);
-
     /**
      *  The next time this GrRenderTargetContext is flushed, the gpu will wait on the passed in
      *  semaphores before executing any commands.
@@ -694,12 +669,6 @@
     bool SK_WARN_UNUSED_RESULT setupDstProxyView(const GrOp& op,
                                                  GrXferProcessor::DstProxyView* result);
 
-    class AsyncReadResult;
-
-    // The async read step of asyncRescaleAndReadPixels()
-    void asyncReadPixels(const SkIRect& rect, SkColorType colorType, ReadPixelsCallback callback,
-                         ReadPixelsContext context);
-
     GrOpsTask* getOpsTask();
 
     SkGlyphRunListPainter* glyphPainter() { return &fGlyphPainter; }
diff --git a/src/gpu/GrSurfaceContext.cpp b/src/gpu/GrSurfaceContext.cpp
index e3b1e28..ff0f930 100644
--- a/src/gpu/GrSurfaceContext.cpp
+++ b/src/gpu/GrSurfaceContext.cpp
@@ -9,6 +9,7 @@
 
 #include "include/private/GrRecordingContext.h"
 #include "src/core/SkAutoPixmapStorage.h"
+#include "src/core/SkYUVMath.h"
 #include "src/gpu/GrAuditTrail.h"
 #include "src/gpu/GrContextPriv.h"
 #include "src/gpu/GrDataUtils.h"
@@ -22,6 +23,7 @@
 #include "src/gpu/GrSurfacePriv.h"
 #include "src/gpu/SkGr.h"
 #include "src/gpu/effects/GrBicubicEffect.h"
+#include "src/gpu/effects/generated/GrColorMatrixFragmentProcessor.h"
 
 #define ASSERT_SINGLE_OWNER        GR_ASSERT_SINGLE_OWNER(this->singleOwner())
 #define RETURN_FALSE_IF_ABANDONED  if (this->fContext->priv().abandoned()) { return false; }
@@ -474,6 +476,511 @@
                                                 srcColorType, src, rowBytes);
 }
 
+void GrSurfaceContext::asyncRescaleAndReadPixels(const SkImageInfo& info,
+                                                 const SkIRect& srcRect,
+                                                 RescaleGamma rescaleGamma,
+                                                 SkFilterQuality rescaleQuality,
+                                                 ReadPixelsCallback callback,
+                                                 ReadPixelsContext context) {
+    auto direct = fContext->priv().asDirectContext();
+
+    // We implement this by rendering and we don't currently support rendering kUnpremul.
+    if (info.alphaType() == kUnpremul_SkAlphaType) {
+        callback(context, nullptr);
+        return;
+    }
+    if (!direct) {
+        callback(context, nullptr);
+        return;
+    }
+    auto rt = this->asRenderTargetProxy();
+    if (rt && rt->wrapsVkSecondaryCB()) {
+        callback(context, nullptr);
+        return;
+    }
+    if (rt && rt->framebufferOnly()) {
+        callback(context, nullptr);
+        return;
+    }
+    auto dstCT = SkColorTypeToGrColorType(info.colorType());
+    if (dstCT == GrColorType::kUnknown) {
+        callback(context, nullptr);
+        return;
+    }
+    bool needsRescale = srcRect.width() != info.width() || srcRect.height() != info.height();
+    auto colorTypeOfFinalContext = this->colorInfo().colorType();
+    auto backendFormatOfFinalContext = this->asSurfaceProxy()->backendFormat();
+    if (needsRescale) {
+        colorTypeOfFinalContext = dstCT;
+        backendFormatOfFinalContext =
+                this->caps()->getDefaultBackendFormat(dstCT, GrRenderable::kYes);
+    }
+    auto readInfo = this->caps()->supportedReadPixelsColorType(colorTypeOfFinalContext,
+                                                               backendFormatOfFinalContext, dstCT);
+    // Fail if we can't read from the source surface's color type.
+    if (readInfo.fColorType == GrColorType::kUnknown) {
+        callback(context, nullptr);
+        return;
+    }
+    // Fail if read color type does not have all of dstCT's color channels and those missing color
+    // channels are in the src.
+    uint32_t dstChannels = GrColorTypeChannelFlags(dstCT);
+    uint32_t legalReadChannels = GrColorTypeChannelFlags(readInfo.fColorType);
+    uint32_t srcChannels = GrColorTypeChannelFlags(this->colorInfo().colorType());
+    if ((~legalReadChannels & dstChannels) & srcChannels) {
+        callback(context, nullptr);
+        return;
+    }
+
+    std::unique_ptr<GrRenderTargetContext> tempRTC;
+    int x = srcRect.fLeft;
+    int y = srcRect.fTop;
+    if (needsRescale) {
+        tempRTC = this->rescale(info, kTopLeft_GrSurfaceOrigin, srcRect, rescaleGamma,
+                                rescaleQuality);
+        if (!tempRTC) {
+            callback(context, nullptr);
+            return;
+        }
+        SkASSERT(SkColorSpace::Equals(tempRTC->colorInfo().colorSpace(), info.colorSpace()));
+        SkASSERT(tempRTC->origin() == kTopLeft_GrSurfaceOrigin);
+        x = y = 0;
+    } else {
+        sk_sp<GrColorSpaceXform> xform = GrColorSpaceXform::Make(this->colorInfo().colorSpace(),
+                                                                 this->colorInfo().alphaType(),
+                                                                 info.colorSpace(),
+                                                                 info.alphaType());
+        // Insert a draw to a temporary surface if we need to do a y-flip or color space conversion.
+        if (this->origin() == kBottomLeft_GrSurfaceOrigin || xform) {
+            GrSurfaceProxyView texProxyView = this->readSurfaceView();
+            SkRect srcRectToDraw = SkRect::Make(srcRect);
+            // If the src is not texturable first try to make a copy to a texture.
+            if (!texProxyView.asTextureProxy()) {
+                texProxyView =
+                        GrSurfaceProxyView::Copy(fContext, texProxyView, GrMipMapped::kNo, srcRect,
+                                                 SkBackingFit::kApprox, SkBudgeted::kNo);
+                if (!texProxyView) {
+                    callback(context, nullptr);
+                    return;
+                }
+                SkASSERT(texProxyView.asTextureProxy());
+                srcRectToDraw = SkRect::MakeWH(srcRect.width(), srcRect.height());
+            }
+            tempRTC = GrRenderTargetContext::Make(direct, this->colorInfo().colorType(),
+                                                  info.refColorSpace(), SkBackingFit::kApprox,
+                                                  srcRect.size(), 1, GrMipMapped::kNo,
+                                                  GrProtected::kNo, kTopLeft_GrSurfaceOrigin);
+            if (!tempRTC) {
+                callback(context, nullptr);
+                return;
+            }
+            tempRTC->drawTexture(nullptr, std::move(texProxyView), this->colorInfo().alphaType(),
+                                 GrSamplerState::Filter::kNearest, SkBlendMode::kSrc,
+                                 SK_PMColor4fWHITE, srcRectToDraw,
+                                 SkRect::MakeWH(srcRect.width(), srcRect.height()), GrAA::kNo,
+                                 GrQuadAAFlags::kNone, SkCanvas::kFast_SrcRectConstraint,
+                                 SkMatrix::I(), std::move(xform));
+            x = y = 0;
+        }
+    }
+    auto rtc = tempRTC ? tempRTC.get() : this;
+    return rtc->asyncReadPixels(SkIRect::MakeXYWH(x, y, info.width(), info.height()),
+                                info.colorType(), callback, context);
+}
+
+class GrSurfaceContext::AsyncReadResult : public SkImage::AsyncReadResult {
+public:
+    AsyncReadResult(uint32_t inboxID) : fInboxID(inboxID) {}
+    ~AsyncReadResult() override {
+        for (int i = 0; i < fPlanes.count(); ++i) {
+            if (!fPlanes[i].fMappedBuffer) {
+                delete[] static_cast<const char*>(fPlanes[i].fData);
+            } else {
+                GrClientMappedBufferManager::BufferFinishedMessageBus::Post(
+                        {std::move(fPlanes[i].fMappedBuffer), fInboxID});
+            }
+        }
+    }
+
+    int count() const override { return fPlanes.count(); }
+    const void* data(int i) const override { return fPlanes[i].fData; }
+    size_t rowBytes(int i) const override { return fPlanes[i].fRowBytes; }
+
+    bool addTransferResult(const PixelTransferResult& result,
+                           SkISize dimensions,
+                           size_t rowBytes,
+                           GrClientMappedBufferManager* manager) {
+        SkASSERT(!result.fTransferBuffer->isMapped());
+        const void* mappedData = result.fTransferBuffer->map();
+        if (!mappedData) {
+            return false;
+        }
+        if (result.fPixelConverter) {
+            std::unique_ptr<char[]> convertedData(new char[rowBytes * dimensions.height()]);
+            result.fPixelConverter(convertedData.get(), mappedData);
+            this->addCpuPlane(std::move(convertedData), rowBytes);
+            result.fTransferBuffer->unmap();
+        } else {
+            manager->insert(result.fTransferBuffer);
+            this->addMappedPlane(mappedData, rowBytes, std::move(result.fTransferBuffer));
+        }
+        return true;
+    }
+
+    void addCpuPlane(std::unique_ptr<const char[]> data, size_t rowBytes) {
+        SkASSERT(data);
+        SkASSERT(rowBytes > 0);
+        fPlanes.emplace_back(data.release(), rowBytes, nullptr);
+    }
+
+private:
+    void addMappedPlane(const void* data, size_t rowBytes, sk_sp<GrGpuBuffer> mappedBuffer) {
+        SkASSERT(data);
+        SkASSERT(rowBytes > 0);
+        SkASSERT(mappedBuffer);
+        SkASSERT(mappedBuffer->isMapped());
+        fPlanes.emplace_back(data, rowBytes, std::move(mappedBuffer));
+    }
+
+    struct Plane {
+        Plane(const void* data, size_t rowBytes, sk_sp<GrGpuBuffer> buffer)
+                : fData(data), fRowBytes(rowBytes), fMappedBuffer(std::move(buffer)) {}
+        const void* fData;
+        size_t fRowBytes;
+        // If this is null then fData is heap alloc and must be delete[]ed as const char[].
+        sk_sp<GrGpuBuffer> fMappedBuffer;
+    };
+    SkSTArray<3, Plane> fPlanes;
+    uint32_t fInboxID;
+};
+
+void GrSurfaceContext::asyncReadPixels(const SkIRect& rect,
+                                       SkColorType colorType,
+                                       ReadPixelsCallback callback,
+                                       ReadPixelsContext context) {
+    SkASSERT(rect.fLeft >= 0 && rect.fRight <= this->width());
+    SkASSERT(rect.fTop >= 0 && rect.fBottom <= this->height());
+
+    if (this->asSurfaceProxy()->isProtected() == GrProtected::kYes) {
+        callback(context, nullptr);
+        return;
+    }
+
+    auto directContext = fContext->priv().asDirectContext();
+    SkASSERT(directContext);
+    auto mappedBufferManager = directContext->priv().clientMappedBufferManager();
+
+    auto transferResult = this->transferPixels(SkColorTypeToGrColorType(colorType), rect);
+
+    if (!transferResult.fTransferBuffer) {
+        auto ii = SkImageInfo::Make(rect.size(), colorType, this->colorInfo().alphaType(),
+                                    this->colorInfo().refColorSpace());
+        auto result = std::make_unique<AsyncReadResult>(0);
+        std::unique_ptr<char[]> data(new char[ii.computeMinByteSize()]);
+        SkPixmap pm(ii, data.get(), ii.minRowBytes());
+        result->addCpuPlane(std::move(data), pm.rowBytes());
+
+        if (!this->readPixels(ii, pm.writable_addr(), pm.rowBytes(), {rect.fLeft, rect.fTop})) {
+            callback(context, nullptr);
+            return;
+        }
+        callback(context, std::move(result));
+        return;
+    }
+
+    struct FinishContext {
+        ReadPixelsCallback* fClientCallback;
+        ReadPixelsContext fClientContext;
+        SkISize fSize;
+        SkColorType fColorType;
+        GrClientMappedBufferManager* fMappedBufferManager;
+        PixelTransferResult fTransferResult;
+    };
+    // Assumption is that the caller would like to flush. We could take a parameter or require an
+    // explicit flush from the caller. We'd have to have a way to defer attaching the finish
+    // callback to GrGpu until after the next flush that flushes our op list, though.
+    auto* finishContext = new FinishContext{callback,
+                                            context,
+                                            rect.size(),
+                                            colorType,
+                                            mappedBufferManager,
+                                            std::move(transferResult)};
+    auto finishCallback = [](GrGpuFinishedContext c) {
+        const auto* context = reinterpret_cast<const FinishContext*>(c);
+        auto result = std::make_unique<AsyncReadResult>(context->fMappedBufferManager->inboxID());
+        size_t rowBytes = context->fSize.width() * SkColorTypeBytesPerPixel(context->fColorType);
+        if (!result->addTransferResult(context->fTransferResult, context->fSize, rowBytes,
+                                       context->fMappedBufferManager)) {
+            result.reset();
+        }
+        (*context->fClientCallback)(context->fClientContext, std::move(result));
+        delete context;
+    };
+    GrFlushInfo flushInfo;
+    flushInfo.fFinishedContext = finishContext;
+    flushInfo.fFinishedProc = finishCallback;
+    this->flush(SkSurface::BackendSurfaceAccess::kNoAccess, flushInfo, nullptr);
+}
+
+void GrSurfaceContext::asyncRescaleAndReadPixelsYUV420(SkYUVColorSpace yuvColorSpace,
+                                                       sk_sp<SkColorSpace> dstColorSpace,
+                                                       const SkIRect& srcRect,
+                                                       SkISize dstSize,
+                                                       RescaleGamma rescaleGamma,
+                                                       SkFilterQuality rescaleQuality,
+                                                       ReadPixelsCallback callback,
+                                                       ReadPixelsContext context) {
+    SkASSERT(srcRect.fLeft >= 0 && srcRect.fRight <= this->width());
+    SkASSERT(srcRect.fTop >= 0 && srcRect.fBottom <= this->height());
+    SkASSERT(!dstSize.isZero());
+    SkASSERT((dstSize.width() % 2 == 0) && (dstSize.height() % 2 == 0));
+
+    auto direct = fContext->priv().asDirectContext();
+    if (!direct) {
+        callback(context, nullptr);
+        return;
+    }
+    auto rt = this->asRenderTargetProxy();
+    if (rt && rt->wrapsVkSecondaryCB()) {
+        callback(context, nullptr);
+        return;
+    }
+    if (rt && rt->framebufferOnly()) {
+        callback(context, nullptr);
+        return;
+    }
+    if (this->asSurfaceProxy()->isProtected() == GrProtected::kYes) {
+        callback(context, nullptr);
+        return;
+    }
+    int x = srcRect.fLeft;
+    int y = srcRect.fTop;
+    bool needsRescale = srcRect.size() != dstSize;
+    GrSurfaceProxyView srcView;
+    if (needsRescale) {
+        // We assume the caller wants kPremul. There is no way to indicate a preference.
+        auto info = SkImageInfo::Make(dstSize, kRGBA_8888_SkColorType, kPremul_SkAlphaType,
+                                      dstColorSpace);
+        // TODO: Incorporate the YUV conversion into last pass of rescaling.
+        auto tempRTC = this->rescale(info, kTopLeft_GrSurfaceOrigin, srcRect, rescaleGamma,
+                                     rescaleQuality);
+        if (!tempRTC) {
+            callback(context, nullptr);
+            return;
+        }
+        SkASSERT(SkColorSpace::Equals(tempRTC->colorInfo().colorSpace(), info.colorSpace()));
+        SkASSERT(tempRTC->origin() == kTopLeft_GrSurfaceOrigin);
+        x = y = 0;
+        srcView = tempRTC->readSurfaceView();
+    } else {
+        srcView = this->readSurfaceView();
+        if (!srcView.asTextureProxy()) {
+            srcView = GrSurfaceProxyView::Copy(fContext, std::move(srcView), GrMipMapped::kNo,
+                                               srcRect, SkBackingFit::kApprox, SkBudgeted::kYes);
+            if (!srcView) {
+                // If we can't get a texture copy of the contents then give up.
+                callback(context, nullptr);
+                return;
+            }
+            SkASSERT(srcView.asTextureProxy());
+            x = y = 0;
+        }
+        // We assume the caller wants kPremul. There is no way to indicate a preference.
+        sk_sp<GrColorSpaceXform> xform = GrColorSpaceXform::Make(
+                this->colorInfo().colorSpace(), this->colorInfo().alphaType(), dstColorSpace.get(),
+                kPremul_SkAlphaType);
+        if (xform) {
+            SkRect srcRectToDraw = SkRect::MakeXYWH(x, y, srcRect.width(), srcRect.height());
+            auto tempRTC = GrRenderTargetContext::Make(
+                    direct, this->colorInfo().colorType(), dstColorSpace, SkBackingFit::kApprox,
+                    dstSize, 1, GrMipMapped::kNo, GrProtected::kNo, kTopLeft_GrSurfaceOrigin);
+            if (!tempRTC) {
+                callback(context, nullptr);
+                return;
+            }
+            tempRTC->drawTexture(nullptr, std::move(srcView), this->colorInfo().alphaType(),
+                                 GrSamplerState::Filter::kNearest, SkBlendMode::kSrc,
+                                 SK_PMColor4fWHITE, srcRectToDraw, SkRect::Make(srcRect.size()),
+                                 GrAA::kNo, GrQuadAAFlags::kNone, SkCanvas::kFast_SrcRectConstraint,
+                                 SkMatrix::I(), std::move(xform));
+            srcView = tempRTC->readSurfaceView();
+            SkASSERT(srcView.asTextureProxy());
+            x = y = 0;
+        }
+    }
+
+    auto yRTC = GrRenderTargetContext::MakeWithFallback(
+            direct, GrColorType::kAlpha_8, dstColorSpace, SkBackingFit::kApprox, dstSize, 1,
+            GrMipMapped::kNo, GrProtected::kNo, kTopLeft_GrSurfaceOrigin);
+    int halfW = dstSize.width() /2;
+    int halfH = dstSize.height()/2;
+    auto uRTC = GrRenderTargetContext::MakeWithFallback(
+            direct, GrColorType::kAlpha_8, dstColorSpace, SkBackingFit::kApprox, {halfW, halfH}, 1,
+            GrMipMapped::kNo, GrProtected::kNo, kTopLeft_GrSurfaceOrigin);
+    auto vRTC = GrRenderTargetContext::MakeWithFallback(
+            direct, GrColorType::kAlpha_8, dstColorSpace, SkBackingFit::kApprox, {halfW, halfH}, 1,
+            GrMipMapped::kNo, GrProtected::kNo, kTopLeft_GrSurfaceOrigin);
+    if (!yRTC || !uRTC || !vRTC) {
+        callback(context, nullptr);
+        return;
+    }
+
+    float baseM[20];
+    SkColorMatrix_RGB2YUV(yuvColorSpace, baseM);
+
+    // TODO: Use one transfer buffer for all three planes to reduce map/unmap cost?
+
+    auto texMatrix = SkMatrix::Translate(x, y);
+
+    SkRect dstRectY = SkRect::Make(dstSize);
+    SkRect dstRectUV = SkRect::MakeWH(halfW, halfH);
+
+    bool doSynchronousRead = !this->caps()->transferFromSurfaceToBufferSupport();
+    PixelTransferResult yTransfer, uTransfer, vTransfer;
+
+    // This matrix generates (r,g,b,a) = (0, 0, 0, y)
+    float yM[20];
+    std::fill_n(yM, 15, 0.f);
+    std::copy_n(baseM + 0, 5, yM + 15);
+    GrPaint yPaint;
+    auto yTexFP = GrTextureEffect::Make(srcView, this->colorInfo().alphaType(), texMatrix);
+    auto yColFP = GrColorMatrixFragmentProcessor::Make(std::move(yTexFP), yM,
+                                                       /*unpremulInput=*/false,
+                                                       /*clampRGBOutput=*/true,
+                                                       /*premulOutput=*/false);
+    yPaint.addColorFragmentProcessor(std::move(yColFP));
+    yPaint.setPorterDuffXPFactory(SkBlendMode::kSrc);
+    yRTC->fillRectToRect(nullptr, std::move(yPaint), GrAA::kNo, SkMatrix::I(), dstRectY, dstRectY);
+    if (!doSynchronousRead) {
+        yTransfer = yRTC->transferPixels(GrColorType::kAlpha_8,
+                                         SkIRect::MakeWH(yRTC->width(), yRTC->height()));
+        if (!yTransfer.fTransferBuffer) {
+            callback(context, nullptr);
+            return;
+        }
+    }
+
+    texMatrix.preScale(2.f, 2.f);
+    // This matrix generates (r,g,b,a) = (0, 0, 0, u)
+    float uM[20];
+    std::fill_n(uM, 15, 0.f);
+    std::copy_n(baseM + 5, 5, uM + 15);
+    GrPaint uPaint;
+    auto uTexFP = GrTextureEffect::Make(srcView, this->colorInfo().alphaType(), texMatrix,
+                                        GrSamplerState::Filter::kBilerp);
+    auto uColFP = GrColorMatrixFragmentProcessor::Make(std::move(uTexFP), uM,
+                                                       /*unpremulInput=*/false,
+                                                       /*clampRGBOutput=*/true,
+                                                       /*premulOutput=*/false);
+    uPaint.addColorFragmentProcessor(std::move(uColFP));
+    uPaint.setPorterDuffXPFactory(SkBlendMode::kSrc);
+    uRTC->fillRectToRect(nullptr, std::move(uPaint), GrAA::kNo, SkMatrix::I(), dstRectUV,
+                         dstRectUV);
+    if (!doSynchronousRead) {
+        uTransfer = uRTC->transferPixels(GrColorType::kAlpha_8,
+                                         SkIRect::MakeWH(uRTC->width(), uRTC->height()));
+        if (!uTransfer.fTransferBuffer) {
+            callback(context, nullptr);
+            return;
+        }
+    }
+
+    // This matrix generates (r,g,b,a) = (0, 0, 0, v)
+    float vM[20];
+    std::fill_n(vM, 15, 0.f);
+    std::copy_n(baseM + 10, 5, vM + 15);
+    GrPaint vPaint;
+    auto vTexFP = GrTextureEffect::Make(std::move(srcView), this->colorInfo().alphaType(),
+                                        texMatrix, GrSamplerState::Filter::kBilerp);
+    auto vColFP = GrColorMatrixFragmentProcessor::Make(std::move(vTexFP), vM,
+                                                       /*unpremulInput=*/false,
+                                                       /*clampRGBOutput=*/true,
+                                                       /*premulOutput=*/false);
+    vPaint.addColorFragmentProcessor(std::move(vColFP));
+    vPaint.setPorterDuffXPFactory(SkBlendMode::kSrc);
+    vRTC->fillRectToRect(nullptr, std::move(vPaint), GrAA::kNo, SkMatrix::I(), dstRectUV,
+                         dstRectUV);
+    if (!doSynchronousRead) {
+        vTransfer = vRTC->transferPixels(GrColorType::kAlpha_8,
+                                         SkIRect::MakeWH(vRTC->width(), vRTC->height()));
+        if (!vTransfer.fTransferBuffer) {
+            callback(context, nullptr);
+            return;
+        }
+    }
+
+    if (doSynchronousRead) {
+        GrImageInfo yInfo(GrColorType::kAlpha_8, kPremul_SkAlphaType, nullptr, dstSize);
+        GrImageInfo uvInfo = yInfo.makeWH(halfW, halfH);
+        size_t yRB  = yInfo.minRowBytes();
+        size_t uvRB = uvInfo.minRowBytes();
+        std::unique_ptr<char[]> y(new char[yRB * yInfo.height()]);
+        std::unique_ptr<char[]> u(new char[uvRB*uvInfo.height()]);
+        std::unique_ptr<char[]> v(new char[uvRB*uvInfo.height()]);
+        if (!yRTC->readPixels(yInfo,  y.get(), yRB,  {0, 0}, direct) ||
+            !uRTC->readPixels(uvInfo, u.get(), uvRB, {0, 0}, direct) ||
+            !vRTC->readPixels(uvInfo, v.get(), uvRB, {0, 0}, direct)) {
+            callback(context, nullptr);
+            return;
+        }
+        auto result = std::make_unique<AsyncReadResult>(direct->priv().contextID());
+        result->addCpuPlane(std::move(y), yRB );
+        result->addCpuPlane(std::move(u), uvRB);
+        result->addCpuPlane(std::move(v), uvRB);
+        callback(context, std::move(result));
+        return;
+    }
+
+    struct FinishContext {
+        ReadPixelsCallback* fClientCallback;
+        ReadPixelsContext fClientContext;
+        GrClientMappedBufferManager* fMappedBufferManager;
+        SkISize fSize;
+        PixelTransferResult fYTransfer;
+        PixelTransferResult fUTransfer;
+        PixelTransferResult fVTransfer;
+    };
+    // Assumption is that the caller would like to flush. We could take a parameter or require an
+    // explicit flush from the caller. We'd have to have a way to defer attaching the finish
+    // callback to GrGpu until after the next flush that flushes our op list, though.
+    auto* finishContext = new FinishContext{callback,
+                                            context,
+                                            direct->priv().clientMappedBufferManager(),
+                                            dstSize,
+                                            std::move(yTransfer),
+                                            std::move(uTransfer),
+                                            std::move(vTransfer)};
+    auto finishCallback = [](GrGpuFinishedContext c) {
+        const auto* context = reinterpret_cast<const FinishContext*>(c);
+        auto result = std::make_unique<AsyncReadResult>(context->fMappedBufferManager->inboxID());
+        auto manager = context->fMappedBufferManager;
+        size_t rowBytes = SkToSizeT(context->fSize.width());
+        if (!result->addTransferResult(context->fYTransfer, context->fSize, rowBytes, manager)) {
+            (*context->fClientCallback)(context->fClientContext, nullptr);
+            delete context;
+            return;
+        }
+        rowBytes /= 2;
+        SkISize uvSize = {context->fSize.width() / 2, context->fSize.height() / 2};
+        if (!result->addTransferResult(context->fUTransfer, uvSize, rowBytes, manager)) {
+            (*context->fClientCallback)(context->fClientContext, nullptr);
+            delete context;
+            return;
+        }
+        if (!result->addTransferResult(context->fVTransfer, uvSize, rowBytes, manager)) {
+            (*context->fClientCallback)(context->fClientContext, nullptr);
+            delete context;
+            return;
+        }
+        (*context->fClientCallback)(context->fClientContext, std::move(result));
+        delete context;
+    };
+    GrFlushInfo flushInfo;
+    flushInfo.fFinishedContext = finishContext;
+    flushInfo.fFinishedProc = finishCallback;
+    this->flush(SkSurface::BackendSurfaceAccess::kNoAccess, flushInfo, nullptr);
+}
+
 bool GrSurfaceContext::copy(GrSurfaceProxy* src, const SkIRect& srcRect, const SkIPoint& dstPoint) {
     ASSERT_SINGLE_OWNER
     RETURN_FALSE_IF_ABANDONED
@@ -499,12 +1006,11 @@
             this->readSurfaceView(), dstPoint);
 }
 
-std::unique_ptr<GrRenderTargetContext> GrSurfaceContext::rescale(
-        const GrImageInfo& info,
-        GrSurfaceOrigin origin,
-        SkIRect srcRect,
-        SkSurface::RescaleGamma rescaleGamma,
-        SkFilterQuality rescaleQuality) {
+std::unique_ptr<GrRenderTargetContext> GrSurfaceContext::rescale(const GrImageInfo& info,
+                                                                 GrSurfaceOrigin origin,
+                                                                 SkIRect srcRect,
+                                                                 RescaleGamma rescaleGamma,
+                                                                 SkFilterQuality rescaleQuality) {
     auto rtProxy = this->asRenderTargetProxy();
     if (rtProxy && rtProxy->wrapsVkSecondaryCB()) {
         return nullptr;
@@ -538,7 +1044,7 @@
 
     // 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::kLinear && this->colorInfo().colorSpace() &&
+    if (rescaleGamma == RescaleGamma::kLinear && this->colorInfo().colorSpace() &&
         !this->colorInfo().colorSpace()->gammaIsLinear()) {
         auto cs = this->colorInfo().colorSpace()->makeLinearGamma();
         auto xform = GrColorSpaceXform::Make(this->colorInfo().colorSpace(), srcAlphaType, cs.get(),
@@ -640,6 +1146,25 @@
     return tempA;
 }
 
+GrSemaphoresSubmitted GrSurfaceContext::flush(SkSurface::BackendSurfaceAccess access,
+                                              const GrFlushInfo& info,
+                                              const GrBackendSurfaceMutableState* newState) {
+    ASSERT_SINGLE_OWNER
+    if (fContext->priv().abandoned()) {
+        if (info.fSubmittedProc) {
+            info.fSubmittedProc(info.fSubmittedContext, false);
+        }
+        if (info.fFinishedProc) {
+            info.fFinishedProc(info.fFinishedContext);
+        }
+        return GrSemaphoresSubmitted::kNo;
+    }
+    SkDEBUGCODE(this->validate();)
+    GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "flush", fContext);
+
+    return this->drawingManager()->flushSurface(this->asSurfaceProxy(), access, info, newState);
+}
+
 GrSurfaceContext::PixelTransferResult GrSurfaceContext::transferPixels(GrColorType dstCT,
                                                                        const SkIRect& rect) {
     SkASSERT(rect.fLeft >= 0 && rect.fRight <= this->width());
diff --git a/src/gpu/GrSurfaceContext.h b/src/gpu/GrSurfaceContext.h
index 8e3394c..1571f4e 100644
--- a/src/gpu/GrSurfaceContext.h
+++ b/src/gpu/GrSurfaceContext.h
@@ -9,9 +9,11 @@
 #define GrSurfaceContext_DEFINED
 
 #include "include/core/SkFilterQuality.h"
+#include "include/core/SkImage.h"
 #include "include/core/SkRect.h"
 #include "include/core/SkRefCnt.h"
 #include "include/core/SkSurface.h"
+#include "src/gpu/GrClientMappedBufferManager.h"
 #include "src/gpu/GrColorInfo.h"
 #include "src/gpu/GrDataUtils.h"
 #include "src/gpu/GrImageInfo.h"
@@ -83,6 +85,28 @@
     bool readPixels(const GrImageInfo& dstInfo, void* dst, size_t rowBytes, SkIPoint srcPt,
                     GrContext* direct = nullptr);
 
+    using ReadPixelsCallback = SkImage::ReadPixelsCallback;
+    using ReadPixelsContext  = SkImage::ReadPixelsContext;
+    using RescaleGamma       = SkImage::RescaleGamma;
+
+    // GPU implementation for SkImage:: and SkSurface::asyncRescaleAndReadPixels.
+    void asyncRescaleAndReadPixels(const SkImageInfo& info,
+                                   const SkIRect& srcRect,
+                                   RescaleGamma rescaleGamma,
+                                   SkFilterQuality rescaleQuality,
+                                   ReadPixelsCallback callback,
+                                   ReadPixelsContext context);
+
+    // GPU implementation for SkImage:: and SkSurface::asyncRescaleAndReadPixelsYUV420.
+    void asyncRescaleAndReadPixelsYUV420(SkYUVColorSpace yuvColorSpace,
+                                         sk_sp<SkColorSpace> dstColorSpace,
+                                         const SkIRect& srcRect,
+                                         SkISize dstSize,
+                                         RescaleGamma rescaleGamma,
+                                         SkFilterQuality rescaleQuality,
+                                         ReadPixelsCallback callback,
+                                         ReadPixelsContext context);
+
     /**
      * Writes a rectangle of pixels [srcInfo, srcBuffer, srcRowbytes] into the
      * renderTargetContext at the specified position.
@@ -124,9 +148,17 @@
     std::unique_ptr<GrRenderTargetContext> rescale(const GrImageInfo& info,
                                                    GrSurfaceOrigin,
                                                    SkIRect srcRect,
-                                                   SkSurface::RescaleGamma,
+                                                   SkImage::RescaleGamma,
                                                    SkFilterQuality);
 
+    /**
+     * After this returns any pending surface IO will be issued to the backend 3D API and
+     * if the surface has MSAA it will be resolved.
+     */
+    GrSemaphoresSubmitted flush(SkSurface::BackendSurfaceAccess access,
+                                const GrFlushInfo&,
+                                const GrBackendSurfaceMutableState*);
+
     GrAuditTrail* auditTrail();
 
     // Provides access to functions that aren't part of the public API.
@@ -171,6 +203,12 @@
     };
     PixelTransferResult transferPixels(GrColorType colorType, const SkIRect& rect);
 
+    // The async read step of asyncRescaleAndReadPixels()
+    void asyncReadPixels(const SkIRect& rect,
+                         SkColorType colorType,
+                         ReadPixelsCallback callback,
+                         ReadPixelsContext context);
+
 private:
     friend class GrSurfaceProxy; // for copy
 
@@ -192,6 +230,8 @@
      */
     bool copy(GrSurfaceProxy* src, const SkIRect& srcRect, const SkIPoint& dstPoint);
 
+    class AsyncReadResult;
+
     GrColorInfo fColorInfo;
 
     typedef SkRefCnt INHERITED;
diff --git a/src/image/SkImage.cpp b/src/image/SkImage.cpp
index 4b5afd8..63887e3 100644
--- a/src/image/SkImage.cpp
+++ b/src/image/SkImage.cpp
@@ -24,6 +24,7 @@
 #include "src/core/SkSpecialImage.h"
 #include "src/image/SkImage_Base.h"
 #include "src/image/SkReadPixelsRec.h"
+#include "src/image/SkRescaleAndReadPixels.h"
 #include "src/shaders/SkImageShader.h"
 
 #if SK_SUPPORT_GPU
@@ -52,6 +53,44 @@
     return as_IB(this)->onReadPixels(dstInfo, dstPixels, dstRowBytes, srcX, srcY, chint);
 }
 
+void SkImage::asyncRescaleAndReadPixels(const SkImageInfo& info,
+                                        const SkIRect& srcRect,
+                                        RescaleGamma rescaleGamma,
+                                        SkFilterQuality rescaleQuality,
+                                        ReadPixelsCallback callback,
+                                        ReadPixelsContext context) {
+    if (!SkIRect::MakeWH(this->width(), this->height()).contains(srcRect) ||
+        !SkImageInfoIsValid(info)) {
+        callback(context, nullptr);
+        return;
+    }
+    as_IB(this)->onAsyncRescaleAndReadPixels(
+            info, srcRect, rescaleGamma, rescaleQuality, callback, context);
+}
+
+void SkImage::asyncRescaleAndReadPixelsYUV420(SkYUVColorSpace yuvColorSpace,
+                                              sk_sp<SkColorSpace> dstColorSpace,
+                                              const SkIRect& srcRect,
+                                              const SkISize& dstSize,
+                                              RescaleGamma rescaleGamma,
+                                              SkFilterQuality rescaleQuality,
+                                              ReadPixelsCallback callback,
+                                              ReadPixelsContext context) {
+    if (!SkIRect::MakeWH(this->width(), this->height()).contains(srcRect) || dstSize.isZero() ||
+        (dstSize.width() & 0b1) || (dstSize.height() & 0b1)) {
+        callback(context, nullptr);
+        return;
+    }
+    as_IB(this)->onAsyncRescaleAndReadPixelsYUV420(yuvColorSpace,
+                                                   std::move(dstColorSpace),
+                                                   srcRect,
+                                                   dstSize,
+                                                   rescaleGamma,
+                                                   rescaleQuality,
+                                                   callback,
+                                                   context);
+}
+
 bool SkImage::scalePixels(const SkPixmap& dst, SkFilterQuality quality, CachingHint chint) const {
     if (this->width() == dst.width() && this->height() == dst.height()) {
         return this->readPixels(dst, 0, 0, chint);
@@ -207,6 +246,44 @@
     }
 }
 
+void SkImage_Base::onAsyncRescaleAndReadPixels(const SkImageInfo& info,
+                                               const SkIRect& origSrcRect,
+                                               RescaleGamma rescaleGamma,
+                                               SkFilterQuality rescaleQuality,
+                                               ReadPixelsCallback callback,
+                                               ReadPixelsContext context) {
+    SkBitmap src;
+    SkPixmap peek;
+    SkIRect srcRect;
+    if (this->peekPixels(&peek)) {
+        src.installPixels(peek);
+        srcRect = origSrcRect;
+    } else {
+        src.setInfo(this->imageInfo().makeDimensions(origSrcRect.size()));
+        src.allocPixels();
+        if (!this->readPixels(src.pixmap(), origSrcRect.x(), origSrcRect.y())) {
+            callback(context, nullptr);
+            return;
+        }
+        srcRect = SkIRect::MakeSize(src.dimensions());
+    }
+    return SkRescaleAndReadPixels(
+            src, info, srcRect, rescaleGamma, rescaleQuality, callback, context);
+}
+
+void SkImage_Base::onAsyncRescaleAndReadPixelsYUV420(SkYUVColorSpace,
+                                                     sk_sp<SkColorSpace> dstColorSpace,
+                                                     const SkIRect& srcRect,
+                                                     const SkISize& dstSize,
+                                                     RescaleGamma,
+                                                     SkFilterQuality,
+                                                     ReadPixelsCallback callback,
+                                                     ReadPixelsContext context) {
+    // TODO: Call non-YUV asyncRescaleAndReadPixels and then make our callback convert to YUV and
+    // call client's callback.
+    callback(context, nullptr);
+}
+
 GrBackendTexture SkImage_Base::onGetBackendTexture(bool flushPendingGrContextIO,
                                                    GrSurfaceOrigin* origin) const {
     return GrBackendTexture(); // invalid
diff --git a/src/image/SkImage_Base.h b/src/image/SkImage_Base.h
index ff92715..da8b71f 100644
--- a/src/image/SkImage_Base.h
+++ b/src/image/SkImage_Base.h
@@ -46,6 +46,27 @@
     virtual bool onReadPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes,
                               int srcX, int srcY, CachingHint) const = 0;
 
+    /**
+     * Default implementation does a rescale/read and then calls the callback.
+     */
+    virtual void onAsyncRescaleAndReadPixels(const SkImageInfo&,
+                                             const SkIRect& srcRect,
+                                             RescaleGamma,
+                                             SkFilterQuality,
+                                             ReadPixelsCallback,
+                                             ReadPixelsContext);
+    /**
+     * Default implementation does a rescale/read/yuv conversion and then calls the callback.
+     */
+    virtual void onAsyncRescaleAndReadPixelsYUV420(SkYUVColorSpace,
+                                                   sk_sp<SkColorSpace> dstColorSpace,
+                                                   const SkIRect& srcRect,
+                                                   const SkISize& dstSize,
+                                                   RescaleGamma,
+                                                   SkFilterQuality,
+                                                   ReadPixelsCallback,
+                                                   ReadPixelsContext);
+
     virtual GrContext* context() const { return nullptr; }
 
 #if SK_SUPPORT_GPU
diff --git a/src/image/SkImage_Gpu.cpp b/src/image/SkImage_Gpu.cpp
index f3cfce5..914810d 100644
--- a/src/image/SkImage_Gpu.cpp
+++ b/src/image/SkImage_Gpu.cpp
@@ -123,6 +123,47 @@
                                    this->alphaType(), std::move(newCS));
 }
 
+void SkImage_Gpu::onAsyncRescaleAndReadPixels(const SkImageInfo& info,
+                                              const SkIRect& srcRect,
+                                              RescaleGamma rescaleGamma,
+                                              SkFilterQuality rescaleQuality,
+                                              ReadPixelsCallback callback,
+                                              ReadPixelsContext context) {
+    GrColorType ct = SkColorTypeToGrColorType(this->colorType());
+    auto ctx = GrSurfaceContext::Make(fContext.get(), fView, ct, this->alphaType(),
+                                      this->refColorSpace());
+    if (!ctx) {
+        callback(context, nullptr);
+        return;
+    }
+    ctx->asyncRescaleAndReadPixels(info, srcRect, rescaleGamma, rescaleQuality, callback, context);
+}
+
+void SkImage_Gpu::onAsyncRescaleAndReadPixelsYUV420(SkYUVColorSpace yuvColorSpace,
+                                                    sk_sp<SkColorSpace> dstColorSpace,
+                                                    const SkIRect& srcRect,
+                                                    const SkISize& dstSize,
+                                                    RescaleGamma rescaleGamma,
+                                                    SkFilterQuality rescaleQuality,
+                                                    ReadPixelsCallback callback,
+                                                    ReadPixelsContext context) {
+    GrColorType ct = SkColorTypeToGrColorType(this->colorType());
+    auto ctx = GrSurfaceContext::Make(fContext.get(), fView, ct, this->alphaType(),
+                                      this->refColorSpace());
+    if (!ctx) {
+        callback(context, nullptr);
+        return;
+    }
+    ctx->asyncRescaleAndReadPixelsYUV420(yuvColorSpace,
+                                         std::move(dstColorSpace),
+                                         srcRect,
+                                         dstSize,
+                                         rescaleGamma,
+                                         rescaleQuality,
+                                         callback,
+                                         context);
+}
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 static sk_sp<SkImage> new_wrapped_texture_common(GrContext* ctx,
diff --git a/src/image/SkImage_Gpu.h b/src/image/SkImage_Gpu.h
index 1237d82..3a6015d 100644
--- a/src/image/SkImage_Gpu.h
+++ b/src/image/SkImage_Gpu.h
@@ -50,6 +50,22 @@
 
     sk_sp<SkImage> onReinterpretColorSpace(sk_sp<SkColorSpace>) const final;
 
+    void onAsyncRescaleAndReadPixels(const SkImageInfo&,
+                                     const SkIRect& srcRect,
+                                     RescaleGamma,
+                                     SkFilterQuality,
+                                     ReadPixelsCallback,
+                                     ReadPixelsContext) override;
+
+    void onAsyncRescaleAndReadPixelsYUV420(SkYUVColorSpace,
+                                           sk_sp<SkColorSpace>,
+                                           const SkIRect& srcRect,
+                                           const SkISize& dstSize,
+                                           RescaleGamma,
+                                           SkFilterQuality,
+                                           ReadPixelsCallback,
+                                           ReadPixelsContext) override;
+
     /**
      * This is the implementation of SkDeferredDisplayListRecorder::makePromiseImage.
      */
diff --git a/src/image/SkRescaleAndReadPixels.cpp b/src/image/SkRescaleAndReadPixels.cpp
new file mode 100644
index 0000000..0b7ee2e
--- /dev/null
+++ b/src/image/SkRescaleAndReadPixels.cpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/core/SkCanvas.h"
+#include "include/core/SkImageInfo.h"
+#include "include/core/SkPaint.h"
+#include "include/core/SkRect.h"
+#include "include/core/SkSurface.h"
+
+#include <cmath>
+
+void SkRescaleAndReadPixels(SkBitmap bmp,
+                            const SkImageInfo& resultInfo,
+                            const SkIRect& srcRect,
+                            SkImage::RescaleGamma rescaleGamma,
+                            SkFilterQuality rescaleQuality,
+                            SkImage::ReadPixelsCallback callback,
+                            SkImage::ReadPixelsContext context) {
+    int srcW = srcRect.width();
+    int srcH = srcRect.height();
+
+    float sx = (float)resultInfo.width() / srcW;
+    float sy = (float)resultInfo.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> tempSurf;
+    sk_sp<SkImage> srcImage;
+    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 && bmp.info().colorSpace() &&
+        !bmp.info().colorSpace()->gammaIsLinear()) {
+        auto cs = bmp.info().colorSpace()->makeLinearGamma();
+        // Promote to F16 color type to preserve precision.
+        auto ii = SkImageInfo::Make(srcW, srcH, kRGBA_F16_SkColorType, bmp.info().alphaType(),
+                                    std::move(cs));
+        auto linearSurf = SkSurface::MakeRaster(ii);
+        if (!linearSurf) {
+            callback(context, nullptr);
+            return;
+        }
+        linearSurf->getCanvas()->drawBitmap(bmp, -srcX, -srcY, &paint);
+        tempSurf = std::move(linearSurf);
+        srcImage = tempSurf->makeImageSnapshot();
+        srcX = 0;
+        srcY = 0;
+        constraint = SkCanvas::kFast_SrcRectConstraint;
+    } else {
+        // MakeFromBitmap would trigger a copy if bmp is mutable.
+        srcImage = SkImage::MakeFromRaster(bmp.pixmap(), nullptr, nullptr);
+    }
+    while (stepsX || stepsY) {
+        int nextW = resultInfo.width();
+        int nextH = resultInfo.height();
+        if (stepsX < 0) {
+            nextW = resultInfo.width() << (-stepsX - 1);
+            stepsX++;
+        } else if (stepsX != 0) {
+            if (stepsX > 1) {
+                nextW = srcW * 2;
+            }
+            --stepsX;
+        }
+        if (stepsY < 0) {
+            nextH = resultInfo.height() << (-stepsY - 1);
+            stepsY++;
+        } else if (stepsY != 0) {
+            if (stepsY > 1) {
+                nextH = srcH * 2;
+            }
+            --stepsY;
+        }
+        auto ii = srcImage->imageInfo().makeWH(nextW, nextH);
+        if (!stepsX && !stepsY) {
+            // Might as well fold conversion to final info in the last step.
+            ii = resultInfo;
+        }
+        auto next = SkSurface::MakeRaster(ii);
+        if (!next) {
+            callback(context, nullptr);
+            return;
+        }
+        next->getCanvas()->drawImageRect(
+                std::move(srcImage), SkIRect::MakeXYWH(srcX, srcY, srcW, srcH),
+                SkRect::MakeWH((float)nextW, (float)nextH), &paint, constraint);
+        tempSurf = std::move(next);
+        srcImage = tempSurf->makeImageSnapshot();
+        srcX = srcY = 0;
+        srcW = nextW;
+        srcH = nextH;
+        constraint = SkCanvas::kFast_SrcRectConstraint;
+    }
+
+    size_t rowBytes = resultInfo.minRowBytes();
+    std::unique_ptr<char[]> data(new char[resultInfo.height() * rowBytes]);
+    SkPixmap pm(resultInfo, data.get(), rowBytes);
+    if (srcImage->readPixels(pm, srcX, srcY)) {
+        class Result : public SkImage::AsyncReadResult {
+        public:
+            Result(std::unique_ptr<const char[]> data, size_t rowBytes)
+                    : fData(std::move(data)), fRowBytes(rowBytes) {}
+            int count() const override { return 1; }
+            const void* data(int i) const override { return fData.get(); }
+            size_t rowBytes(int i) const override { return fRowBytes; }
+
+        private:
+            std::unique_ptr<const char[]> fData;
+            size_t fRowBytes;
+        };
+        callback(context, std::make_unique<Result>(std::move(data), rowBytes));
+    } else {
+        callback(context, nullptr);
+    }
+}
diff --git a/src/image/SkRescaleAndReadPixels.h b/src/image/SkRescaleAndReadPixels.h
new file mode 100644
index 0000000..e1e9448
--- /dev/null
+++ b/src/image/SkRescaleAndReadPixels.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/core/SkBitmap.h"
+#include "include/core/SkFilterQuality.h"
+#include "include/core/SkImage.h"
+
+struct SkImageInfo;
+struct SkIRect;
+
+/** Generic/synchronous implementation for SkImage:: and SkSurface::asyncRescaleAndReadPixels. */
+void SkRescaleAndReadPixels(SkBitmap src,
+                            const SkImageInfo& resultInfo,
+                            const SkIRect& srcRect,
+                            SkImage::RescaleGamma,
+                            SkFilterQuality,
+                            SkImage::ReadPixelsCallback,
+                            SkImage::ReadPixelsContext);
diff --git a/src/image/SkSurface.cpp b/src/image/SkSurface.cpp
index 1897f95..3a5e862 100644
--- a/src/image/SkSurface.cpp
+++ b/src/image/SkSurface.cpp
@@ -12,6 +12,7 @@
 #include "include/gpu/GrBackendSurface.h"
 #include "src/core/SkAutoPixmapStorage.h"
 #include "src/core/SkImagePriv.h"
+#include "src/image/SkRescaleAndReadPixels.h"
 #include "src/image/SkSurface_Base.h"
 
 static SkPixelGeometry compute_default_geometry() {
@@ -97,126 +98,29 @@
     }
 }
 
-void SkSurface_Base::onAsyncRescaleAndReadPixels(const SkImageInfo& info, const SkIRect& srcRect,
+void SkSurface_Base::onAsyncRescaleAndReadPixels(const SkImageInfo& info,
+                                                 const SkIRect& origSrcRect,
                                                  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)));
+    SkBitmap src;
+    SkPixmap peek;
+    SkIRect srcRect;
+    if (this->peekPixels(&peek)) {
+        src.installPixels(peek);
+        srcRect = origSrcRect;
     } 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);
-                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) {
+        src.setInfo(this->imageInfo().makeDimensions(origSrcRect.size()));
+        src.allocPixels();
+        if (!this->readPixels(src, origSrcRect.x(), origSrcRect.y())) {
             callback(context, nullptr);
             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;
+        srcRect = SkIRect::MakeSize(src.dimensions());
     }
-
-    size_t rowBytes = info.minRowBytes();
-    std::unique_ptr<char[]> data(new char[info.height() * rowBytes]);
-    SkPixmap pm(info, data.get(), rowBytes);
-    if (src->readPixels(pm, srcX, srcY)) {
-        class Result : public AsyncReadResult {
-        public:
-            Result(std::unique_ptr<const char[]> data, size_t rowBytes)
-                    : fData(std::move(data)), fRowBytes(rowBytes) {}
-            int count() const override { return 1; }
-            const void* data(int i) const override { return fData.get(); }
-            size_t rowBytes(int i) const override { return fRowBytes; }
-
-        private:
-            std::unique_ptr<const char[]> fData;
-            size_t fRowBytes;
-        };
-        callback(context, std::make_unique<Result>(std::move(data), rowBytes));
-    } else {
-        callback(context, nullptr);
-    }
+    return SkRescaleAndReadPixels(src, info, srcRect, rescaleGamma, rescaleQuality, callback,
+                                  context);
 }
 
 void SkSurface_Base::onAsyncRescaleAndReadPixelsYUV420(
diff --git a/tests/ReadPixelsTest.cpp b/tests/ReadPixelsTest.cpp
index 6951df7..5acac1a 100644
--- a/tests/ReadPixelsTest.cpp
+++ b/tests/ReadPixelsTest.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
-#include <initializer_list>
 #include "include/core/SkCanvas.h"
+#include "include/core/SkImage.h"
 #include "include/core/SkSurface.h"
 #include "include/effects/SkGradientShader.h"
 #include "include/gpu/GrContext.h"
@@ -23,9 +23,12 @@
 #include "tests/Test.h"
 #include "tests/TestUtils.h"
 #include "tools/ToolUtils.h"
+#include "tools/gpu/BackendTextureImageFactory.h"
 #include "tools/gpu/GrContextFactory.h"
 #include "tools/gpu/ProxyUtils.h"
 
+#include <initializer_list>
+
 static const int DEV_W = 100, DEV_H = 100;
 static const SkIRect DEV_RECT = SkIRect::MakeWH(DEV_W, DEV_H);
 static const SkRect DEV_RECT_S = SkRect::MakeWH(DEV_W * SK_Scalar1,
@@ -647,8 +650,15 @@
 // the pixmap.
 template <typename T> using GpuSrcFactory = T(SkPixmap&);
 
+enum class GpuReadResult {
+    kFail,
+    kSuccess,
+    kExcusedFailure,
+};
+
 // Does a read from the T into the pixmap.
-template <typename T> using GpuReadSrcFn = bool(const T&, const SkIVector& offset, const SkPixmap&);
+template <typename T>
+using GpuReadSrcFn = GpuReadResult(const T&, const SkIVector& offset, const SkPixmap&);
 
 }  // anonymous namespace
 
@@ -685,19 +695,19 @@
                     dstPixels.computeByteSize(),
                     kInitialByte);
 
-        const bool success = read(src, offset, dstPixels);
+        const GpuReadResult result = read(src, offset, dstPixels);
 
         if (!SkIRect::Intersects(rect, surfBounds)) {
-            REPORTER_ASSERT(reporter, !success);
+            REPORTER_ASSERT(reporter, result != GpuReadResult::kSuccess);
         } else if (readCT == kUnknown_SkColorType) {
-            REPORTER_ASSERT(reporter, !success);
+            REPORTER_ASSERT(reporter, result != GpuReadResult::kSuccess);
         } else if (readAT == kUnknown_SkAlphaType) {
-            REPORTER_ASSERT(reporter, !success);
+            REPORTER_ASSERT(reporter, result != GpuReadResult::kSuccess);
         } else if (!rules.fUncontainedRectSucceeds && !surfBounds.contains(rect)) {
-            REPORTER_ASSERT(reporter, !success);
+            REPORTER_ASSERT(reporter, result != GpuReadResult::kSuccess);
         } else if (!rules.fAllowUnpremulRead && readAT == kUnpremul_SkAlphaType) {
-            REPORTER_ASSERT(reporter, !success);
-        } else if (!success) {
+            REPORTER_ASSERT(reporter, result != GpuReadResult::kSuccess);
+        } else if (result == GpuReadResult::kFail) {
             // TODO: Support RGB/BGR 101010x, BGRA 1010102 on the GPU.
             if (SkColorTypeToGrColorType(readCT) != GrColorType::kUnknown) {
                 ERRORF(reporter,
@@ -716,7 +726,7 @@
         // Considering the rect we tried to read and the surface bounds figure  out which pixels in
         // both src and dst space should actually have been read and written.
         SkIRect srcReadRect;
-        if (success && srcReadRect.intersect(surfBounds, rect)) {
+        if (result == GpuReadResult::kSuccess && srcReadRect.intersect(surfBounds, rect)) {
             SkIRect dstWriteRect = srcReadRect.makeOffset(-rect.fLeft, -rect.fTop);
 
             const bool lumConversion =
@@ -724,18 +734,19 @@
                      (SkColorTypeChannelFlags(readCT) & kGray_SkColorChannelFlag);
             // A CS or luminance conversion allows a 3 value difference and otherwise a 2 value
             // difference. Note that sometimes read back on GPU can be lossy even when there no
-            // conversion at allbecause GPU->CPU read may go to a lower bit depth format and then be
-            // promoted back to the original type. For example, GL ES cannot read to 1010102, so we
-            // go through 8888.
+            // conversion at all because GPU->CPU read may go to a lower bit depth format and then
+            // be promoted back to the original type. For example, GL ES cannot read to 1010102, so
+            // we go through 8888.
             const float numer = (lumConversion || csConversion) ? 3.f : 2.f;
-            int rgbBits = std::min({min_rgb_channel_bits(readCT),
-                                    min_rgb_channel_bits(srcCT),
-                                    8});
+            int rgbBits = std::min(
+                    {min_rgb_channel_bits(readCT), min_rgb_channel_bits(srcCT), 8});
             float tol = numer / (1 << rgbBits);
             float alphaTol = 0;
             if (readAT != kOpaque_SkAlphaType && srcAT != kOpaque_SkAlphaType) {
-                const int alphaBits = std::min(alpha_channel_bits(readCT),
-                                               alpha_channel_bits(srcCT));
+                // Alpha can also get squashed down to 8 bits going through an intermediate
+                // color format.
+                const int alphaBits =
+                        std::min({alpha_channel_bits(readCT), alpha_channel_bits(srcCT), 8});
                 alphaTol = 2.f / (1 << alphaBits);
             }
 
@@ -924,20 +935,20 @@
 namespace {
 struct AsyncContext {
     bool fCalled = false;
-    std::unique_ptr<const SkSurface::AsyncReadResult> fResult;
+    std::unique_ptr<const SkImage::AsyncReadResult> fResult;
 };
 }  // anonymous namespace
 
 // Making this a lambda in the test functions caused:
 //   "error: cannot compile this forwarded non-trivially copyable parameter yet"
 // on x86/Win/Clang bot, referring to 'result'.
-static void async_callback(void* c, std::unique_ptr<const SkSurface::AsyncReadResult> result) {
+static void async_callback(void* c, std::unique_ptr<const SkImage::AsyncReadResult> result) {
     auto context = static_cast<AsyncContext*>(c);
     context->fResult = std::move(result);
     context->fCalled = true;
 };
 
-DEF_GPUTEST_FOR_RENDERING_CONTEXTS(AsyncReadPixels, reporter, ctxInfo) {
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SurfaceAsyncReadPixels, reporter, ctxInfo) {
     using Surface = sk_sp<SkSurface>;
     auto reader = std::function<GpuReadSrcFn<Surface>>([](const Surface& surface,
                                                           const SkIVector& offset,
@@ -946,18 +957,18 @@
         auto rect = SkIRect::MakeSize(pixels.dimensions()).makeOffset(offset);
 
         // Rescale quality and linearity don't matter since we're doing a non-scaling readback.
-        surface->asyncRescaleAndReadPixels(pixels.info(), rect, SkSurface::RescaleGamma::kSrc,
+        surface->asyncRescaleAndReadPixels(pixels.info(), rect, SkImage::RescaleGamma::kSrc,
                                            kNone_SkFilterQuality, async_callback, &context);
         surface->getContext()->submit();
         while (!context.fCalled) {
             surface->getCanvas()->getGrContext()->checkAsyncWorkCompletion();
         }
         if (!context.fResult) {
-            return false;
+            return GpuReadResult::kFail;
         }
         SkRectMemcpy(pixels.writable_addr(), pixels.rowBytes(), context.fResult->data(0),
                      context.fResult->rowBytes(0), pixels.info().minRowBytes(), pixels.height());
-        return true;
+        return GpuReadResult::kSuccess;
     });
     GpuReadPixelTestRules rules;
     rules.fAllowUnpremulSrc = false;
@@ -981,11 +992,65 @@
     }
 }
 
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(ImageAsyncReadPixels, reporter, ctxInfo) {
+    using Image = sk_sp<SkImage>;
+    GrContext* context = ctxInfo.grContext();
+    auto reader = std::function<GpuReadSrcFn<Image>>([context](const Image& image,
+                                                               const SkIVector& offset,
+                                                               const SkPixmap& pixels) {
+        AsyncContext asyncContext;
+        auto rect = SkIRect::MakeSize(pixels.dimensions()).makeOffset(offset);
+        // The GPU implementation is based on rendering and will fail for non-renderable color
+        // types.
+        auto ct = SkColorTypeToGrColorType(image->colorType());
+        auto format = context->priv().caps()->getDefaultBackendFormat(ct, GrRenderable::kYes);
+        if (!context->priv().caps()->isFormatAsColorTypeRenderable(ct, format)) {
+            return GpuReadResult::kExcusedFailure;
+        }
+
+        // Rescale quality and linearity don't matter since we're doing a non-scaling readback.
+        image->asyncRescaleAndReadPixels(pixels.info(), rect, SkImage::RescaleGamma::kSrc,
+                                         kNone_SkFilterQuality, async_callback, &asyncContext);
+        context->submit();
+        while (!asyncContext.fCalled) {
+            context->checkAsyncWorkCompletion();
+        }
+        if (!asyncContext.fResult) {
+            return GpuReadResult::kFail;
+        }
+        SkRectMemcpy(pixels.writable_addr(), pixels.rowBytes(), asyncContext.fResult->data(0),
+                     asyncContext.fResult->rowBytes(0), pixels.info().minRowBytes(),
+                     pixels.height());
+        return GpuReadResult::kSuccess;
+    });
+
+    GpuReadPixelTestRules rules;
+    rules.fAllowUnpremulSrc = true;
+    // GPU doesn't support reading to kUnpremul because the rescaling works by rendering and now
+    // we only support premul rendering.
+    rules.fAllowUnpremulRead = false;
+    rules.fUncontainedRectSucceeds = false;
+
+    for (auto origin : {kTopLeft_GrSurfaceOrigin, kBottomLeft_GrSurfaceOrigin}) {
+        for (auto renderable : {GrRenderable::kNo, GrRenderable::kYes}) {
+            auto factory = std::function<GpuSrcFactory<Image>>([&](const SkPixmap& src) {
+                if (src.colorType() == kRGB_888x_SkColorType) {
+                    return Image();
+                }
+                return sk_gpu_test::MakeBackendTextureImage(ctxInfo.grContext(), src, renderable,
+                                                            origin);
+            });
+            gpu_read_pixels_test_driver(reporter, rules, factory, reader);
+        }
+    }
+}
+
 DEF_GPUTEST_FOR_RENDERING_CONTEXTS(ReadPixels_Gpu, reporter, ctxInfo) {
     using Surface = sk_sp<SkSurface>;
     auto reader = std::function<GpuReadSrcFn<Surface>>(
             [](const Surface& surface, const SkIVector& offset, const SkPixmap& pixels) {
-                return surface->readPixels(pixels, offset.fX, offset.fY);
+                return surface->readPixels(pixels, offset.fX, offset.fY) ? GpuReadResult::kSuccess
+                                                                         : GpuReadResult::kFail;
             });
     GpuReadPixelTestRules rules;
     rules.fAllowUnpremulSrc = false;
@@ -1062,10 +1127,10 @@
                 if (yuv) {
                     surf->asyncRescaleAndReadPixelsYUV420(
                             kIdentity_SkYUVColorSpace, SkColorSpace::MakeSRGB(), ii.bounds(),
-                            ii.dimensions(), SkSurface::RescaleGamma::kSrc, kNone_SkFilterQuality,
+                            ii.dimensions(), SkImage::RescaleGamma::kSrc, kNone_SkFilterQuality,
                             &async_callback, &cbContext);
                 } else {
-                    surf->asyncRescaleAndReadPixels(ii, ii.bounds(), SkSurface::RescaleGamma::kSrc,
+                    surf->asyncRescaleAndReadPixels(ii, ii.bounds(), SkImage::RescaleGamma::kSrc,
                                                     kNone_SkFilterQuality, &async_callback,
                                                     &cbContext);
                 }
diff --git a/tools/gpu/BackendTextureImageFactory.cpp b/tools/gpu/BackendTextureImageFactory.cpp
new file mode 100644
index 0000000..4519e0a
--- /dev/null
+++ b/tools/gpu/BackendTextureImageFactory.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "tools/gpu/BackendTextureImageFactory.h"
+
+#include "include/core/SkImage.h"
+#include "include/core/SkPixmap.h"
+#include "include/gpu/GrBackendSurface.h"
+#include "include/gpu/GrContext.h"
+#include "src/core/SkAutoPixmapStorage.h"
+
+namespace {
+class ManagedBackendTexture : public SkNVRefCnt<ManagedBackendTexture> {
+public:
+    ~ManagedBackendTexture() {
+        if (fContext && fTexture.isValid()) {
+            fContext->submit(true);
+            fContext->deleteBackendTexture(fTexture);
+        }
+    }
+
+    static void Proc(void* context) { static_cast<ManagedBackendTexture*>(context)->unref(); }
+
+    template <typename... Args>
+    static sk_sp<ManagedBackendTexture> Make(GrContext* context, Args&&... args) {
+        sk_sp<ManagedBackendTexture> mbet(new ManagedBackendTexture);
+        mbet->fContext = context;
+        mbet->fTexture = context->createBackendTexture(std::forward<Args>(args)...,
+                                                       Proc,
+                                                       mbet->refAndPassAsContext());
+        return mbet;
+    }
+
+    const GrBackendTexture& texture() { return fTexture; }
+
+    void* refAndPassAsContext() {
+        this->ref();
+        return static_cast<void*>(this);
+    }
+
+private:
+    ManagedBackendTexture() = default;
+    GrContext* fContext = nullptr;
+    GrBackendTexture fTexture;
+};
+}  // namespace
+
+namespace sk_gpu_test {
+sk_sp<SkImage> MakeBackendTextureImage(GrContext* context,
+                                       const SkPixmap& pixmap,
+                                       GrRenderable renderable,
+                                       GrSurfaceOrigin origin) {
+    const SkPixmap* src = &pixmap;
+    SkAutoPixmapStorage temp;
+    if (origin == kBottomLeft_GrSurfaceOrigin) {
+        temp.alloc(src->info());
+        auto s = static_cast<const char*>(src->addr(0, pixmap.height() - 1));
+        auto d = static_cast<char*>(temp.writable_addr(0, 0));
+        for (int y = 0; y < temp.height(); ++y, s -= pixmap.rowBytes(), d += temp.rowBytes()) {
+            std::copy_n(s, temp.info().minRowBytes(), d);
+        }
+        src = &temp;
+    }
+    auto mbet = ManagedBackendTexture::Make(context, src, 1, renderable, GrProtected::kNo);
+    auto image = SkImage::MakeFromTexture(context, mbet->texture(), origin, src->colorType(),
+                                          src->alphaType(), src->refColorSpace(),
+                                          ManagedBackendTexture::Proc, mbet->refAndPassAsContext());
+    // We currently have an issue where depending on how MakeFromTexture fails it may not
+    // call the release proc (to be fixed soon, crbug.com/1097484). For now we use this hack
+    // to see if it failed without calling Proc.
+    if (!image) {
+        context->submit(true);
+        if (!mbet->unique()) {
+            ManagedBackendTexture::Proc(mbet.get());
+        }
+    }
+    return image;
+}
+}  // namespace sk_gpu_test
diff --git a/tools/gpu/BackendTextureImageFactory.h b/tools/gpu/BackendTextureImageFactory.h
new file mode 100644
index 0000000..a8a02ab
--- /dev/null
+++ b/tools/gpu/BackendTextureImageFactory.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/core/SkRefCnt.h"
+#include "include/gpu/GrTypes.h"
+
+class GrContext;
+class SkImage;
+class SkPixmap;
+
+namespace sk_gpu_test {
+/**
+ * Creates a backend texture with pixmap contents and wraps it in a SkImage that safely deletes
+ * the texture when it goes away. Unlike using makeTextureImage() on a non-GPU image, this will
+ * fail rather than fallback if the pixmaps's color type doesn't map to a supported texture format.
+ * For testing purposes the texture can be made renderable to exercise different code paths for
+ * renderable textures/formats.
+ */
+sk_sp<SkImage> MakeBackendTextureImage(GrContext*, const SkPixmap&, GrRenderable, GrSurfaceOrigin);
+}  // namespace sk_gpu_test