Add SkSurface::asyncReadPixels()

Initial version. Current limitations: No Metal support, no color space
conversions, for each src color type only one dst color type is legal (
which may or may not be the src color type), no alpha type conversions.

Bug: skia:8962

Change-Id: I6f046a32342b8f5ffb1799d67d7ba15c250ef9bf
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/212981
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
diff --git a/src/gpu/GrGpuCommandBuffer.h b/src/gpu/GrGpuCommandBuffer.h
index 1c4d5f4..abb3c4d 100644
--- a/src/gpu/GrGpuCommandBuffer.h
+++ b/src/gpu/GrGpuCommandBuffer.h
@@ -33,6 +33,9 @@
     // GrGpuRenderTargetCommandBuffer.
     virtual void copy(GrSurface* src, GrSurfaceOrigin srcOrigin,
                       const SkIRect& srcRect, const SkIPoint& dstPoint) = 0;
+    // Initiates a transfer from the surface owned by the command buffer to the GrGpuBuffer.
+    virtual void transferFrom(const SkIRect& srcRect, GrColorType bufferColorType,
+                              GrGpuBuffer* transferBuffer, size_t offset) = 0;
 
     virtual void insertEventMarker(const char*) = 0;
 
diff --git a/src/gpu/GrRenderTargetContext.cpp b/src/gpu/GrRenderTargetContext.cpp
index 977e96f..8dac16d 100644
--- a/src/gpu/GrRenderTargetContext.cpp
+++ b/src/gpu/GrRenderTargetContext.cpp
@@ -5,6 +5,7 @@
  * found in the LICENSE file.
  */
 
+#include "src/gpu/GrRenderTargetContext.h"
 #include "include/core/SkDrawable.h"
 #include "include/gpu/GrBackendSemaphore.h"
 #include "include/gpu/GrRenderTarget.h"
@@ -31,7 +32,6 @@
 #include "src/gpu/GrPathRenderer.h"
 #include "src/gpu/GrQuad.h"
 #include "src/gpu/GrRecordingContextPriv.h"
-#include "src/gpu/GrRenderTargetContext.h"
 #include "src/gpu/GrRenderTargetContextPriv.h"
 #include "src/gpu/GrResourceProvider.h"
 #include "src/gpu/GrShape.h"
@@ -60,6 +60,7 @@
 #include "src/gpu/ops/GrStencilPathOp.h"
 #include "src/gpu/ops/GrStrokeRectOp.h"
 #include "src/gpu/ops/GrTextureOp.h"
+#include "src/gpu/ops/GrTransferFromOp.h"
 #include "src/gpu/text/GrTextContext.h"
 #include "src/gpu/text/GrTextTarget.h"
 
@@ -1735,6 +1736,114 @@
     this->getRTOpList()->addOp(std::move(op), *this->caps());
 }
 
+bool GrRenderTargetContext::asyncReadPixels(SkColorType ct, SkAlphaType at, sk_sp<SkColorSpace> cs,
+                                            const SkIRect& srcRect, ReadPixelsCallback callback,
+                                            ReadPixelsContext context) {
+    auto direct = fContext->priv().asDirectContext();
+    if (!direct) {
+        return false;
+    }
+    if (!this->caps()->transferBufferSupport()) {
+        return false;
+    }
+    if (fRenderTargetProxy->wrapsVkSecondaryCB()) {
+        return false;
+    }
+    // We currently don't know our own alpha type, we assume it's premul if we have an alpha channel
+    // and opaque otherwise.
+    if (!GrPixelConfigIsAlphaOnly(fRenderTargetProxy->config()) && at != kPremul_SkAlphaType) {
+        return false;
+    }
+    // TODO(bsalomon): Enhance support for reading to different color types.
+    auto dstCT = SkColorTypeToGrColorType(ct);
+    auto readCT = this->caps()->supportedReadPixelsColorType(fRenderTargetProxy->config(), dstCT);
+    if (readCT != dstCT) {
+        return false;
+    }
+    if (!this->caps()->transferFromOffsetAlignment(readCT)) {
+        return false;
+    }
+
+    // TODO(bsalomon): Support color space conversion.
+    if (!SkColorSpace::Equals(cs.get(), this->colorSpaceInfo().colorSpace())) {
+        return false;
+    }
+
+    // Insert a draw to a temporary surface if we need to do a y-flip (and in future for a color
+    // space conversion.)
+    if (this->origin() == kBottomLeft_GrSurfaceOrigin) {
+        sk_sp<GrTextureProxy> texProxy = sk_ref_sp(fRenderTargetProxy->asTextureProxy());
+        const auto& backendFormat = fRenderTargetProxy->backendFormat();
+        SkRect srcRectToDraw = SkRect::Make(srcRect);
+        // If the src is not texturable first try to make a copy to a texture.
+        if (!texProxy) {
+            GrSurfaceDesc desc;
+            desc.fWidth = srcRect.width();
+            desc.fHeight = srcRect.height();
+            desc.fConfig = fRenderTargetProxy->config();
+            auto sContext = direct->priv().makeDeferredSurfaceContext(
+                    backendFormat, desc, this->origin(), GrMipMapped::kNo, SkBackingFit::kApprox,
+                    SkBudgeted::kNo, this->colorSpaceInfo().refColorSpace());
+            if (!sContext) {
+                return false;
+            }
+            if (!sContext->copy(fRenderTargetProxy.get(), srcRect, {0, 0})) {
+                return false;
+            }
+            texProxy = sk_ref_sp(sContext->asTextureProxy());
+            SkASSERT(texProxy);
+            srcRectToDraw = SkRect::MakeWH(srcRect.width(), srcRect.height());
+        }
+        auto rtc = direct->priv().makeDeferredRenderTargetContext(
+                backendFormat, SkBackingFit::kApprox, srcRect.width(), srcRect.height(),
+                fRenderTargetProxy->config(), cs, 1, GrMipMapped::kNo, kTopLeft_GrSurfaceOrigin);
+        if (!rtc) {
+            return false;
+        }
+        rtc->drawTexture(GrNoClip(), std::move(texProxy), GrSamplerState::Filter::kNearest,
+                         SkBlendMode::kSrc, SK_PMColor4fWHITE, srcRectToDraw,
+                         SkRect::MakeWH(srcRect.width(), srcRect.height()), GrAA::kNo,
+                         GrQuadAAFlags::kNone, SkCanvas::kFast_SrcRectConstraint, SkMatrix::I(),
+                         /* colorSpaceXform = */ nullptr);
+        return rtc->asyncReadPixels(ct, at, std::move(cs),
+                                    SkIRect::MakeWH(srcRect.width(), srcRect.height()), callback,
+                                    context);
+    }
+    size_t rowBytes = GrColorTypeBytesPerPixel(dstCT) * srcRect.width();
+    size_t size = rowBytes * srcRect.height();
+    auto buffer = direct->priv().resourceProvider()->createBuffer(
+            size, GrGpuBufferType::kXferGpuToCpu, GrAccessPattern::kStream_GrAccessPattern);
+    if (!buffer) {
+        return false;
+    }
+    this->getRTOpList()->addOp(GrTransferFromOp::Make(fContext, srcRect, dstCT, buffer, 0),
+                               *this->caps());
+    struct FinishContext {
+        ReadPixelsCallback* fClientCallback;
+        ReadPixelsContext fClientContext;
+        sk_sp<GrGpuBuffer> fBuffer;
+        size_t fRowBytes;
+    };
+    // 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, buffer, rowBytes};
+    auto finishCallback = [](GrGpuFinishedContext c) {
+        auto context = reinterpret_cast<const FinishContext*>(c);
+        void* data = context->fBuffer->map();
+        (*context->fClientCallback)(context->fClientContext, data, data ? context->fRowBytes : 0);
+        if (data) {
+            context->fBuffer->unmap();
+        }
+        delete context;
+    };
+    GrFlushInfo flushInfo;
+    flushInfo.fFinishedContext = finishContext;
+    flushInfo.fFinishedProc = finishCallback;
+    this->flush(SkSurface::BackendSurfaceAccess::kNoAccess, flushInfo);
+    return true;
+}
+
 GrSemaphoresSubmitted GrRenderTargetContext::flush(SkSurface::BackendSurfaceAccess access,
                                                    const GrFlushInfo& info) {
     ASSERT_SINGLE_OWNER
diff --git a/src/gpu/GrRenderTargetContext.h b/src/gpu/GrRenderTargetContext.h
index 8b467db..6f39325 100644
--- a/src/gpu/GrRenderTargetContext.h
+++ b/src/gpu/GrRenderTargetContext.h
@@ -402,6 +402,11 @@
      */
     void drawDrawable(std::unique_ptr<SkDrawable::GpuDrawHandler>, const SkRect& bounds);
 
+    using ReadPixelsCallback = SkSurface::ReadPixelsCallback;
+    using ReadPixelsContext = SkSurface::ReadPixelsContext;
+    bool asyncReadPixels(SkColorType, SkAlphaType, sk_sp<SkColorSpace>, const SkIRect& srcRect,
+                         ReadPixelsCallback, ReadPixelsContext);
+
     /**
      * 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.
diff --git a/src/gpu/gl/GrGLGpuCommandBuffer.h b/src/gpu/gl/GrGLGpuCommandBuffer.h
index 02c16d7..0f9370f 100644
--- a/src/gpu/gl/GrGLGpuCommandBuffer.h
+++ b/src/gpu/gl/GrGLGpuCommandBuffer.h
@@ -26,6 +26,12 @@
         fGpu->copySurface(fTexture, fOrigin, src, srcOrigin, srcRect, dstPoint);
     }
 
+    void transferFrom(const SkIRect& srcRect, GrColorType bufferColorType,
+                      GrGpuBuffer* transferBuffer, size_t offset) override {
+        fGpu->transferPixelsFrom(fTexture, srcRect.fLeft, srcRect.fTop, srcRect.width(),
+                                 srcRect.height(), bufferColorType, transferBuffer, offset);
+    }
+
     void insertEventMarker(const char* msg) override {
         fGpu->insertEventMarker(msg);
     }
@@ -67,6 +73,12 @@
         fGpu->copySurface(fRenderTarget, fOrigin, src, srcOrigin, srcRect, dstPoint);
     }
 
+    void transferFrom(const SkIRect& srcRect, GrColorType bufferColorType,
+                      GrGpuBuffer* transferBuffer, size_t offset) override {
+        fGpu->transferPixelsFrom(fRenderTarget, srcRect.fLeft, srcRect.fTop, srcRect.width(),
+                                 srcRect.height(), bufferColorType, transferBuffer, offset);
+    }
+
     void set(GrRenderTarget*, GrSurfaceOrigin,
              const GrGpuRTCommandBuffer::LoadAndStoreInfo&,
              const GrGpuRTCommandBuffer::StencilLoadAndStoreInfo&);
diff --git a/src/gpu/mock/GrMockGpuCommandBuffer.h b/src/gpu/mock/GrMockGpuCommandBuffer.h
index 0a13f42..9e2905e 100644
--- a/src/gpu/mock/GrMockGpuCommandBuffer.h
+++ b/src/gpu/mock/GrMockGpuCommandBuffer.h
@@ -21,6 +21,8 @@
 
     void copy(GrSurface* src, GrSurfaceOrigin srcOrigin, const SkIRect& srcRect,
               const SkIPoint& dstPoint) override {}
+    void transferFrom(const SkIRect& srcRect, GrColorType bufferColorType,
+                      GrGpuBuffer* transferBuffer, size_t offset) override {}
     void insertEventMarker(const char*) override {}
 
 private:
@@ -42,6 +44,8 @@
     void end() override {}
     void copy(GrSurface* src, GrSurfaceOrigin srcOrigin, const SkIRect& srcRect,
               const SkIPoint& dstPoint) override {}
+    void transferFrom(const SkIRect& srcRect, GrColorType bufferColorType,
+                      GrGpuBuffer* transferBuffer, size_t offset) override {}
 
     int numDraws() const { return fNumDraws; }
 
diff --git a/src/gpu/mtl/GrMtlGpuCommandBuffer.h b/src/gpu/mtl/GrMtlGpuCommandBuffer.h
index 5a6bc36..6f85603 100644
--- a/src/gpu/mtl/GrMtlGpuCommandBuffer.h
+++ b/src/gpu/mtl/GrMtlGpuCommandBuffer.h
@@ -33,7 +33,11 @@
               const SkIPoint& dstPoint) override {
         fGpu->copySurface(fTexture, fOrigin, src, srcOrigin, srcRect, dstPoint);
     }
-
+    void transferFrom(const SkIRect& srcRect, GrColorType bufferColorType,
+                      GrGpuBuffer* transferBuffer, size_t offset) override {
+        fGpu->transferPixelsFrom(fTexture, srcRect.fLeft, srcRect.fTop, srcRect.width(),
+                                 srcRect.height(), bufferColorType, transferBuffer, offset);
+    }
     void insertEventMarker(const char* msg) override {}
 
 private:
@@ -62,7 +66,8 @@
         // TODO: this could be more efficient
         state->doUpload(upload);
     }
-
+    void transferFrom(const SkIRect& srcRect, GrColorType bufferColorType,
+                      GrGpuBuffer* transferBuffer, size_t offset) override;
     void copy(GrSurface* src, GrSurfaceOrigin srcOrigin, const SkIRect& srcRect,
               const SkIPoint& dstPoint) override;
 
diff --git a/src/gpu/mtl/GrMtlGpuCommandBuffer.mm b/src/gpu/mtl/GrMtlGpuCommandBuffer.mm
index b20f0ce..7479d19 100644
--- a/src/gpu/mtl/GrMtlGpuCommandBuffer.mm
+++ b/src/gpu/mtl/GrMtlGpuCommandBuffer.mm
@@ -96,6 +96,15 @@
     fGpu->copySurface(fRenderTarget, fOrigin, src, srcOrigin, srcRect, dstPoint);
 }
 
+void GrMtlGpuRTCommandBuffer::transferFrom(const SkIRect& srcRect, GrColorType bufferColorType,
+                                           GrGpuBuffer* transferBuffer, size_t offset) {
+    // We cannot have an active encoder when we call transferFrom since it requires its own
+    // command encoder.
+    SkASSERT(nil == fActiveRenderCmdEncoder);
+    fGpu->transferPixelsFrom(fRenderTarget, srcRect.fLeft, srcRect.fTop, srcRect.width(),
+                             srcRect.height(), bufferColorType, transferBuffer, offset);
+}
+
 GrMtlPipelineState* GrMtlGpuRTCommandBuffer::prepareDrawState(
         const GrPrimitiveProcessor& primProc,
         const GrPipeline& pipeline,
diff --git a/src/gpu/ops/GrCopySurfaceOp.h b/src/gpu/ops/GrCopySurfaceOp.h
index 3581031..3c6194d 100644
--- a/src/gpu/ops/GrCopySurfaceOp.h
+++ b/src/gpu/ops/GrCopySurfaceOp.h
@@ -30,12 +30,12 @@
 #ifdef SK_DEBUG
     SkString dumpInfo() const override {
         SkString string;
-        string.append(INHERITED::dumpInfo());
-        string.printf("srcProxyID: %d,\n"
-                      "srcRect: [ L: %d, T: %d, R: %d, B: %d ], dstPt: [ X: %d, Y: %d ]\n",
-                      fSrc.get()->uniqueID().asUInt(),
-                      fSrcRect.fLeft, fSrcRect.fTop, fSrcRect.fRight, fSrcRect.fBottom,
-                      fDstPoint.fX, fDstPoint.fY);
+        string = INHERITED::dumpInfo();
+        string.appendf(
+                "srcProxyID: %d,\n"
+                "srcRect: [ L: %d, T: %d, R: %d, B: %d ], dstPt: [ X: %d, Y: %d ]\n",
+                fSrc.get()->uniqueID().asUInt(), fSrcRect.fLeft, fSrcRect.fTop, fSrcRect.fRight,
+                fSrcRect.fBottom, fDstPoint.fX, fDstPoint.fY);
         return string;
     }
 #endif
diff --git a/src/gpu/ops/GrTransferFromOp.cpp b/src/gpu/ops/GrTransferFromOp.cpp
new file mode 100644
index 0000000..e1ddfc5
--- /dev/null
+++ b/src/gpu/ops/GrTransferFromOp.cpp
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/gpu/ops/GrTransferFromOp.h"
+#include "include/private/GrRecordingContext.h"
+#include "src/gpu/GrCaps.h"
+#include "src/gpu/GrGpuCommandBuffer.h"
+#include "src/gpu/GrMemoryPool.h"
+#include "src/gpu/GrRecordingContextPriv.h"
+
+std::unique_ptr<GrOp> GrTransferFromOp::Make(GrRecordingContext* context,
+                                             const SkIRect& srcRect,
+                                             GrColorType dstColorType,
+                                             sk_sp<GrGpuBuffer> dstBuffer,
+                                             size_t dstOffset) {
+    SkASSERT(context->priv().caps()->transferFromOffsetAlignment(dstColorType));
+    SkASSERT(dstOffset % context->priv().caps()->transferFromOffsetAlignment(dstColorType) == 0);
+    GrOpMemoryPool* pool = context->priv().opMemoryPool();
+    return pool->allocate<GrTransferFromOp>(srcRect, dstColorType, std::move(dstBuffer), dstOffset);
+}
+
+void GrTransferFromOp::onExecute(GrOpFlushState* state, const SkRect& chainBounds) {
+    state->commandBuffer()->transferFrom(fSrcRect, fDstColorType, fDstBuffer.get(), fDstOffset);
+}
diff --git a/src/gpu/ops/GrTransferFromOp.h b/src/gpu/ops/GrTransferFromOp.h
new file mode 100644
index 0000000..34d8f54
--- /dev/null
+++ b/src/gpu/ops/GrTransferFromOp.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrTransferFromOp_DEFINED
+#define GrTransferFromOp_DEFINED
+
+#include "src/gpu/GrOpFlushState.h"
+#include "src/gpu/ops/GrOp.h"
+
+/**
+ * Does a transfer from the surface context's surface to a transfer buffer. It is assumed
+ * that the caller has checked the GrCaps to ensure this transfer is legal.
+ */
+class GrTransferFromOp final : public GrOp {
+public:
+    DEFINE_OP_CLASS_ID
+
+    static std::unique_ptr<GrOp> Make(GrRecordingContext*,
+                                      const SkIRect& srcRect,
+                                      GrColorType dstColorType,
+                                      sk_sp<GrGpuBuffer> dstBuffer,
+                                      size_t dstOffset);
+
+    const char* name() const override { return "TransferFromOp"; }
+
+#ifdef SK_DEBUG
+    SkString dumpInfo() const override {
+        SkString string;
+        string = INHERITED::dumpInfo();
+        string.appendf(
+                "bufferID:: %d offset: %zu, color type: %d\n"
+                "srcRect: [ L: %d, T: %d, R: %d, B: %d ]\n",
+                fDstBuffer->uniqueID().asUInt(), fDstOffset, fDstColorType, fSrcRect.fLeft,
+                fSrcRect.fTop, fSrcRect.fRight, fSrcRect.fBottom);
+        return string;
+    }
+#endif
+
+private:
+    friend class GrOpMemoryPool;  // for ctor
+
+    GrTransferFromOp(const SkIRect& srcRect,
+                     GrColorType dstColorType,
+                     sk_sp<GrGpuBuffer> dstBuffer,
+                     size_t dstOffset)
+            : INHERITED(ClassID())
+            , fDstBuffer(std::move(dstBuffer))
+            , fDstOffset(dstOffset)
+            , fSrcRect(srcRect)
+            , fDstColorType(dstColorType) {
+        this->setBounds(SkRect::Make(srcRect), HasAABloat::kNo, IsZeroArea::kNo);
+    }
+
+    void onPrepare(GrOpFlushState*) override {}
+
+    void onExecute(GrOpFlushState*, const SkRect& chainBounds) override;
+
+    sk_sp<GrGpuBuffer> fDstBuffer;
+    size_t fDstOffset;
+    SkIRect fSrcRect;
+    GrColorType fDstColorType;
+
+    typedef GrOp INHERITED;
+};
+
+#endif
diff --git a/src/gpu/vk/GrVkCommandBuffer.cpp b/src/gpu/vk/GrVkCommandBuffer.cpp
index 4cad381..315c991 100644
--- a/src/gpu/vk/GrVkCommandBuffer.cpp
+++ b/src/gpu/vk/GrVkCommandBuffer.cpp
@@ -649,7 +649,6 @@
     VkResult err = GR_VK_CALL(gpu->vkInterface(), GetFenceStatus(gpu->device(), fSubmitFence));
     switch (err) {
         case VK_SUCCESS:
-            fFinishedProcs.reset();
             return true;
 
         case VK_NOT_READY:
@@ -672,6 +671,7 @@
     for (int i = 0; i < fSecondaryCommandBuffers.count(); ++i) {
         fSecondaryCommandBuffers[i]->releaseResources(gpu);
     }
+    fFinishedProcs.reset();
 }
 
 void GrVkPrimaryCommandBuffer::recycleSecondaryCommandBuffers() {
diff --git a/src/gpu/vk/GrVkGpuCommandBuffer.cpp b/src/gpu/vk/GrVkGpuCommandBuffer.cpp
index ab2ad06..c95f1e5 100644
--- a/src/gpu/vk/GrVkGpuCommandBuffer.cpp
+++ b/src/gpu/vk/GrVkGpuCommandBuffer.cpp
@@ -36,7 +36,6 @@
 public:
     InlineUpload(GrOpFlushState* state, const GrDeferredTextureUploadFn& upload)
             : fFlushState(state), fUpload(upload) {}
-    ~InlineUpload() override = default;
 
     void execute(const Args& args) override { fFlushState->doUpload(fUpload); }
 
@@ -54,7 +53,6 @@
             , fSrcRect(srcRect)
             , fDstPoint(dstPoint)
             , fShouldDiscardDst(shouldDiscardDst) {}
-    ~Copy() override = default;
 
     void execute(const Args& args) override {
         args.fGpu->copySurface(args.fSurface, args.fOrigin, fSrc.get(), fSrcOrigin, fSrcRect,
@@ -70,6 +68,28 @@
     bool fShouldDiscardDst;
 };
 
+class TransferFrom : public GrVkPrimaryCommandBufferTask {
+public:
+    TransferFrom(const SkIRect& srcRect, GrColorType bufferColorType, GrGpuBuffer* transferBuffer,
+                 size_t offset)
+            : fTransferBuffer(sk_ref_sp(transferBuffer))
+            , fOffset(offset)
+            , fSrcRect(srcRect)
+            , fBufferColorType(bufferColorType) {}
+
+    void execute(const Args& args) override {
+        args.fGpu->transferPixelsFrom(args.fSurface, fSrcRect.fLeft, fSrcRect.fTop,
+                                      fSrcRect.width(), fSrcRect.height(), fBufferColorType,
+                                      fTransferBuffer.get(), fOffset);
+    }
+
+private:
+    sk_sp<GrGpuBuffer> fTransferBuffer;
+    size_t fOffset;
+    SkIRect fSrcRect;
+    GrColorType fBufferColorType;
+};
+
 }  // anonymous namespace
 
 /////////////////////////////////////////////////////////////////////////////
@@ -79,6 +99,11 @@
     fTasks.emplace<Copy>(src, srcOrigin, srcRect, dstPoint, false);
 }
 
+void GrVkGpuTextureCommandBuffer::transferFrom(const SkIRect& srcRect, GrColorType bufferColorType,
+                                               GrGpuBuffer* transferBuffer, size_t offset) {
+    fTasks.emplace<TransferFrom>(srcRect, bufferColorType, transferBuffer, offset);
+}
+
 void GrVkGpuTextureCommandBuffer::insertEventMarker(const char* msg) {
     // TODO: does Vulkan have a correlate?
 }
@@ -620,6 +645,16 @@
     }
 }
 
+void GrVkGpuRTCommandBuffer::transferFrom(const SkIRect& srcRect, GrColorType bufferColorType,
+                                          GrGpuBuffer* transferBuffer, size_t offset) {
+    CommandBufferInfo& cbInfo = fCommandBufferInfos[fCurrentCmdInfo];
+    if (!cbInfo.fIsEmpty) {
+        this->addAdditionalRenderPass();
+    }
+    fPreCommandBufferTasks.emplace<TransferFrom>(srcRect, bufferColorType, transferBuffer, offset);
+    ++fCommandBufferInfos[fCurrentCmdInfo].fNumPreCmds;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 
 void GrVkGpuRTCommandBuffer::bindGeometry(const GrGpuBuffer* indexBuffer,
diff --git a/src/gpu/vk/GrVkGpuCommandBuffer.h b/src/gpu/vk/GrVkGpuCommandBuffer.h
index 8ab9d7b..76c1284 100644
--- a/src/gpu/vk/GrVkGpuCommandBuffer.h
+++ b/src/gpu/vk/GrVkGpuCommandBuffer.h
@@ -48,6 +48,8 @@
 
     void copy(GrSurface* src, GrSurfaceOrigin srcOrigin, const SkIRect& srcRect,
               const SkIPoint& dstPoint) override;
+    void transferFrom(const SkIRect& srcRect, GrColorType bufferColorType,
+                      GrGpuBuffer* transferBuffer, size_t offset) override;
 
     void insertEventMarker(const char*) override;
 
@@ -81,6 +83,8 @@
 
     void copy(GrSurface* src, GrSurfaceOrigin srcOrigin, const SkIRect& srcRect,
               const SkIPoint& dstPoint) override;
+    void transferFrom(const SkIRect& srcRect, GrColorType bufferColorType,
+                      GrGpuBuffer* transferBuffer, size_t offset) override;
 
     void executeDrawable(std::unique_ptr<SkDrawable::GpuDrawHandler>) override;
 
diff --git a/src/image/SkSurface.cpp b/src/image/SkSurface.cpp
index dd8ac6f..cd214e8 100644
--- a/src/image/SkSurface.cpp
+++ b/src/image/SkSurface.cpp
@@ -5,12 +5,13 @@
  * found in the LICENSE file.
  */
 
+#include <atomic>
 #include "include/core/SkCanvas.h"
 #include "include/core/SkFontLCDConfig.h"
 #include "include/gpu/GrBackendSurface.h"
+#include "src/core/SkAutoPixmapStorage.h"
 #include "src/core/SkImagePriv.h"
 #include "src/image/SkSurface_Base.h"
-#include <atomic>
 
 static SkPixelGeometry compute_default_geometry() {
     SkFontLCDConfig::LCDOrder order = SkFontLCDConfig::GetSubpixelOrder();
@@ -86,6 +87,19 @@
     }
 }
 
+void SkSurface_Base::onAsyncReadPixels(SkColorType ct, SkAlphaType at, sk_sp<SkColorSpace> cs,
+                                       const SkIRect& rect, ReadPixelsCallback callback,
+                                       ReadPixelsContext context) {
+    auto info = SkImageInfo::Make(rect.width(), rect.height(), ct, at, std::move(cs));
+    SkAutoPixmapStorage pm;
+    pm.alloc(info);
+    if (this->readPixels(pm, rect.fLeft, rect.fTop)) {
+        callback(context, pm.addr(), pm.rowBytes());
+    } else {
+        callback(context, nullptr, 0);
+    }
+}
+
 bool SkSurface_Base::outstandingImageSnapshot() const {
     return fCachedImage && !fCachedImage->unique();
 }
@@ -207,6 +221,18 @@
     return bitmap.peekPixels(&pm) && this->readPixels(pm, srcX, srcY);
 }
 
+void SkSurface::asyncReadPixels(SkColorType ct, SkAlphaType at, sk_sp<SkColorSpace> cs,
+                                const SkIRect& srcRect, ReadPixelsCallback callback,
+                                ReadPixelsContext context) {
+    auto dstII = SkImageInfo::Make(srcRect.width(), srcRect.height(), ct, at, cs);
+    if (!SkIRect::MakeWH(this->width(), this->height()).contains(srcRect) ||
+        !SkImageInfoIsValid(dstII)) {
+        callback(context, nullptr, 0);
+        return;
+    }
+    asSB(this)->onAsyncReadPixels(ct, at, std::move(cs), srcRect, callback, context);
+}
+
 void SkSurface::writePixels(const SkPixmap& pmap, int x, int y) {
     if (pmap.addr() == nullptr || pmap.width() <= 0 || pmap.height() <= 0) {
         return;
diff --git a/src/image/SkSurface_Base.h b/src/image/SkSurface_Base.h
index b38169b..1c895ee 100644
--- a/src/image/SkSurface_Base.h
+++ b/src/image/SkSurface_Base.h
@@ -46,6 +46,13 @@
     virtual void onWritePixels(const SkPixmap&, int x, int y) = 0;
 
     /**
+     * Default implementation does a synchronous read and calls the callback.
+     */
+    virtual void onAsyncReadPixels(SkColorType, SkAlphaType, sk_sp<SkColorSpace>,
+                                   const SkIRect& srcRect, ReadPixelsCallback callback,
+                                   ReadPixelsContext context);
+
+    /**
      *  Default implementation:
      *
      *  image = this->newImageSnapshot();
diff --git a/src/image/SkSurface_Gpu.cpp b/src/image/SkSurface_Gpu.cpp
index 7a3c24a..2690a6d 100644
--- a/src/image/SkSurface_Gpu.cpp
+++ b/src/image/SkSurface_Gpu.cpp
@@ -132,6 +132,19 @@
     fDevice->writePixels(src, x, y);
 }
 
+void SkSurface_Gpu::onAsyncReadPixels(SkColorType ct, SkAlphaType at, sk_sp<SkColorSpace> cs,
+                                      const SkIRect& srcRect, ReadPixelsCallback callback,
+                                      ReadPixelsContext context) {
+    auto* rtc = fDevice->accessRenderTargetContext();
+    if (!rtc->caps()->transferBufferSupport()) {
+        INHERITED::onAsyncReadPixels(ct, at, cs, srcRect, callback, context);
+        return;
+    }
+    if (!rtc->asyncReadPixels(ct, at, std::move(cs), srcRect, callback, context)) {
+        callback(context, nullptr, 0);
+    }
+}
+
 // Create a new render target and, if necessary, copy the contents of the old
 // render target into it. Note that this flushes the SkGpuDevice but
 // doesn't force an OpenGL flush.
diff --git a/src/image/SkSurface_Gpu.h b/src/image/SkSurface_Gpu.h
index 799de37..d10947c 100644
--- a/src/image/SkSurface_Gpu.h
+++ b/src/image/SkSurface_Gpu.h
@@ -30,6 +30,9 @@
     sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override;
     sk_sp<SkImage> onNewImageSnapshot(const SkIRect* subset) override;
     void onWritePixels(const SkPixmap&, int x, int y) override;
+    void onAsyncReadPixels(SkColorType, SkAlphaType, sk_sp<SkColorSpace>, const SkIRect& rect,
+                           ReadPixelsCallback, ReadPixelsContext) override;
+
     void onCopyOnWrite(ContentChangeMode) override;
     void onDiscard() override;
     GrSemaphoresSubmitted onFlush(BackendSurfaceAccess access, const GrFlushInfo& info) override;