Fix SW decompression of BC1 and add a GM to check it.

Bug: skia:9680
Change-Id: I8a8f9ee1aad783fc247c5516ac1effd0dab25936
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/268158
Commit-Queue: Robert Phillips <robertphillips@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
diff --git a/gm/bc1_transparency.cpp b/gm/bc1_transparency.cpp
new file mode 100644
index 0000000..f6d3d7e
--- /dev/null
+++ b/gm/bc1_transparency.cpp
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm/gm.h"
+#include "include/core/SkCanvas.h"
+#include "include/core/SkImage.h"
+#include "src/core/SkCompressedDataUtils.h"
+#include "src/gpu/GrCaps.h"
+#include "src/gpu/GrContextPriv.h"
+#include "src/image/SkImage_Base.h"
+
+constexpr int kImgWidth  = 16;
+constexpr int kImgHeight = 8;
+constexpr int kPad       = 4;
+
+struct BC1Block {
+    uint16_t fColor0;
+    uint16_t fColor1;
+    uint32_t fIndices;
+};
+
+static int num_4x4_blocks(int size) {
+    return ((size + 3) & ~3) >> 2;
+}
+
+static uint16_t to565(SkColor col) {
+    int r5 = SkMulDiv255Round(31, SkColorGetR(col));
+    int g6 = SkMulDiv255Round(63, SkColorGetG(col));
+    int b5 = SkMulDiv255Round(31, SkColorGetB(col));
+
+    return (r5 << 11) | (g6 << 5) | b5;
+}
+
+// BC1 has per-block transparency. If, taken as ints,
+//    fColor0 < fColor1    -> the block has transparency (& it is in color3)
+//    fColor1 > fColor0    -> the block is opaque
+//
+// This method can create two blocks to test out BC1's behavior. If BC1
+// behaves as expected (i.e., w/ per-block transparency) then, for RGBA textures,
+// the transparent block(s) should appear as:
+//    opaque black, medium grey, transparent black, white.
+// and the opaque block(s) should appear as:
+//    opaque black, dark grey, light grey, white
+//
+// For RGB textures, however, the transparent block(s) should appear as:
+//    opaque black, medium grey, _opaque_ black, white
+// and the opaque block(s) should appear as:
+//    opaque black, dark grey, light grey, white.
+static void create_BC1_block(BC1Block* block, bool transparent) {
+    unsigned int byte;
+
+    if (transparent) {
+        block->fColor0 = to565(SK_ColorBLACK);
+        block->fColor1 = to565(SK_ColorWHITE);
+        SkASSERT(block->fColor0 <= block->fColor1); // this signals a transparent block
+        // opaque black (col0), medium grey (col2), transparent black (col3), white (col1).
+        byte = (0x0 << 0) | (0x2 << 2) | (0x3 << 4) | (0x1 << 6);
+    } else {
+        block->fColor0 = to565(SK_ColorWHITE);
+        block->fColor1 = to565(SK_ColorBLACK);
+        SkASSERT(block->fColor0 > block->fColor1); // this signals an opaque block
+        // opaque black (col1), dark grey (col3), light grey (col2), white (col0)
+        byte = (0x1 << 0) | (0x3 << 2) | (0x2 << 4) | (0x0 << 6);
+    }
+
+    block->fIndices = (byte << 24) | (byte << 16) | (byte << 8) | byte;
+}
+
+// This makes a 16x8 BC1 texture which has the top 4 rows be officially transparent
+// and the bottom 4 rows be officially opaque.
+static sk_sp<SkData> make_compressed_data() {
+    SkISize dim{ kImgWidth, kImgHeight };
+
+    size_t totalSize = SkCompressedDataSize(SkImage::CompressionType::kBC1_RGB8_UNORM, dim,
+                                            nullptr, false);
+
+    sk_sp<SkData> tmp = SkData::MakeUninitialized(totalSize);
+    BC1Block* dstBlocks = reinterpret_cast<BC1Block*>(tmp->writable_data());
+
+    BC1Block transBlock, opaqueBlock;
+    create_BC1_block(&transBlock, true);
+    create_BC1_block(&opaqueBlock, false);
+
+    int numXBlocks = num_4x4_blocks(kImgWidth);
+    int numYBlocks = num_4x4_blocks(kImgHeight);
+
+    for (int y = 0; y < numYBlocks; ++y) {
+        for (int x = 0; x < numXBlocks; ++x) {
+            dstBlocks[y*numXBlocks + x] = (y < numYBlocks/2) ? transBlock : opaqueBlock;
+        }
+    }
+
+    return tmp;
+}
+
+static sk_sp<SkImage> data_to_img(GrContext *context, sk_sp<SkData> data,
+                                  SkImage::CompressionType compression) {
+    if (context) {
+        return SkImage::MakeTextureFromCompressed(context, std::move(data),
+                                                  kImgWidth,
+                                                  kImgHeight,
+                                                  compression,
+                                                  GrMipMapped::kNo);
+    } else {
+        return SkImage::MakeRasterFromCompressed(std::move(data),
+                                                 kImgWidth,
+                                                 kImgHeight,
+                                                 compression);
+    }
+}
+
+static void draw_image(GrContext* context, SkCanvas* canvas, sk_sp<SkImage> image, int x, int y) {
+
+    bool isCompressed = false;
+    if (image->isTextureBacked()) {
+        const GrCaps* caps = context->priv().caps();
+
+        GrTextureProxy* proxy = as_IB(image)->peekProxy();
+        isCompressed = caps->isFormatCompressed(proxy->backendFormat());
+    }
+
+    canvas->drawImage(image, x, y);
+
+    if (!isCompressed) {
+        SkRect r = SkRect::MakeXYWH(x, y, kImgWidth, kImgHeight);
+        r.outset(1.0f, 1.0f);
+
+        SkPaint redStroke;
+        redStroke.setColor(SK_ColorRED);
+        redStroke.setStyle(SkPaint::kStroke_Style);
+        redStroke.setStrokeWidth(2.0f);
+
+        canvas->drawRect(r, redStroke);
+    }
+}
+
+namespace skiagm {
+
+// This GM draws the BC1 compressed texture filled with "make_compressed_data"s data twice.
+//
+// It is drawn once (on the top) as a kBC1_RGB8_UNORM texture and then again (on the bottom)
+// as a kBC1_RGBA8_UNORM texture.
+//
+// If BC1 behaves as expected we should see:
+//
+//   RGB8             Black MidGrey Black*  White ...
+//                    Black DrkGrey LtGrey  White ...
+//
+//   RGBA8            Black MidGrey Green+  White ...
+//                    Black DrkGrey LtGrey  White ...
+//
+// * We expect this to be black bc the transparent black will be forced to opaque. If BC1 were
+//   treating it as an opaque block then it would be LtGrey - not black.
+// + This is just the background showing through the transparent black
+class BC1TransparencyGM : public GM {
+public:
+    BC1TransparencyGM() {
+        this->setBGColor(SK_ColorGREEN);
+    }
+
+protected:
+
+    SkString onShortName() override {
+        return SkString("bc1_transparency");
+    }
+
+    SkISize onISize() override {
+        return SkISize::Make(kImgWidth + 2 * kPad, 2 * kImgHeight + 3 * kPad);
+    }
+
+    void onOnceBeforeDraw() override {
+        fBC1Data = make_compressed_data();
+    }
+
+    void onDraw(SkCanvas* canvas) override {
+        GrContext* context = canvas->getGrContext();
+
+        sk_sp<SkImage> rgbImg = data_to_img(context, fBC1Data,
+                                            SkImage::CompressionType::kBC1_RGB8_UNORM);
+
+        sk_sp<SkImage> rgbaImg = data_to_img(context, fBC1Data,
+                                             SkImage::CompressionType::kBC1_RGBA8_UNORM);
+
+        draw_image(context, canvas, rgbImg, kPad, kPad);
+        draw_image(context, canvas, rgbaImg, kPad, 2 * kPad + kImgHeight);
+    }
+
+private:
+    sk_sp<SkData> fBC1Data;
+
+    typedef GM INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+DEF_GM(return new BC1TransparencyGM;)
+}
diff --git a/gn/gm.gni b/gn/gm.gni
index b360c89..fbe83d1 100644
--- a/gn/gm.gni
+++ b/gn/gm.gni
@@ -30,6 +30,7 @@
   "$_gm/backdrop.cpp",
   "$_gm/backdrop_imagefilter_croprect.cpp",
   "$_gm/badpaint.cpp",
+  "$_gm/bc1_transparency.cpp",
   "$_gm/beziereffects.cpp",
   "$_gm/beziers.cpp",
   "$_gm/bicubic.cpp",
diff --git a/src/core/SkCompressedDataUtils.cpp b/src/core/SkCompressedDataUtils.cpp
index aba7f48..5c9d14a 100644
--- a/src/core/SkCompressedDataUtils.cpp
+++ b/src/core/SkCompressedDataUtils.cpp
@@ -190,16 +190,17 @@
 
             colors[0] = from565(curBlock->fColor0);
             colors[1] = from565(curBlock->fColor1);
-            if (isOpaque) {
+            if (colors[0] <= colors[1]) {                 // signal for a transparent block
+                colors[2] = SkPackARGB32(
+                    0xFF,
+                    (SkGetPackedR32(colors[0]) + SkGetPackedR32(colors[1])) >> 1,
+                    (SkGetPackedG32(colors[0]) + SkGetPackedG32(colors[1])) >> 1,
+                    (SkGetPackedB32(colors[0]) + SkGetPackedB32(colors[1])) >> 1);
+                // The opacity of the overall texture trumps the per-block transparency
+                colors[3] = SkPackARGB32(isOpaque ? 0xFF : 0, 0, 0, 0);
+            } else {
                 colors[2] = lerp(2.0f/3.0f, colors[0], colors[1]);
                 colors[3] = lerp(1.0f/3.0f, colors[0], colors[1]);
-            } else {
-                colors[2] = SkPackARGB32(
-                                0xFF,
-                                (SkGetPackedR32(colors[0]) + SkGetPackedR32(colors[1])) >> 1,
-                                (SkGetPackedG32(colors[0]) + SkGetPackedG32(colors[1])) >> 1,
-                                (SkGetPackedB32(colors[0]) + SkGetPackedB32(colors[1])) >> 1);
-                colors[3] = SkPackARGB32(0, 0, 0, 0);
             }
 
             int shift = 0;
diff --git a/src/gpu/GrDataUtils.cpp b/src/gpu/GrDataUtils.cpp
index 9840010..82e3c58 100644
--- a/src/gpu/GrDataUtils.cpp
+++ b/src/gpu/GrDataUtils.cpp
@@ -138,6 +138,8 @@
 static void create_BC1_block(SkColor col0, SkColor col1, BC1Block* block) {
     block->fColor0 = to565(col0);
     block->fColor1 = to565(col1);
+    SkASSERT(block->fColor0 <= block->fColor1); // we always assume transparent blocks
+
     if (col0 == SK_ColorTRANSPARENT) {
         // This sets all 16 pixels to just use color3 (under the assumption
         // that this is a kBC1_RGBA8_UNORM texture. Note that in this case