Rework transfer pixels tests to be more robust.

Make them query caps for supported read/write info and do CPU
conversions before uploading/after reading.

Removes use of GrColor so in theory could be used to test
non-8888 color types.

Bug: skia:6718

Change-Id: Icf9d0b778348a4e960fbfec49e1308b21e45a051
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/227497
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
diff --git a/src/gpu/GrDataUtils.h b/src/gpu/GrDataUtils.h
index a1f4eeb..5417ac9 100644
--- a/src/gpu/GrDataUtils.h
+++ b/src/gpu/GrDataUtils.h
@@ -53,6 +53,10 @@
         return {this->colorType(), at, this->refColorSpace(), this->width(), this->height()};
     }
 
+    GrPixelInfo makeWH(int width, int height) {
+        return {this->colorType(), this->alphaType(), this->refColorSpace(), width, height};
+    }
+
     GrColorType colorType() const { return fColorInfo.colorType(); }
 
     SkAlphaType alphaType() const { return fColorInfo.alphaType(); }
diff --git a/tests/TransferPixelsTest.cpp b/tests/TransferPixelsTest.cpp
index fdfda42..8759a29 100644
--- a/tests/TransferPixelsTest.cpp
+++ b/tests/TransferPixelsTest.cpp
@@ -9,6 +9,7 @@
 
 #include "include/core/SkTypes.h"
 
+#include "TestUtils.h"
 #include "include/core/SkSurface.h"
 #include "include/gpu/GrTexture.h"
 #include "src/gpu/GrContextPriv.h"
@@ -22,43 +23,49 @@
 using sk_gpu_test::GrContextFactory;
 
 void fill_transfer_data(int left, int top, int width, int height, int bufferWidth,
-                        GrColor* data) {
-
+                        GrColorType dstType, char* dst) {
+    size_t dstBpp = GrColorTypeBytesPerPixel(dstType);
+    auto dstLocation = [dst, dstBpp, bufferWidth](int x, int y) {
+        return dst + y * dstBpp * bufferWidth + x * dstBpp;
+    };
     // 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);
+            uint32_t srcPixel = GrColorPackRGBA(red - (red>>8),
+                                                green - (green>>8), 0xff, 0xff);
+            GrPixelInfo srcInfo(GrColorType::kRGBA_8888, kPremul_SkAlphaType, nullptr, 1, 1);
+            GrPixelInfo dstInfo(dstType, kPremul_SkAlphaType, nullptr, 1, 1);
+            GrConvertPixels(dstInfo, dstLocation(i, j), dstBpp, srcInfo, &srcPixel, 4);
         }
     }
 }
 
-bool do_buffers_contain_same_values(const GrColor* bufferA,
-                                    const GrColor* bufferB,
-                                    int width,
-                                    int height,
-                                    size_t rowBytesA,
-                                    size_t rowBytesB,
-                                    bool swiz) {
-    for (int j = 0; j < height; ++j) {
-        for (int i = 0; i < width; ++i) {
-            auto colorA = bufferA[i];
-            if (swiz) {
-                colorA = GrColorPackRGBA(GrColorUnpackB(colorA), GrColorUnpackG(colorA),
-                                         GrColorUnpackR(colorA), GrColorUnpackA(colorA));
-            }
-            if (colorA != bufferB[i]) {
-                return false;
-            }
+bool read_pixels_from_texture(GrTexture* texture, GrColorType colorType, char* dst) {
+    auto* context = texture->getContext();
+    auto* gpu = context->priv().getGpu();
+    auto* caps = context->priv().caps();
+
+    int w = texture->width();
+    int h = texture->height();
+    size_t rowBytes = GrColorTypeBytesPerPixel(colorType) * w;
+
+    GrCaps::SupportedRead supportedRead = caps->supportedReadPixelsColorType(
+            texture->config(), texture->backendFormat(), colorType);
+    if (supportedRead.fColorType != colorType || supportedRead.fSwizzle != GrSwizzle("rgba")) {
+        size_t tmpRowBytes = GrColorTypeBytesPerPixel(supportedRead.fColorType) * w;
+        std::unique_ptr<char[]> tmpPixels(new char[tmpRowBytes * h]);
+        if (!gpu->readPixels(texture, 0, 0, w, h,
+                             supportedRead.fColorType, tmpPixels.get(), tmpRowBytes)) {
+            return false;
         }
-        bufferA = reinterpret_cast<const GrColor*>(reinterpret_cast<const char*>(bufferA) +
-                                                   rowBytesA);
-        bufferB = reinterpret_cast<const GrColor*>(reinterpret_cast<const char*>(bufferB) +
-                                                   rowBytesB);
+        GrPixelInfo tmpInfo(supportedRead.fColorType, kPremul_SkAlphaType, nullptr, w, h);
+        GrPixelInfo dstInfo(colorType,                kPremul_SkAlphaType, nullptr, w, h);
+        return GrConvertPixels(dstInfo, dst, rowBytes, tmpInfo, tmpPixels.get(), tmpRowBytes, false,
+                               supportedRead.fSwizzle);
     }
-    return true;
+    return gpu->readPixels(texture, 0, 0, w, h, supportedRead.fColorType, dst, rowBytes);
 }
 
 void basic_transfer_to_test(skiatest::Reporter* reporter, GrContext* context, GrColorType colorType,
@@ -67,38 +74,25 @@
         return;
     }
 
-    auto resourceProvider = context->priv().resourceProvider();
-    GrGpu* gpu = context->priv().getGpu();
+    auto* caps = context->priv().caps();
 
-    // set up the data
-    const int kTextureWidth = 16;
-    const int kTextureHeight = 16;
-#ifdef SK_BUILD_FOR_IOS
-    // UNPACK_ROW_LENGTH is broken on iOS so rowBytes needs to match data width
-    const int kBufferWidth = GrBackendApi::kOpenGL == context->backend() ? 16 : 20;
-#else
-    const int kBufferWidth = 20;
-#endif
-    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 = rowBytes*kBufferHeight;
-    sk_sp<GrGpuBuffer> buffer(resourceProvider->createBuffer(size, GrGpuBufferType::kXferCpuToGpu,
-                                                             kDynamic_GrAccessPattern));
-    if (!buffer) {
+    auto backendFormat = caps->getBackendFormatFromColorType(colorType);
+    if (!caps->isFormatTexturable(colorType, backendFormat)) {
         return;
     }
 
-    void* data = buffer->map();
-    memcpy(data, srcBuffer.get(), size);
-    buffer->unmap();
+    // if ((renderTarget && !caps->isFormatRenderable(colorType, backendFormat))) {
+    //        return;
+    //    }
 
-    // create texture
+    auto resourceProvider = context->priv().resourceProvider();
+    GrGpu* gpu = context->priv().getGpu();
+
+    const int kTextureWidth = 16;
+    const int kTextureHeight = 16;
+    int srcBufferWidth = caps->writePixelsRowBytesSupport() ? 16 : 20;
+    const int kBufferHeight = 16;
+
     GrSurfaceDesc desc;
     desc.fFlags = renderTarget ? kRenderTarget_GrSurfaceFlag : kNone_GrSurfaceFlags;
     desc.fWidth = kTextureWidth;
@@ -106,92 +100,110 @@
     desc.fConfig = GrColorTypeToPixelConfig(colorType);
     desc.fSampleCnt = 1;
 
-    if (!context->priv().caps()->isConfigTexturable(desc.fConfig) ||
-        (renderTarget && !context->priv().caps()->isConfigRenderable(desc.fConfig))) {
+    // TODO: Replace with isFormatRenderable when available.
+    if ((renderTarget && !caps->isConfigRenderable(desc.fConfig))) {
         return;
     }
 
-    sk_sp<GrTexture> tex = resourceProvider->createTexture(
-        desc, SkBudgeted::kNo, GrResourceProvider::Flags::kNoPendingIO);
+    sk_sp<GrTexture> tex = resourceProvider->createTexture(desc, SkBudgeted::kNo,
+                                                           GrResourceProvider::Flags::kNoPendingIO);
     if (!tex) {
+        ERRORF(reporter, "Could not create texture");
         return;
     }
 
+    // The caps tell us what color type we are allowed to upload and read back from this texture,
+    // either of which may differ from 'colorType'.
+    GrColorType allowedSrc = caps->supportedWritePixelsColorType(desc.fConfig, colorType);
+    size_t srcRowBytes = GrColorTypeBytesPerPixel(allowedSrc) * kTextureWidth;
+    std::unique_ptr<char[]> srcData(new char[kTextureWidth * srcRowBytes]);
+
+    fill_transfer_data(0, 0, kTextureWidth, kTextureHeight, srcBufferWidth, allowedSrc,
+                       srcData.get());
+
+    // create and fill transfer buffer
+    size_t size = srcRowBytes * kBufferHeight;
+    sk_sp<GrGpuBuffer> buffer(resourceProvider->createBuffer(size, GrGpuBufferType::kXferCpuToGpu,
+                                                             kDynamic_GrAccessPattern));
+    if (!buffer) {
+        return;
+    }
+    void* data = buffer->map();
+    if (!buffer) {
+        ERRORF(reporter, "Could not map buffer");
+        return;
+    }
+    memcpy(data, srcData.get(), size);
+    buffer->unmap();
+
     //////////////////////////
     // transfer full data
 
     bool result;
-    result = gpu->transferPixelsTo(tex.get(), 0, 0, kTextureWidth, kTextureHeight, colorType,
-                                   buffer.get(), 0, rowBytes);
+    result = gpu->transferPixelsTo(tex.get(), 0, 0, kTextureWidth, kTextureHeight, allowedSrc,
+                                   buffer.get(), 0, srcRowBytes);
     REPORTER_ASSERT(reporter, result);
 
-    memset(dstBuffer.get(), 0xCDCD, size);
-    result = gpu->readPixels(tex.get(), 0, 0, kTextureWidth, kTextureHeight, colorType,
-                             dstBuffer.get(), rowBytes);
-    if (result) {
-        REPORTER_ASSERT(reporter, do_buffers_contain_same_values(srcBuffer,
-                                                                 dstBuffer,
-                                                                 kTextureWidth,
-                                                                 kTextureHeight,
-                                                                 rowBytes,
-                                                                 rowBytes,
-                                                                 false));
+    size_t dstRowBytes = GrColorTypeBytesPerPixel(colorType) * kTextureWidth;
+    std::unique_ptr<char[]> dstBuffer(new char[dstRowBytes * kTextureHeight]());
+
+    result = read_pixels_from_texture(tex.get(), colorType, dstBuffer.get());
+    if (!result) {
+        ERRORF(reporter, "Could not read pixels from texture");
+        return;
     }
 
+    static constexpr float kTol[4] = {};
+    auto error = std::function<ComparePixmapsErrorReporter>(
+            [reporter, colorType](int x, int y, const float diffs[4]) {
+                ERRORF(reporter, "Error at (%d %d) in transfer, color type: %d", x, y, colorType);
+            });
+    GrPixelInfo srcInfo(allowedSrc, kPremul_SkAlphaType, nullptr, tex->width(), tex->height());
+    GrPixelInfo dstInfo(colorType, kPremul_SkAlphaType, nullptr, tex->width(), tex->height());
+
+    compare_pixels(srcInfo, srcData.get(), srcRowBytes, dstInfo, dstBuffer.get(), dstRowBytes, kTol,
+                   error);
+
     //////////////////////////
     // transfer partial data
-#ifdef SK_BUILD_FOR_IOS
-    // UNPACK_ROW_LENGTH is broken on iOS so we can't do partial transfers
-    if (GrBackendApi::kOpenGL == context->backend()) {
+
+    // We're relying on this cap to write partial texture data
+    if (!caps->writePixelsRowBytesSupport()) {
         return;
     }
-#endif
     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());
+    fill_transfer_data(kLeft, kTop, kWidth, kHeight, srcBufferWidth, allowedSrc, srcData.get());
     data = buffer->map();
-    memcpy(data, srcBuffer.get(), size);
+    memcpy(data, srcData.get(), size);
     buffer->unmap();
 
-    size_t offset = sizeof(GrColor) * (kTop * kBufferWidth + kLeft);
-    result = gpu->transferPixelsTo(tex.get(), kLeft, kTop, kWidth, kHeight, colorType,
-                                   buffer.get(), offset, rowBytes);
+    size_t offset = kTop * srcRowBytes + kLeft * GrColorTypeBytesPerPixel(allowedSrc);
+    result = gpu->transferPixelsTo(tex.get(), kLeft, kTop, kWidth, kHeight, allowedSrc,
+                                   buffer.get(), offset, srcRowBytes);
     REPORTER_ASSERT(reporter, result);
 
-    memset(dstBuffer.get(), 0xCDCD, size);
-    result = gpu->readPixels(tex.get(), 0, 0, kTextureWidth, kTextureHeight, colorType,
-                             dstBuffer.get(), rowBytes);
-    if (result) {
-        REPORTER_ASSERT(reporter, do_buffers_contain_same_values(srcBuffer,
-                                                                 dstBuffer,
-                                                                 kTextureWidth,
-                                                                 kTextureHeight,
-                                                                 rowBytes,
-                                                                 rowBytes,
-                                                                 false));
+    result = read_pixels_from_texture(tex.get(), colorType, dstBuffer.get());
+    if (!result) {
+        ERRORF(reporter, "Could not read pixels from texture");
+        return;
     }
+    compare_pixels(srcInfo, srcData.get(), srcRowBytes, dstInfo, dstBuffer.get(), dstRowBytes, kTol,
+                   error);
 }
 
 void basic_transfer_from_test(skiatest::Reporter* reporter, const sk_gpu_test::ContextInfo& ctxInfo,
                               GrColorType colorType, bool renderTarget) {
     auto context = ctxInfo.grContext();
-    if (GrCaps::kNone_MapFlags == context->priv().caps()->mapBufferFlags()) {
+    auto caps = context->priv().caps();
+    if (GrCaps::kNone_MapFlags == caps->mapBufferFlags()) {
         return;
     }
 
-    // On OpenGL ES it may not be possible to read back in to BGRA becagse GL_RGBA/GL_UNSIGNED_BYTE
-    // may be the only allowed format/type params to glReadPixels. So read back into GL_RGBA.
-    // TODO(bsalomon): Make this work in GrGLGpu.
-    auto readColorType = colorType;
-    if (GrColorType::kBGRA_8888 == colorType &&
-        ctxInfo.type() == sk_gpu_test::GrContextFactory::kGLES_ContextType) {
-        readColorType = GrColorType::kRGBA_8888;
-    }
-
     auto resourceProvider = context->priv().resourceProvider();
     GrGpu* gpu = context->priv().getGpu();
 
@@ -205,10 +217,42 @@
     const int kPartialWidth = 10;
     const int kPartialHeight = 2;
 
-    size_t bpp = GrColorTypeBytesPerPixel(readColorType);
+    // create texture
+    GrSurfaceDesc desc;
+    desc.fFlags = renderTarget ? kRenderTarget_GrSurfaceFlag : kNone_GrSurfaceFlags;
+    desc.fWidth = kTextureWidth;
+    desc.fHeight = kTextureHeight;
+    desc.fConfig = GrColorTypeToPixelConfig(colorType);
+    desc.fSampleCnt = 1;
+
+    if (!context->priv().caps()->isConfigTexturable(desc.fConfig) ||
+        (renderTarget && !context->priv().caps()->isConfigRenderable(desc.fConfig))) {
+        return;
+    }
+
+    size_t textureDataBpp = GrColorTypeBytesPerPixel(colorType);
+    size_t textureDataRowBytes = kTextureWidth * textureDataBpp;
+    std::unique_ptr<char[]> textureData(new char[kTextureHeight * textureDataRowBytes]);
+    fill_transfer_data(0, 0, kTextureWidth, kTextureHeight, kTextureWidth, colorType,
+                       textureData.get());
+    GrMipLevel data;
+    data.fPixels = textureData.get();
+    data.fRowBytes = textureDataRowBytes;
+    sk_sp<GrTexture> tex = resourceProvider->createTexture(desc, SkBudgeted::kNo, &data, 1);
+    if (!tex) {
+        return;
+    }
+
+    // Create the transfer buffer.
+    auto allowedRead =
+            caps->supportedReadPixelsColorType(desc.fConfig, tex->backendFormat(), colorType);
+    GrPixelInfo readInfo(allowedRead.fColorType, kPremul_SkAlphaType, nullptr, kTextureWidth,
+                         kTextureHeight);
+
+    size_t bpp = GrColorTypeBytesPerPixel(allowedRead.fColorType);
     size_t fullBufferRowBytes = kTextureWidth * bpp;
     size_t partialBufferRowBytes = kPartialWidth * bpp;
-    size_t offsetAlignment = context->priv().caps()->transferFromOffsetAlignment(readColorType);
+    size_t offsetAlignment = caps->transferFromOffsetAlignment(allowedRead.fColorType);
     SkASSERT(offsetAlignment);
 
     size_t bufferSize = fullBufferRowBytes * kTextureHeight;
@@ -225,34 +269,11 @@
 
     int expectedTransferCnt = 0;
     gpu->stats()->reset();
-    // create texture
-    GrSurfaceDesc desc;
-    desc.fFlags = renderTarget ? kRenderTarget_GrSurfaceFlag : kNone_GrSurfaceFlags;
-    desc.fWidth = kTextureWidth;
-    desc.fHeight = kTextureHeight;
-    desc.fConfig = GrColorTypeToPixelConfig(colorType);
-    desc.fSampleCnt = 1;
-
-    if (!context->priv().caps()->isConfigTexturable(desc.fConfig) ||
-        (renderTarget && !context->priv().caps()->isConfigRenderable(desc.fConfig))) {
-        return;
-    }
-
-    SkAutoTMalloc<GrColor> textureData(kTextureWidth * kTextureHeight);
-    size_t textureDataRowBytes = kTextureWidth * sizeof(GrColor);
-    fill_transfer_data(0, 0, kTextureWidth, kTextureHeight, kTextureWidth, textureData.get());
-    GrMipLevel data;
-    data.fPixels = textureData.get();
-    data.fRowBytes = kTextureWidth * sizeof(GrColor);
-    sk_sp<GrTexture> tex = resourceProvider->createTexture(desc, SkBudgeted::kNo, &data, 1);
-    if (!tex) {
-        return;
-    }
 
     //////////////////////////
     // transfer full data
     bool result = gpu->transferPixelsFrom(tex.get(), 0, 0, kTextureWidth, kTextureHeight,
-                                          readColorType, buffer.get(), 0);
+                                          allowedRead.fColorType, buffer.get(), 0);
     if (!result) {
         ERRORF(reporter, "transferPixelsFrom failed.");
         return;
@@ -266,24 +287,39 @@
                          GrPrepareForExternalIORequests());
     }
 
-    const auto* map = reinterpret_cast<const GrColor*>(buffer->map());
+    // Copy the transfer buffer contents to a temporary so we can manipulate it.
+    const auto* map = reinterpret_cast<const char*>(buffer->map());
     REPORTER_ASSERT(reporter, map);
     if (!map) {
+        ERRORF(reporter, "Failed to map transfer buffer.");
         return;
     }
-    REPORTER_ASSERT(reporter, do_buffers_contain_same_values(textureData.get(),
-                                                             map,
-                                                             kTextureWidth,
-                                                             kTextureHeight,
-                                                             textureDataRowBytes,
-                                                             fullBufferRowBytes,
-                                                             readColorType != colorType));
+    std::unique_ptr<char[]> transferData(new char[kTextureHeight * fullBufferRowBytes]);
+    memcpy(transferData.get(), map, fullBufferRowBytes * kTextureHeight);
     buffer->unmap();
 
+    GrPixelInfo transferInfo(allowedRead.fColorType, kPremul_SkAlphaType, nullptr, kTextureWidth,
+                             kTextureHeight);
+    // Caps may indicate that we should swizzle this data before we compare it.
+    if (allowedRead.fSwizzle != GrSwizzle("rgba")) {
+        GrConvertPixels(transferInfo, transferData.get(), fullBufferRowBytes, transferInfo,
+                        transferData.get(), fullBufferRowBytes, false, allowedRead.fSwizzle);
+    }
+
+    static constexpr float kTol[4] = {};
+    auto error = std::function<ComparePixmapsErrorReporter>(
+            [reporter, colorType](int x, int y, const float diffs[4]) {
+                ERRORF(reporter, "Error at (%d %d) in transfer, color type: %d", x, y, colorType);
+            });
+    GrPixelInfo textureDataInfo(colorType, kPremul_SkAlphaType, nullptr, kTextureWidth,
+                                kTextureHeight);
+    compare_pixels(textureDataInfo, textureData.get(), textureDataRowBytes, transferInfo,
+                   transferData.get(), fullBufferRowBytes, kTol, error);
+
     ///////////////////////
     // Now test a partial read at an offset into the buffer.
     result = gpu->transferPixelsFrom(tex.get(), kPartialLeft, kPartialTop, kPartialWidth,
-                                     kPartialHeight, readColorType, buffer.get(),
+                                     kPartialHeight, allowedRead.fColorType, buffer.get(),
                                      partialReadOffset);
     if (!result) {
         ERRORF(reporter, "transferPixelsFrom failed.");
@@ -296,24 +332,27 @@
                          GrPrepareForExternalIORequests());
     }
 
-    map = reinterpret_cast<const GrColor*>(buffer->map());
+    map = reinterpret_cast<const char*>(buffer->map());
     REPORTER_ASSERT(reporter, map);
     if (!map) {
+        ERRORF(reporter, "Failed to map transfer buffer.");
         return;
     }
-    const GrColor* textureDataStart = reinterpret_cast<const GrColor*>(
-            reinterpret_cast<const char*>(textureData.get()) +
-            textureDataRowBytes * kPartialTop + sizeof(GrColor) * kPartialLeft);
-    const GrColor* bufferStart = reinterpret_cast<const GrColor*>(
-            reinterpret_cast<const char*>(map) + partialReadOffset);
-    REPORTER_ASSERT(reporter, do_buffers_contain_same_values(textureDataStart,
-                                                             bufferStart,
-                                                             kPartialWidth,
-                                                             kPartialHeight,
-                                                             textureDataRowBytes,
-                                                             partialBufferRowBytes,
-                                                             readColorType != colorType));
+    const char* bufferStart = reinterpret_cast<const char*>(map) + partialReadOffset;
+    memcpy(transferData.get(), bufferStart, partialBufferRowBytes * kTextureHeight);
     buffer->unmap();
+
+    transferInfo = transferInfo.makeWH(kPartialWidth, kPartialHeight);
+    if (allowedRead.fSwizzle != GrSwizzle("rgba")) {
+        GrConvertPixels(transferInfo, transferData.get(), fullBufferRowBytes, transferInfo,
+                        transferData.get(), fullBufferRowBytes, false, allowedRead.fSwizzle);
+    }
+
+    const char* textureDataStart =
+            textureData.get() + textureDataRowBytes * kPartialTop + textureDataBpp * kPartialLeft;
+    textureDataInfo = textureDataInfo.makeWH(kPartialWidth, kPartialHeight);
+    compare_pixels(textureDataInfo, textureDataStart, textureDataRowBytes, transferInfo,
+                   transferData.get(), partialBufferRowBytes, kTol, error);
 #if GR_GPU_STATS
     REPORTER_ASSERT(reporter, gpu->stats()->transfersFromSurface() == expectedTransferCnt);
 #else