Clean up onTransferPixels

Bug: skia:5126
Change-Id: I323c50e7854744302007b4ae7bd25e5742c14cbc
Reviewed-on: https://skia-review.googlesource.com/19055
Commit-Queue: Jim Van Verth <jvanverth@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
diff --git a/gn/tests.gni b/gn/tests.gni
index 80af714..6843a6f 100644
--- a/gn/tests.gni
+++ b/gn/tests.gni
@@ -252,6 +252,7 @@
   "$_tests/TopoSortTest.cpp",
   "$_tests/TraceMemoryDumpTest.cpp",
   "$_tests/TracingTest.cpp",
+  "$_tests/TransferPixelsTest.cpp",
   "$_tests/TypefaceTest.cpp",
   "$_tests/UnicodeTest.cpp",
   "$_tests/UtilsTest.cpp",
diff --git a/src/gpu/GrGpu.cpp b/src/gpu/GrGpu.cpp
index 262f155..e0766a7 100644
--- a/src/gpu/GrGpu.cpp
+++ b/src/gpu/GrGpu.cpp
@@ -389,30 +389,24 @@
     return this->writePixels(surface, left, top, width, height, config, texels);
 }
 
-bool GrGpu::transferPixels(GrSurface* surface,
+bool GrGpu::transferPixels(GrTexture* texture,
                            int left, int top, int width, int height,
                            GrPixelConfig config, GrBuffer* transferBuffer,
-                           size_t offset, size_t rowBytes, GrFence* fence) {
+                           size_t offset, size_t rowBytes) {
     SkASSERT(transferBuffer);
-    SkASSERT(fence);
 
     // We don't allow conversion between integer configs and float/fixed configs.
-    if (GrPixelConfigIsSint(surface->config()) != GrPixelConfigIsSint(config)) {
+    if (GrPixelConfigIsSint(texture->config()) != GrPixelConfigIsSint(config)) {
         return false;
     }
 
     this->handleDirtyContext();
-    if (this->onTransferPixels(surface, left, top, width, height, config,
+    if (this->onTransferPixels(texture, left, top, width, height, config,
                                transferBuffer, offset, rowBytes)) {
         SkIRect rect = SkIRect::MakeXYWH(left, top, width, height);
-        this->didWriteToSurface(surface, &rect);
+        this->didWriteToSurface(texture, &rect);
         fStats.incTransfersToTexture();
 
-        if (*fence) {
-            this->deleteFence(*fence);
-        }
-        *fence = this->insertFence();
-
         return true;
     }
     return false;
diff --git a/src/gpu/GrGpu.h b/src/gpu/GrGpu.h
index 418b8b4..d413fd6 100644
--- a/src/gpu/GrGpu.h
+++ b/src/gpu/GrGpu.h
@@ -291,23 +291,26 @@
                      size_t rowBytes);
 
     /**
-     * Updates the pixels in a rectangle of a surface using a buffer
+     * Updates the pixels in a rectangle of a texture using a buffer
      *
-     * @param surface          The surface to write to.
+     * There are a couple of assumptions here. First, we only update the top miplevel.
+     * And second, that any y flip needed has already been done in the buffer.
+     *
+     * @param texture          The texture to write to.
      * @param left             left edge of the rectangle to write (inclusive)
      * @param top              top edge of the rectangle to write (inclusive)
      * @param width            width of rectangle to write in pixels.
      * @param height           height of rectangle to write in pixels.
      * @param config           the pixel config of the source buffer
-     * @param transferBuffer   GrBuffer to read pixels from (type must be "kCpuToGpu")
+     * @param transferBuffer   GrBuffer to read pixels from (type must be "kXferCpuToGpu")
      * @param offset           offset from the start of the buffer
-     * @param rowBytes         number of bytes between consecutive rows. Zero
+     * @param rowBytes         number of bytes between consecutive rows in the buffer. Zero
      *                         means rows are tightly packed.
      */
-    bool transferPixels(GrSurface* surface,
+    bool transferPixels(GrTexture* texture,
                         int left, int top, int width, int height,
                         GrPixelConfig config, GrBuffer* transferBuffer,
-                        size_t offset, size_t rowBytes, GrFence* fence);
+                        size_t offset, size_t rowBytes);
 
     // After the client interacts directly with the 3D context state the GrGpu
     // must resync its internal state and assumptions about 3D context state.
@@ -588,8 +591,8 @@
                                GrPixelConfig config,
                                const SkTArray<GrMipLevel>& texels) = 0;
 
-    // overridden by backend-specific derived class to perform the surface write
-    virtual bool onTransferPixels(GrSurface*,
+    // overridden by backend-specific derived class to perform the texture transfer
+    virtual bool onTransferPixels(GrTexture*,
                                   int left, int top, int width, int height,
                                   GrPixelConfig config, GrBuffer* transferBuffer,
                                   size_t offset, size_t rowBytes) = 0;
diff --git a/src/gpu/gl/GrGLBuffer.cpp b/src/gpu/gl/GrGLBuffer.cpp
index 7dfc6b8..180dc39 100644
--- a/src/gpu/gl/GrGLBuffer.cpp
+++ b/src/gpu/gl/GrGLBuffer.cpp
@@ -31,6 +31,12 @@
 
 GrGLBuffer* GrGLBuffer::Create(GrGLGpu* gpu, size_t size, GrBufferType intendedType,
                                GrAccessPattern accessPattern, const void* data) {
+    if (gpu->glCaps().transferBufferType() == GrGLCaps::kNone_TransferBufferType &&
+        (kXferCpuToGpu_GrBufferType == intendedType ||
+         kXferGpuToCpu_GrBufferType == intendedType)) {
+        return nullptr;
+    }
+
     sk_sp<GrGLBuffer> buffer(new GrGLBuffer(gpu, size, intendedType, accessPattern, data));
     if (0 == buffer->bufferID()) {
         return nullptr;
diff --git a/src/gpu/gl/GrGLCaps.cpp b/src/gpu/gl/GrGLCaps.cpp
index 95791f1..faea4e6 100644
--- a/src/gpu/gl/GrGLCaps.cpp
+++ b/src/gpu/gl/GrGLCaps.cpp
@@ -457,10 +457,14 @@
             fTransferBufferType = kPBO_TransferBufferType;
         }
     } else {
-        if (version >= GR_GL_VER(3, 0) || ctxInfo.hasExtension("GL_NV_pixel_buffer_object")) {
+        if (version >= GR_GL_VER(3, 0) ||
+            (ctxInfo.hasExtension("GL_NV_pixel_buffer_object") &&
+             // GL_EXT_unpack_subimage needed to support subtexture rectangles
+             ctxInfo.hasExtension("GL_EXT_unpack_subimage"))) {
             fTransferBufferType = kPBO_TransferBufferType;
-        } else if (ctxInfo.hasExtension("GL_CHROMIUM_pixel_transfer_buffer_object")) {
-            fTransferBufferType = kChromium_TransferBufferType;
+// TODO: get transfer buffers working in Chrome
+//        } else if (ctxInfo.hasExtension("GL_CHROMIUM_pixel_transfer_buffer_object")) {
+//            fTransferBufferType = kChromium_TransferBufferType;
         }
     }
 
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index e5e8841..f63fc25 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -783,36 +783,6 @@
                                left, top, width, height, config, texels);
 }
 
-bool GrGLGpu::onTransferPixels(GrSurface* surface,
-                               int left, int top, int width, int height,
-                               GrPixelConfig config, GrBuffer* transferBuffer,
-                               size_t offset, size_t rowBytes) {
-    GrGLTexture* glTex = static_cast<GrGLTexture*>(surface->asTexture());
-
-    if (!check_write_and_transfer_input(glTex, surface, config)) {
-        return false;
-    }
-
-    this->setScratchTextureUnit();
-    GL_CALL(BindTexture(glTex->target(), glTex->textureID()));
-
-    SkASSERT(!transferBuffer->isMapped());
-    SkASSERT(!transferBuffer->isCPUBacked());
-    const GrGLBuffer* glBuffer = static_cast<const GrGLBuffer*>(transferBuffer);
-    this->bindBuffer(kXferCpuToGpu_GrBufferType, glBuffer);
-
-    bool success = false;
-    GrMipLevel mipLevel;
-    mipLevel.fPixels = transferBuffer;
-    mipLevel.fRowBytes = rowBytes;
-    SkSTArray<1, GrMipLevel> texels;
-    texels.push_back(mipLevel);
-    success = this->uploadTexData(glTex->config(), glTex->width(), glTex->height(), glTex->origin(),
-                                  glTex->target(), kTransfer_UploadType, left, top, width, height,
-                                  config, texels);
-    return success;
-}
-
 // For GL_[UN]PACK_ALIGNMENT.
 static inline GrGLint config_alignment(GrPixelConfig config) {
     switch (config) {
@@ -839,6 +809,78 @@
     return 0;
 }
 
+bool GrGLGpu::onTransferPixels(GrTexture* texture,
+                               int left, int top, int width, int height,
+                               GrPixelConfig config, GrBuffer* transferBuffer,
+                               size_t offset, size_t rowBytes) {
+    GrGLTexture* glTex = static_cast<GrGLTexture*>(texture);
+    GrPixelConfig texConfig = glTex->config();
+    SkASSERT(this->caps()->isConfigTexturable(texConfig));
+
+    if (!check_write_and_transfer_input(glTex, texture, config)) {
+        return false;
+    }
+
+    if (width <= 0 || width > SK_MaxS32 || height <= 0 || height > SK_MaxS32) {
+        return false;
+    }
+
+    this->setScratchTextureUnit();
+    GL_CALL(BindTexture(glTex->target(), glTex->textureID()));
+
+    SkASSERT(!transferBuffer->isMapped());
+    SkASSERT(!transferBuffer->isCPUBacked());
+    const GrGLBuffer* glBuffer = static_cast<const GrGLBuffer*>(transferBuffer);
+    this->bindBuffer(kXferCpuToGpu_GrBufferType, glBuffer);
+
+    size_t bpp = GrBytesPerPixel(config);
+    const size_t trimRowBytes = width * bpp;
+    const void* pixels = (void*)offset;
+    if (!GrSurfacePriv::AdjustWritePixelParams(glTex->width(), glTex->height(), bpp,
+                                               &left, &top,
+                                               &width, &height,
+                                               &pixels,
+                                               &rowBytes)) {
+        return false;
+    }
+    if (width < 0 || width < 0) {
+        return false;
+    }
+
+    bool restoreGLRowLength = false;
+    if (trimRowBytes != rowBytes) {
+        // we should have checked for this support already
+        SkASSERT(this->glCaps().unpackRowLengthSupport());
+        GL_CALL(PixelStorei(GR_GL_UNPACK_ROW_LENGTH, rowBytes / bpp));
+        restoreGLRowLength = true;
+    }
+
+    // Internal format comes from the texture desc.
+    GrGLenum internalFormat;
+    // External format and type come from the upload data.
+    GrGLenum externalFormat;
+    GrGLenum externalType;
+    if (!this->glCaps().getTexImageFormats(texConfig, config, &internalFormat,
+                                           &externalFormat, &externalType)) {
+        return false;
+    }
+
+    GL_CALL(PixelStorei(GR_GL_UNPACK_ALIGNMENT, config_alignment(texConfig)));
+    GL_CALL(TexSubImage2D(glTex->target(),
+                          0,
+                          left, top,
+                          width,
+                          height,
+                          externalFormat, externalType,
+                          pixels));
+
+    if (restoreGLRowLength) {
+        GL_CALL(PixelStorei(GR_GL_UNPACK_ROW_LENGTH, 0));
+    }
+
+    return true;
+}
+
 /**
  * Creates storage space for the texture and fills it with texels.
  *
@@ -971,6 +1013,13 @@
                             const SkTArray<GrMipLevel>& texels) {
     SkASSERT(this->caps()->isConfigTexturable(texConfig));
 
+    // unbind any previous transfer buffer
+    auto& xferBufferState = fHWBufferState[kXferCpuToGpu_GrBufferType];
+    if (!xferBufferState.fBoundBufferUniqueID.isInvalid()) {
+        GL_CALL(BindBuffer(xferBufferState.fGLTarget, 0));
+        xferBufferState.invalidate();
+    }
+
     // texels is const.
     // But we may need to flip the texture vertically to prepare it.
     // Rather than flip in place and alter the incoming data,
@@ -980,7 +1029,7 @@
 
     for (int currentMipLevel = texelsShallowCopy.count() - 1; currentMipLevel >= 0;
          currentMipLevel--) {
-        SkASSERT(texelsShallowCopy[currentMipLevel].fPixels || kTransfer_UploadType == uploadType);
+        SkASSERT(texelsShallowCopy[currentMipLevel].fPixels);
     }
 
     const GrGLInterface* interface = this->glInterface();
@@ -1086,30 +1135,26 @@
                 GR_GL_CALL(interface, PixelStorei(GR_GL_UNPACK_ROW_LENGTH, rowLength));
                 restoreGLRowLength = true;
             }
-        } else if (kTransfer_UploadType != uploadType) {
-            if (trimRowBytes != rowBytes || swFlipY) {
-                // copy data into our new storage, skipping the trailing bytes
-                const char* src = (const char*)texelsShallowCopy[currentMipLevel].fPixels;
-                if (swFlipY && currentHeight >= 1) {
-                    src += (currentHeight - 1) * rowBytes;
-                }
-                char* dst = buffer + individual_mip_offsets[currentMipLevel];
-                for (int y = 0; y < currentHeight; y++) {
-                    memcpy(dst, src, trimRowBytes);
-                    if (swFlipY) {
-                        src -= rowBytes;
-                    } else {
-                        src += rowBytes;
-                    }
-                    dst += trimRowBytes;
-                }
-                // now point data to our copied version
-                texelsShallowCopy[currentMipLevel].fPixels = buffer +
-                    individual_mip_offsets[currentMipLevel];
-                texelsShallowCopy[currentMipLevel].fRowBytes = trimRowBytes;
+        } else if (trimRowBytes != rowBytes || swFlipY) {
+            // copy data into our new storage, skipping the trailing bytes
+            const char* src = (const char*)texelsShallowCopy[currentMipLevel].fPixels;
+            if (swFlipY && currentHeight >= 1) {
+                src += (currentHeight - 1) * rowBytes;
             }
-        } else {
-            return false;
+            char* dst = buffer + individual_mip_offsets[currentMipLevel];
+            for (int y = 0; y < currentHeight; y++) {
+                memcpy(dst, src, trimRowBytes);
+                if (swFlipY) {
+                    src -= rowBytes;
+                } else {
+                    src += rowBytes;
+                }
+                dst += trimRowBytes;
+            }
+            // now point data to our copied version
+            texelsShallowCopy[currentMipLevel].fPixels = buffer +
+                individual_mip_offsets[currentMipLevel];
+            texelsShallowCopy[currentMipLevel].fRowBytes = trimRowBytes;
         }
     }
 
diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h
index 80a12eb..5897f5d 100644
--- a/src/gpu/gl/GrGLGpu.h
+++ b/src/gpu/gl/GrGLGpu.h
@@ -247,7 +247,7 @@
                        GrPixelConfig config,
                        const SkTArray<GrMipLevel>& texels) override;
 
-    bool onTransferPixels(GrSurface*,
+    bool onTransferPixels(GrTexture*,
                           int left, int top, int width, int height,
                           GrPixelConfig config, GrBuffer* transferBuffer,
                           size_t offset, size_t rowBytes) override;
@@ -373,9 +373,8 @@
 
     // helper for onCreateTexture and writeTexturePixels
     enum UploadType {
-        kNewTexture_UploadType,    // we are creating a new texture
-        kWrite_UploadType,         // we are using TexSubImage2D to copy data to an existing texture
-        kTransfer_UploadType,      // we are using a transfer buffer to copy data
+        kNewTexture_UploadType,   // we are creating a new texture
+        kWrite_UploadType,        // we are using TexSubImage2D to copy data to an existing texture
     };
     bool uploadTexData(GrPixelConfig texConfig, int texWidth, int texHeight,
                        GrSurfaceOrigin texOrigin, GrGLenum target, UploadType uploadType, int left,
diff --git a/src/gpu/vk/GrVkGpu.cpp b/src/gpu/vk/GrVkGpu.cpp
index b10e9ed..d237632 100644
--- a/src/gpu/vk/GrVkGpu.cpp
+++ b/src/gpu/vk/GrVkGpu.cpp
@@ -301,11 +301,13 @@
             buff = GrVkIndexBuffer::Create(this, size, kDynamic_GrAccessPattern == accessPattern);
             break;
         case kXferCpuToGpu_GrBufferType:
-            SkASSERT(kStream_GrAccessPattern == accessPattern);
+            SkASSERT(kDynamic_GrAccessPattern == accessPattern ||
+                     kStream_GrAccessPattern == accessPattern);
             buff = GrVkTransferBuffer::Create(this, size, GrVkBuffer::kCopyRead_Type);
             break;
         case kXferGpuToCpu_GrBufferType:
-            SkASSERT(kStream_GrAccessPattern == accessPattern);
+            SkASSERT(kDynamic_GrAccessPattern == accessPattern ||
+                     kStream_GrAccessPattern == accessPattern);
             buff = GrVkTransferBuffer::Create(this, size, GrVkBuffer::kCopyWrite_Type);
             break;
         case kTexel_GrBufferType:
@@ -420,6 +422,62 @@
     return success;
 }
 
+bool GrVkGpu::onTransferPixels(GrTexture* texture,
+                               int left, int top, int width, int height,
+                               GrPixelConfig config, GrBuffer* transferBuffer,
+                               size_t bufferOffset, size_t rowBytes) {
+    // Vulkan only supports 4-byte aligned offsets
+    if (SkToBool(bufferOffset & 0x2)) {
+        return false;
+    }
+    GrVkTexture* vkTex = static_cast<GrVkTexture*>(texture);
+    if (!vkTex) {
+        return false;
+    }
+    GrVkTransferBuffer* vkBuffer = static_cast<GrVkTransferBuffer*>(transferBuffer);
+    if (!vkBuffer) {
+        return false;
+    }
+
+    // We assume Vulkan doesn't do sRGB <-> linear conversions when reading and writing pixels.
+    if (GrPixelConfigIsSRGB(texture->config()) != GrPixelConfigIsSRGB(config)) {
+        return false;
+    }
+
+    size_t bpp = GrBytesPerPixel(config);
+    if (rowBytes == 0) {
+        rowBytes = bpp*width;
+    }
+
+    // Set up copy region
+    VkBufferImageCopy region;
+    memset(&region, 0, sizeof(VkBufferImageCopy));
+    region.bufferOffset = bufferOffset;
+    region.bufferRowLength = (uint32_t)(rowBytes/bpp);
+    region.bufferImageHeight = 0;
+    region.imageSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 };
+    region.imageOffset = { left, top, 0 };
+    region.imageExtent = { (uint32_t)width, (uint32_t)height, 1 };
+
+    // Change layout of our target so it can be copied to
+    vkTex->setImageLayout(this,
+                          VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+                          VK_ACCESS_TRANSFER_WRITE_BIT,
+                          VK_PIPELINE_STAGE_TRANSFER_BIT,
+                          false);
+
+    // Copy the buffer to the image
+    fCurrentCmdBuffer->copyBufferToImage(this,
+                                         vkBuffer,
+                                         vkTex,
+                                         VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+                                         1,
+                                         &region);
+
+    vkTex->texturePriv().dirtyMipMaps(true);
+    return true;
+}
+
 void GrVkGpu::resolveImage(GrSurface* dst, GrVkRenderTarget* src, const SkIRect& srcRect,
                            const SkIPoint& dstPoint) {
     SkASSERT(dst);
diff --git a/src/gpu/vk/GrVkGpu.h b/src/gpu/vk/GrVkGpu.h
index 236b34a..8a3fb09 100644
--- a/src/gpu/vk/GrVkGpu.h
+++ b/src/gpu/vk/GrVkGpu.h
@@ -203,10 +203,10 @@
                        int left, int top, int width, int height,
                        GrPixelConfig config, const SkTArray<GrMipLevel>&) override;
 
-    bool onTransferPixels(GrSurface*,
+    bool onTransferPixels(GrTexture*,
                           int left, int top, int width, int height,
                           GrPixelConfig config, GrBuffer* transferBuffer,
-                          size_t offset, size_t rowBytes) override { return false; }
+                          size_t offset, size_t rowBytes) override;
 
     // Ends and submits the current command buffer to the queue and then creates a new command
     // buffer and begins it. If sync is set to kForce_SyncQueue, the function will wait for all
diff --git a/tests/TransferPixelsTest.cpp b/tests/TransferPixelsTest.cpp
new file mode 100755
index 0000000..2e75dec
--- /dev/null
+++ b/tests/TransferPixelsTest.cpp
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+// This is a GPU-backend specific test. It relies on static intializers to work
+
+#include "SkTypes.h"
+
+#if SK_SUPPORT_GPU && SK_ALLOW_STATIC_GLOBAL_INITIALIZERS
+
+#include "GrContextFactory.h"
+#include "GrContextPriv.h"
+#include "GrGpu.h"
+#include "GrResourceProvider.h"
+#include "GrSurfaceProxy.h"
+#include "GrTexture.h"
+#include "GrTest.h"
+#include "SkGr.h"
+#include "SkSurface.h"
+#include "Test.h"
+
+using sk_gpu_test::GrContextFactory;
+
+void fill_transfer_data(int left, int top, int width, int height, int bufferWidth,
+                        GrColor* data) {
+
+    // build red-green gradient
+    for (int j = top; j < top + height; ++j) {
+        for (int i = left; i < left + width; ++i) {
+            unsigned int red = (unsigned int)(256.f*((i - left) / (float)width));
+            unsigned int green = (unsigned int)(256.f*((j - top) / (float)height));
+            data[i + j*bufferWidth] = GrColorPackRGBA(red - (red>>8),
+                                                      green - (green>>8), 0xff, 0xff);
+        }
+    }
+}
+
+bool does_full_buffer_contain_correct_values(GrColor* srcBuffer,
+                                             GrColor* dstBuffer,
+                                             int width,
+                                             int height,
+                                             int bufferWidth,
+                                             int bufferHeight,
+                                             GrSurfaceOrigin origin) {
+    GrColor* srcPtr = srcBuffer;
+    bool bottomUp = SkToBool(kBottomLeft_GrSurfaceOrigin == origin);
+    GrColor* dstPtr = bottomUp ? dstBuffer + bufferWidth*(bufferHeight-1) : dstBuffer;
+    int dstIncrement = bottomUp ? -bufferWidth : +bufferWidth;
+
+    for (int j = 0; j < height; ++j) {
+        for (int i = 0; i < width; ++i) {
+            if (srcPtr[i] != dstPtr[i]) {
+                return false;
+            }
+        }
+        srcPtr += bufferWidth;
+        dstPtr += dstIncrement;
+    }
+    return true;
+}
+
+void basic_transfer_test(skiatest::Reporter* reporter, GrContext* context, GrPixelConfig config,
+                         GrSurfaceOrigin origin, bool renderTarget) {
+    // set up the data
+    const int kTextureWidth = 16;
+    const int kTextureHeight = 16;
+    const int kBufferWidth = 20;
+    const int kBufferHeight = 16;
+    size_t rowBytes = kBufferWidth * sizeof(GrColor);
+    SkAutoTMalloc<GrColor> srcBuffer(kBufferWidth*kBufferHeight);
+    SkAutoTMalloc<GrColor> dstBuffer(kBufferWidth*kBufferHeight);
+
+    fill_transfer_data(0, 0, kTextureWidth, kTextureHeight, kBufferWidth, srcBuffer.get());
+
+    // create and fill transfer buffer
+    size_t size = GrBytesPerPixel(config)*kBufferWidth*kBufferWidth;
+    uint32_t bufferFlags = GrResourceProvider::kNoPendingIO_Flag;
+    GrBuffer* buffer = context->resourceProvider()->createBuffer(size,
+                                                                 kXferCpuToGpu_GrBufferType,
+                                                                 kDynamic_GrAccessPattern,
+                                                                 bufferFlags);
+    void* data = buffer->map();
+    memcpy(data, srcBuffer.get(), size);
+    buffer->unmap();
+
+    // create texture
+    GrSurfaceDesc desc;
+    desc.fConfig = config;
+    desc.fFlags = renderTarget ? kRenderTarget_GrSurfaceFlag : kNone_GrSurfaceFlags;
+    desc.fOrigin = origin;
+    desc.fWidth = kTextureWidth;
+    desc.fHeight = kTextureHeight;
+    desc.fSampleCnt = 0;
+    sk_sp<GrTexture> tex = context->resourceProvider()->createTexture(desc, SkBudgeted::kNo);
+
+    //////////////////////////
+    // transfer full data
+
+    bool result;
+    result = context->getGpu()->transferPixels(tex.get(), 0, 0, kTextureWidth, kTextureHeight,
+                                               config, buffer, 0, rowBytes);
+    REPORTER_ASSERT(reporter, result);
+
+    result = context->getGpu()->readPixels(tex.get(), 0, 0, kTextureWidth, kTextureHeight, config,
+                                           dstBuffer.get(), rowBytes);
+    REPORTER_ASSERT(reporter, result);
+    REPORTER_ASSERT(reporter, does_full_buffer_contain_correct_values(srcBuffer,
+                                                                      dstBuffer,
+                                                                      kTextureWidth,
+                                                                      kTextureHeight,
+                                                                      kBufferWidth,
+                                                                      kBufferHeight,
+                                                                      origin));
+    //////////////////////////
+    // transfer partial data
+
+    const int kLeft = 2;
+    const int kTop = 10;
+    const int kWidth = 10;
+    const int kHeight = 2;
+
+    // change color of subrectangle
+    fill_transfer_data(kLeft, kTop, kWidth, kHeight, kBufferWidth, srcBuffer.get());
+    data = buffer->map();
+    memcpy(data, srcBuffer.get(), size);
+    buffer->unmap();
+
+    size_t offset = sizeof(GrColor)*(kTop*kBufferWidth + kLeft);
+    result = context->getGpu()->transferPixels(tex.get(), kLeft, kTop, kWidth, kHeight, config,
+                                               buffer, offset, rowBytes);
+    REPORTER_ASSERT(reporter, result);
+
+    memset(dstBuffer, 0, kWidth*kHeight*sizeof(GrColor));
+
+    result = context->getGpu()->readPixels(tex.get(), 0, 0, kTextureWidth, kTextureHeight, config,
+                                           dstBuffer.get(), rowBytes);
+    REPORTER_ASSERT(reporter, result);
+
+    REPORTER_ASSERT(reporter, does_full_buffer_contain_correct_values(srcBuffer,
+                                                                      dstBuffer,
+                                                                      kTextureWidth,
+                                                                      kTextureHeight,
+                                                                      kBufferWidth,
+                                                                      kBufferHeight,
+                                                                      origin));
+}
+
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(TransferPixelsTest, reporter, ctxInfo) {
+    // RGBA
+    basic_transfer_test(reporter, ctxInfo.grContext(), kRGBA_8888_GrPixelConfig,
+                        kTopLeft_GrSurfaceOrigin, false);
+    basic_transfer_test(reporter, ctxInfo.grContext(), kRGBA_8888_GrPixelConfig,
+                        kTopLeft_GrSurfaceOrigin, true);
+    basic_transfer_test(reporter, ctxInfo.grContext(), kRGBA_8888_GrPixelConfig,
+                        kBottomLeft_GrSurfaceOrigin, false);
+    basic_transfer_test(reporter, ctxInfo.grContext(), kRGBA_8888_GrPixelConfig,
+                        kBottomLeft_GrSurfaceOrigin, true);
+
+    // BGRA
+    basic_transfer_test(reporter, ctxInfo.grContext(), kBGRA_8888_GrPixelConfig,
+                        kTopLeft_GrSurfaceOrigin, false);
+    basic_transfer_test(reporter, ctxInfo.grContext(), kBGRA_8888_GrPixelConfig,
+                        kTopLeft_GrSurfaceOrigin, true);
+    basic_transfer_test(reporter, ctxInfo.grContext(), kBGRA_8888_GrPixelConfig,
+                        kBottomLeft_GrSurfaceOrigin, false);
+    basic_transfer_test(reporter, ctxInfo.grContext(), kBGRA_8888_GrPixelConfig,
+                        kBottomLeft_GrSurfaceOrigin, true);
+}
+
+#endif
diff --git a/tools/gpu/GrTest.cpp b/tools/gpu/GrTest.cpp
index 93e5f6e..301ba67 100644
--- a/tools/gpu/GrTest.cpp
+++ b/tools/gpu/GrTest.cpp
@@ -402,7 +402,7 @@
         return false;
     }
 
-    bool onTransferPixels(GrSurface* surface,
+    bool onTransferPixels(GrTexture* texture,
                           int left, int top, int width, int height,
                           GrPixelConfig config, GrBuffer* transferBuffer,
                           size_t offset, size_t rowBytes) override {