Two new benches for ETC1 bitmaps.

First bench: Simply render from a compressed ETC1 bitmap. This is roughly
equal between compressed and uncompressed data.

Second bench: Notify the bitmap that the pixels have changed (that's right,
like a liar) to force a re-upload to the GPU. On this bench, decompressed
textures seem to inexplicably do an order of magnitude better than
compressed textures when it should be the other way around, but that
investigation is reserved for a future CL.

R=robertphillips@google.com

Author: krajcevski@google.com

Review URL: https://codereview.chromium.org/317023002
diff --git a/bench/ETCBitmapBench.cpp b/bench/ETCBitmapBench.cpp
new file mode 100644
index 0000000..4b9cd6e
--- /dev/null
+++ b/bench/ETCBitmapBench.cpp
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBenchmark.h"
+#include "SkCanvas.h"
+#include "SkData.h"
+#include "SkDecodingImageGenerator.h"
+#include "SkImageDecoder.h"
+#include "SkOSFile.h"
+#include "SkPixelRef.h"
+
+#ifndef SK_IGNORE_ETC1_SUPPORT
+
+#include "etc1.h"
+
+// This takes the etc1 data pointed to by orig, and copies it `factor` times in each
+// dimension. The return value is the new data or NULL on error.
+static etc1_byte* create_expanded_etc1_bitmap(const uint8_t* orig, int factor) {
+    SkASSERT(NULL != orig);
+    SkASSERT(factor > 1);
+
+    const etc1_byte* origData = reinterpret_cast<const etc1_byte*>(orig);
+    if (!etc1_pkm_is_valid(orig)) {
+        return NULL;
+    }
+
+    etc1_uint32 origWidth = etc1_pkm_get_width(origData);
+    etc1_uint32 origHeight = etc1_pkm_get_height(origData);
+
+    // The width and height must be aligned along block boundaries
+    static const etc1_uint32 kETC1BlockWidth = 4;
+    static const etc1_uint32 kETC1BlockHeight = 4;
+    if ((origWidth % kETC1BlockWidth) != 0 ||
+        (origHeight % kETC1BlockHeight) != 0) {
+        return NULL;
+    }
+
+    // The picture must be at least as large as a block.
+    if (origWidth <= kETC1BlockWidth || origHeight <= kETC1BlockHeight) {
+        return NULL;
+    }
+
+    etc1_uint32 newWidth = origWidth * factor;
+    etc1_uint32 newHeight = origHeight * factor;
+
+    etc1_uint32 newDataSz = etc1_get_encoded_data_size(newWidth, newHeight);
+    etc1_byte* newData = reinterpret_cast<etc1_byte *>(
+        sk_malloc_throw(newDataSz + ETC_PKM_HEADER_SIZE));
+    etc1_pkm_format_header(newData, newWidth, newHeight);
+
+    etc1_byte* copyInto = newData;
+
+    copyInto += ETC_PKM_HEADER_SIZE;
+    origData += ETC_PKM_HEADER_SIZE;
+
+    etc1_uint32 origBlocksX = (origWidth >> 2);
+    etc1_uint32 origBlocksY = (origHeight >> 2);
+    etc1_uint32 newBlocksY = (newHeight >> 2);
+    etc1_uint32 origRowSzInBytes = origBlocksX * ETC1_ENCODED_BLOCK_SIZE;
+
+    for (etc1_uint32 j = 0; j < newBlocksY; ++j) {
+        const etc1_byte* rowStart = origData + ((j % origBlocksY) * origRowSzInBytes);
+        for(etc1_uint32 i = 0; i < newWidth; i += origWidth) {
+            memcpy(copyInto, rowStart, origRowSzInBytes);
+            copyInto += origRowSzInBytes;
+        }
+    }
+    return newData;
+}
+
+// This is the base class for all of the benches in this file. In general
+// the ETC1 benches should all be working on the same data. Due to the
+// simplicity of the PKM file, that data is the 128x128 mandrill etc1
+// compressed texture repeated by some factor (currently 8 -> 1024x1024)
+class ETCBitmapBenchBase : public SkBenchmark {
+public:
+    ETCBitmapBenchBase() : fPKMData(loadPKM()) {
+        if (NULL == fPKMData) {
+            SkDebugf("Could not load PKM data!");
+        }
+    }
+
+protected:
+    SkAutoDataUnref fPKMData;
+
+private:
+    SkData *loadPKM() {
+        SkString filename = SkOSPath::SkPathJoin(
+            INHERITED::GetResourcePath().c_str(), "mandrill_128.pkm");
+
+        // Expand the data
+        SkAutoDataUnref fileData(SkData::NewFromFileName(filename.c_str()));
+        if (NULL == fileData) {
+            SkDebugf("Could not open the file. Did you forget to set the resourcePath?\n");
+            return NULL;
+        }
+
+        const etc1_uint32 kExpansionFactor = 8;
+        etc1_byte* expandedETC1 =
+            create_expanded_etc1_bitmap(fileData->bytes(), kExpansionFactor);
+        if (NULL == expandedETC1) {
+            SkDebugf("Error expanding ETC1 data by factor of %d\n", kExpansionFactor);
+            return NULL;
+        }
+
+        etc1_uint32 width = etc1_pkm_get_width(expandedETC1);
+        etc1_uint32 height = etc1_pkm_get_width(expandedETC1);
+        etc1_uint32 dataSz = ETC_PKM_HEADER_SIZE + etc1_get_encoded_data_size(width, height);
+        return SkData::NewFromMalloc(expandedETC1, dataSz);
+    }
+
+    typedef SkBenchmark INHERITED;
+};
+
+// This is the rendering benchmark. Prior to rendering the data, create a
+// bitmap using the etc1 data.
+class ETCBitmapBench : public ETCBitmapBenchBase {
+public:
+    ETCBitmapBench(bool decompress, Backend backend)
+        : fDecompress(decompress), fBackend(backend) { }
+
+    virtual bool isSuitableFor(Backend backend) SK_OVERRIDE {
+        return backend == this->fBackend;
+    }
+
+protected:
+    virtual const char* onGetName() SK_OVERRIDE {
+        if (kGPU_Backend == this->fBackend) {
+            if (this->fDecompress) {
+                return "etc1bitmap_render_gpu_decompressed";
+            } else {
+                return "etc1bitmap_render_gpu_compressed";
+            }
+        } else {
+            SkASSERT(kRaster_Backend == this->fBackend);
+            if (this->fDecompress) {
+                return "etc1bitmap_render_raster_decompressed";
+            } else {
+                return "etc1bitmap_render_raster_compressed";
+            }
+        }
+    }
+
+    virtual void onPreDraw() SK_OVERRIDE {
+        if (NULL == fPKMData) {
+            SkDebugf("Failed to load PKM data!\n");
+            return;
+        }
+
+        // Install pixel ref
+        if (!SkInstallDiscardablePixelRef(
+                SkDecodingImageGenerator::Create(
+                    fPKMData, SkDecodingImageGenerator::Options()), &(this->fBitmap))) {
+            SkDebugf("Could not install discardable pixel ref.\n");
+            return;
+        }
+
+        // Decompress it if necessary
+        if (this->fDecompress) {
+            this->fBitmap.lockPixels();
+        }
+    }
+
+    virtual void onDraw(const int loops, SkCanvas* canvas) SK_OVERRIDE {
+        for (int i = 0; i < loops; ++i) {
+            canvas->drawBitmap(this->fBitmap, 0, 0, NULL);
+        }
+    }
+
+protected:
+    SkBitmap fBitmap;
+    bool decompress() const { return fDecompress; }
+    Backend backend() const { return fBackend; }
+private:
+    const bool fDecompress;
+    const Backend fBackend;
+    typedef ETCBitmapBenchBase INHERITED;
+};
+
+// This benchmark is identical to the previous benchmark, but it explicitly forces
+// an upload to the GPU before each draw call. We do this by notifying the bitmap
+// that the pixels have changed (even though they haven't).
+class ETCBitmapUploadBench : public ETCBitmapBench {
+public:
+    ETCBitmapUploadBench(bool decompress, Backend backend)
+        : ETCBitmapBench(decompress, backend) { }
+
+protected:
+    virtual const char* onGetName() SK_OVERRIDE {
+        if (kGPU_Backend == this->backend()) {
+            if (this->decompress()) {
+                return "etc1bitmap_upload_gpu_decompressed";
+            } else {
+                return "etc1bitmap_upload_gpu_compressed";
+            }
+        } else {
+            SkASSERT(kRaster_Backend == this->backend());
+            if (this->decompress()) {
+                return "etc1bitmap_upload_raster_decompressed";
+            } else {
+                return "etc1bitmap_upload_raster_compressed";
+            }
+        }
+    }
+
+    virtual void onDraw(const int loops, SkCanvas* canvas) SK_OVERRIDE {
+        for (int i = 0; i < loops; ++i) {
+            this->fBitmap.pixelRef()->notifyPixelsChanged();
+            canvas->drawBitmap(this->fBitmap, 0, 0, NULL);
+        }
+    }
+
+private:
+    typedef ETCBitmapBench INHERITED;
+};
+
+DEF_BENCH(return new ETCBitmapBench(false, SkBenchmark::kRaster_Backend);)
+DEF_BENCH(return new ETCBitmapBench(true, SkBenchmark::kRaster_Backend);)
+
+DEF_BENCH(return new ETCBitmapBench(false, SkBenchmark::kGPU_Backend);)
+DEF_BENCH(return new ETCBitmapBench(true, SkBenchmark::kGPU_Backend);)
+
+DEF_BENCH(return new ETCBitmapUploadBench(false, SkBenchmark::kRaster_Backend);)
+DEF_BENCH(return new ETCBitmapUploadBench(true, SkBenchmark::kRaster_Backend);)
+
+DEF_BENCH(return new ETCBitmapUploadBench(false, SkBenchmark::kGPU_Backend);)
+DEF_BENCH(return new ETCBitmapUploadBench(true, SkBenchmark::kGPU_Backend);)
+
+#endif  // SK_IGNORE_ETC1_SUPPORT
diff --git a/gyp/bench.gypi b/gyp/bench.gypi
index 295be45..9efcbf3 100644
--- a/gyp/bench.gypi
+++ b/gyp/bench.gypi
@@ -33,6 +33,7 @@
     '../bench/DeferredCanvasBench.cpp',
     '../bench/DeferredSurfaceCopyBench.cpp',
     '../bench/DisplacementBench.cpp',
+    '../bench/ETCBitmapBench.cpp',
     '../bench/FSRectBench.cpp',
     '../bench/FontCacheBench.cpp',
     '../bench/FontScalerBench.cpp',
diff --git a/src/gpu/SkGr.cpp b/src/gpu/SkGr.cpp
index 31372cd..fb2ebdd 100644
--- a/src/gpu/SkGr.cpp
+++ b/src/gpu/SkGr.cpp
@@ -244,7 +244,18 @@
 
     // Is this an ETC1 encoded texture?
 #ifndef SK_IGNORE_ETC1_SUPPORT
-    else if (cache && ctx->getGpu()->caps()->isConfigTexturable(kETC1_GrPixelConfig)) {
+    else if (
+        // We do not support scratch ETC1 textures, hence they should all be at least
+        // trying to go to the cache.
+        cache
+        // Make sure that the underlying device supports ETC1 textures before we go ahead
+        // and check the data.
+        && ctx->getGpu()->caps()->isConfigTexturable(kETC1_GrPixelConfig)
+        // If the bitmap had compressed data and was then uncompressed, it'll still return
+        // compressed data on 'refEncodedData' and upload it. Probably not good, since if
+        // the bitmap has available pixels, then they might not be what the decompressed
+        // data is.
+        && !(bitmap->readyToDraw())) {
         GrTexture *texture = load_etc1_texture(ctx, params, *bitmap, desc);
         if (NULL != texture) {
             return texture;