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