Pass compressed blitters to our mask drawing algorithm

R=robertphillips@google.com, reed@google.com

Author: krajcevski@google.com

Review URL: https://codereview.chromium.org/446103002
diff --git a/gyp/ktx.gyp b/gyp/ktx.gyp
index 32a5c32..dda353d 100644
--- a/gyp/ktx.gyp
+++ b/gyp/ktx.gyp
@@ -9,6 +9,7 @@
     'include_dirs' : [
       '../third_party/ktx',
       '../include/gpu',
+      '../src/core',
       '../src/gpu',
       '../src/utils',
     ],
diff --git a/src/gpu/GrSWMaskHelper.cpp b/src/gpu/GrSWMaskHelper.cpp
index 744fa1d..2863739 100644
--- a/src/gpu/GrSWMaskHelper.cpp
+++ b/src/gpu/GrSWMaskHelper.cpp
@@ -125,13 +125,21 @@
     }
     paint.setAntiAlias(antiAlias);
 
+    SkTBlitterAllocator allocator;
+    SkBlitter* blitter = NULL;
+    if (kBlitter_CompressionMode == fCompressionMode) {
+        SkASSERT(NULL != fCompressedBuffer.get());
+        blitter = SkTextureCompressor::CreateBlitterForFormat(
+            fBM.width(), fBM.height(), fCompressedBuffer.get(), &allocator, fCompressedFormat);
+    }
+
     if (SkRegion::kReplace_Op == op && 0xFF == alpha) {
         SkASSERT(0xFF == paint.getAlpha());
-        fDraw.drawPathCoverage(path, paint);
+        fDraw.drawPathCoverage(path, paint, blitter);
     } else {
         paint.setXfermodeMode(op_to_mode(op));
         paint.setColor(SkColorSetARGB(alpha, alpha, alpha, alpha));
-        fDraw.drawPath(path, paint);
+        fDraw.drawPath(path, paint, blitter);
     }
 }
 
@@ -150,31 +158,50 @@
                                      resultBounds.height());
 
 #if GR_COMPRESS_ALPHA_MASK
-    fCompressMask = choose_compressed_fmt(fContext->getGpu()->caps(), &fCompressedFormat);
-#else
-    fCompressMask = false;
+    if (choose_compressed_fmt(fContext->getGpu()->caps(), &fCompressedFormat)) {
+        fCompressionMode = kCompress_CompressionMode;
+    }
 #endif
 
-    // Make sure that the width is a multiple of 16 so that we can use
-    // specialized SIMD instructions that compress 4 blocks at a time.
-    int cmpWidth, cmpHeight;
-    if (fCompressMask) {
+    // Make sure that the width is a multiple of the desired block dimensions
+    // to allow for specialized SIMD instructions that compress multiple blocks at a time.
+    int cmpWidth = bounds.fRight;
+    int cmpHeight = bounds.fBottom;
+    if (kCompress_CompressionMode == fCompressionMode) {
         int dimX, dimY;
         SkTextureCompressor::GetBlockDimensions(fCompressedFormat, &dimX, &dimY);
-        cmpWidth = dimX * ((bounds.fRight + (dimX - 1)) / dimX);
-        cmpHeight = dimY * ((bounds.fBottom + (dimY - 1)) / dimY);
+        cmpWidth = dimX * ((cmpWidth + (dimX - 1)) / dimX);
+        cmpHeight = dimY * ((cmpHeight + (dimY - 1)) / dimY);
+
+        // Can we create a blitter?
+        if (SkTextureCompressor::ExistsBlitterForFormat(fCompressedFormat)) {
+            int cmpSz = SkTextureCompressor::GetCompressedDataSize(
+                fCompressedFormat, cmpWidth, cmpHeight);
+
+            SkASSERT(cmpSz > 0);
+            SkASSERT(NULL == fCompressedBuffer.get());
+            fCompressedBuffer.reset(cmpSz);
+            fCompressionMode = kBlitter_CompressionMode;
+        }
+    } 
+
+    // If we don't have a custom blitter, then we either need a bitmap to compress
+    // from or a bitmap that we're going to use as a texture. In any case, we should
+    // allocate the pixels for a bitmap
+    const SkImageInfo bmImageInfo = SkImageInfo::MakeA8(cmpWidth, cmpHeight);
+    if (kBlitter_CompressionMode != fCompressionMode) {
+        if (!fBM.allocPixels(bmImageInfo)) {
+            return false;
+        }
+
+        sk_bzero(fBM.getPixels(), fBM.getSafeSize());
     } else {
-        cmpWidth = bounds.fRight;
-        cmpHeight = bounds.fBottom;
+        // Otherwise, we just need to remember how big the buffer is...
+        fBM.setInfo(bmImageInfo);
     }
 
-    if (!fBM.allocPixels(SkImageInfo::MakeA8(cmpWidth, cmpHeight))) {
-        return false;
-    }
-
-    sk_bzero(fBM.getPixels(), fBM.getSafeSize());
-
     sk_bzero(&fDraw, sizeof(fDraw));
+
     fRasterClip.setRect(bounds);
     fDraw.fRC    = &fRasterClip;
     fDraw.fClip  = &fRasterClip.bwRgn();
@@ -193,7 +220,7 @@
     desc.fHeight = fBM.height();
     desc.fConfig = kAlpha_8_GrPixelConfig;
 
-    if (fCompressMask) {
+    if (kNone_CompressionMode != fCompressionMode) {
 
 #ifdef SK_DEBUG
         int dimX, dimY;
@@ -203,12 +230,7 @@
 #endif
 
         desc.fConfig = fmt_to_config(fCompressedFormat);
-
-        // If this config isn't supported then we should fall back to A8
-        if (!(fContext->getGpu()->caps()->isConfigTexturable(desc.fConfig))) {
-            SkDEBUGFAIL("Determining compression should be set from choose_compressed_fmt");
-            desc.fConfig = kAlpha_8_GrPixelConfig;
-        }
+        SkASSERT(fContext->getGpu()->caps()->isConfigTexturable(desc.fConfig));
     }
 
     texture->set(fContext, desc);
@@ -253,11 +275,19 @@
     desc.fConfig = texture->config();
         
     // First see if we should compress this texture before uploading.
-    if (fCompressMask) {
-        this->compressTextureData(texture, desc);
-    } else {
-        // Looks like we have to send a full A8 texture. 
-        this->sendTextureData(texture, desc, fBM.getPixels(), fBM.rowBytes());
+    switch (fCompressionMode) {
+        case kNone_CompressionMode:
+            this->sendTextureData(texture, desc, fBM.getPixels(), fBM.rowBytes());
+            break;
+
+        case kCompress_CompressionMode:
+            this->compressTextureData(texture, desc);
+            break;
+
+        case kBlitter_CompressionMode:
+            SkASSERT(NULL != fCompressedBuffer.get());
+            this->sendTextureData(texture, desc, fCompressedBuffer.get(), 0);
+            break;
     }
 }
 
diff --git a/src/gpu/GrSWMaskHelper.h b/src/gpu/GrSWMaskHelper.h
index 4c06786..ccd2df8 100644
--- a/src/gpu/GrSWMaskHelper.h
+++ b/src/gpu/GrSWMaskHelper.h
@@ -42,7 +42,8 @@
 class GrSWMaskHelper : SkNoncopyable {
 public:
     GrSWMaskHelper(GrContext* context)
-    : fContext(context), fCompressMask(false) {
+    : fContext(context)
+    , fCompressionMode(kNone_CompressionMode) {
     }
 
     // set up the internal state in preparation for draws. Since many masks
@@ -94,7 +95,6 @@
                                          GrDrawTarget* target,
                                          const SkIRect& rect);
 
-protected:
 private:
     GrContext*      fContext;
     SkMatrix        fMatrix;
@@ -102,12 +102,22 @@
     SkDraw          fDraw;
     SkRasterClip    fRasterClip;
 
-    // This flag says whether or not we should compress the mask. If
-    // it is true, then fCompressedFormat is always valid.
-    bool                        fCompressMask;
+    // This enum says whether or not we should compress the mask:
+    // kNone_CompressionMode: compression is not supported on this device.
+    // kCompress_CompressionMode: compress the bitmap before it gets sent to the gpu
+    // kBlitter_CompressionMode: write to the bitmap using a special compressed blitter.
+    enum CompressionMode {
+        kNone_CompressionMode,
+        kCompress_CompressionMode,
+        kBlitter_CompressionMode,
+    } fCompressionMode;
+
+    // This is the buffer into which we store our compressed data. This buffer is
+    // only allocated (non-null) if fCompressionMode is kBlitter_CompressionMode
+    SkAutoMalloc fCompressedBuffer;
 
     // This is the desired format within which to compress the
-    // texture. This value is only valid if fCompressMask is true.
+    // texture. This value is only valid if fCompressionMode is not kNone_CompressionMode.
     SkTextureCompressor::Format fCompressedFormat;
 
     // Actually sends the texture data to the GPU. This is called from
diff --git a/src/utils/SkTextureCompressor.cpp b/src/utils/SkTextureCompressor.cpp
index 4034615..4ced9a0 100644
--- a/src/utils/SkTextureCompressor.cpp
+++ b/src/utils/SkTextureCompressor.cpp
@@ -11,6 +11,7 @@
 #include "SkTextureCompressor_R11EAC.h"
 
 #include "SkBitmap.h"
+#include "SkBitmapProcShader.h"
 #include "SkData.h"
 #include "SkEndian.h"
 
@@ -172,16 +173,17 @@
     return NULL;
 }
 
-SkBlitter* CreateBlitterForFormat(int width, int height, void* compressedBuffer, Format format) {
+SkBlitter* CreateBlitterForFormat(int width, int height, void* compressedBuffer,
+                                  SkTBlitterAllocator *allocator, Format format) {
     switch(format) {
         case kLATC_Format:
-            return CreateLATCBlitter(width, height, compressedBuffer);
+            return CreateLATCBlitter(width, height, compressedBuffer, allocator);
 
         case kR11_EAC_Format:
-            return CreateR11EACBlitter(width, height, compressedBuffer);
+            return CreateR11EACBlitter(width, height, compressedBuffer, allocator);
 
         case kASTC_12x12_Format:
-            return CreateASTCBlitter(width, height, compressedBuffer);
+            return CreateASTCBlitter(width, height, compressedBuffer, allocator);
 
         default:
             return NULL;
diff --git a/src/utils/SkTextureCompressor.h b/src/utils/SkTextureCompressor.h
index e44e7b3..d82bf07 100644
--- a/src/utils/SkTextureCompressor.h
+++ b/src/utils/SkTextureCompressor.h
@@ -8,6 +8,7 @@
 #ifndef SkTextureCompressor_DEFINED
 #define SkTextureCompressor_DEFINED
 
+#include "SkBitmapProcShader.h"
 #include "SkImageInfo.h"
 
 class SkBitmap;
@@ -73,11 +74,24 @@
     typedef bool (*CompressionProc)(uint8_t* dst, const uint8_t* src,
                                     int width, int height, int rowBytes);
 
+    // Returns true if there exists a blitter for the specified format.
+    inline bool ExistsBlitterForFormat(Format format) {
+        switch (format) {
+            case kLATC_Format:
+            case kR11_EAC_Format:
+            case kASTC_12x12_Format:
+                return true;
+
+            default:
+                return false;
+        }
+    }
+
     // Returns the blitter for the given compression format. Note, the blitter
     // is intended to be used with the proper input. I.e. if you try to blit
     // RGB source data into an R11 EAC texture, you're gonna have a bad time.
     SkBlitter* CreateBlitterForFormat(int width, int height, void* compressedBuffer,
-                                      Format format);
+                                      SkTBlitterAllocator *allocator, Format format);
 
     // Returns the desired dimensions of the block size for the given format. These dimensions
     // don't necessarily correspond to the specification's dimensions, since there may
diff --git a/src/utils/SkTextureCompressor_ASTC.cpp b/src/utils/SkTextureCompressor_ASTC.cpp
index fbae850..816d2f1 100644
--- a/src/utils/SkTextureCompressor_ASTC.cpp
+++ b/src/utils/SkTextureCompressor_ASTC.cpp
@@ -2011,9 +2011,26 @@
     return true;
 }
 
-SkBlitter* CreateASTCBlitter(int width, int height, void* outputBuffer) {
-    return new
-        SkTCompressedAlphaBlitter<12, 16, CompressA8ASTCBlockVertical>
+SkBlitter* CreateASTCBlitter(int width, int height, void* outputBuffer,
+                             SkTBlitterAllocator* allocator) {
+    if ((width % 12) != 0 || (height % 12) != 0) {
+        return NULL;
+    }
+
+    // Memset the output buffer to an encoding that decodes to zero. We must do this
+    // in order to avoid having uninitialized values in the buffer if the blitter
+    // decides not to write certain scanlines (and skip entire rows of blocks).
+    // In the case of ASTC, if everything index is zero, then the interpolated value
+    // will decode to zero provided we have the right header. We use the encoding
+    // from recognizing all zero blocks from above.
+    const int nBlocks = (width * height / 144);
+    uint8_t *dst = reinterpret_cast<uint8_t *>(outputBuffer);
+    for (int i = 0; i < nBlocks; ++i) {
+        send_packing(&dst, SkTEndian_SwapLE64(0x0000000001FE000173ULL), 0);
+    }
+
+    return allocator->createT<
+        SkTCompressedAlphaBlitter<12, 16, CompressA8ASTCBlockVertical>, int, int, void* >
         (width, height, outputBuffer);
 }
 
diff --git a/src/utils/SkTextureCompressor_ASTC.h b/src/utils/SkTextureCompressor_ASTC.h
index 57ba08d..bdbe630 100644
--- a/src/utils/SkTextureCompressor_ASTC.h
+++ b/src/utils/SkTextureCompressor_ASTC.h
@@ -8,9 +8,8 @@
 #ifndef SkTextureCompressor_ASTC_DEFINED
 #define SkTextureCompressor_ASTC_DEFINED
 
-#include <stdint.h>
+#include "SkBitmapProcShader.h"
 
-// Forward declare
 class SkBlitter;
 
 namespace SkTextureCompressor {
@@ -18,7 +17,8 @@
     bool CompressA8To12x12ASTC(uint8_t* dst, const uint8_t* src,
                                int width, int height, int rowBytes);
 
-    SkBlitter* CreateASTCBlitter(int width, int height, void* outputBuffer);
+    SkBlitter* CreateASTCBlitter(int width, int height, void* outputBuffer,
+                                 SkTBlitterAllocator *allocator);
 
     void DecompressASTC(uint8_t* dst, int dstRowBytes, const uint8_t* src,
                         int width, int height, int blockDimX, int blockDimY);
diff --git a/src/utils/SkTextureCompressor_LATC.cpp b/src/utils/SkTextureCompressor_LATC.cpp
index 5db0fc6..0b9cf17 100644
--- a/src/utils/SkTextureCompressor_LATC.cpp
+++ b/src/utils/SkTextureCompressor_LATC.cpp
@@ -8,6 +8,7 @@
 #include "SkTextureCompressor_LATC.h"
 #include "SkTextureCompressor_Blitter.h"
 
+#include "SkBlitter.h"
 #include "SkEndian.h"
 
 // Compression options. In general, the slow version is much more accurate, but
@@ -427,10 +428,23 @@
 #endif
 }
 
-SkBlitter* CreateLATCBlitter(int width, int height, void* outputBuffer) {
+SkBlitter* CreateLATCBlitter(int width, int height, void* outputBuffer,
+                             SkTBlitterAllocator* allocator) {
+    if ((width % 4) != 0 || (height % 4) != 0) {
+        return NULL;
+    }
+
 #if COMPRESS_LATC_FAST
-    return new
-        SkTCompressedAlphaBlitter<4, 8, CompressA8LATCBlockVertical>
+    // Memset the output buffer to an encoding that decodes to zero. We must do this
+    // in order to avoid having uninitialized values in the buffer if the blitter
+    // decides not to write certain scanlines (and skip entire rows of blocks).
+    // In the case of LATC, if everything is zero, then LUM0 and LUM1 are also zero,
+    // and they will only be non-zero (0xFF) if the index is 7. So bzero will do just fine.
+    // (8 bytes per block) * (w * h / 16 blocks) = w * h / 2
+    sk_bzero(outputBuffer, width * height / 2);
+
+    return allocator->createT<
+        SkTCompressedAlphaBlitter<4, 8, CompressA8LATCBlockVertical>, int, int, void* >
         (width, height, outputBuffer);
 #elif COMPRESS_LATC_SLOW
     // TODO (krajcevski)
diff --git a/src/utils/SkTextureCompressor_LATC.h b/src/utils/SkTextureCompressor_LATC.h
index 6ee2ff6..e41a249 100644
--- a/src/utils/SkTextureCompressor_LATC.h
+++ b/src/utils/SkTextureCompressor_LATC.h
@@ -8,14 +8,17 @@
 #ifndef SkTextureCompressor_LATC_DEFINED
 #define SkTextureCompressor_LATC_DEFINED
 
-#include "SkBlitter.h"
+#include "SkBitmapProcShader.h"
+
+class SkBlitter;
 
 namespace SkTextureCompressor {
 
     bool CompressA8ToLATC(uint8_t* dst, const uint8_t* src,
                           int width, int height, int rowBytes);
 
-    SkBlitter* CreateLATCBlitter(int width, int height, void* outputBuffer);
+    SkBlitter* CreateLATCBlitter(int width, int height, void* outputBuffer,
+                                 SkTBlitterAllocator *allocator);
 
     void DecompressLATC(uint8_t* dst, int dstRowBytes, const uint8_t* src, int width, int height);
 }
diff --git a/src/utils/SkTextureCompressor_R11EAC.cpp b/src/utils/SkTextureCompressor_R11EAC.cpp
index 7baa219..1d2b8e5 100644
--- a/src/utils/SkTextureCompressor_R11EAC.cpp
+++ b/src/utils/SkTextureCompressor_R11EAC.cpp
@@ -8,6 +8,7 @@
 #include "SkTextureCompressor.h"
 #include "SkTextureCompressor_Blitter.h"
 
+#include "SkBlitter.h"
 #include "SkEndian.h"
 
 // #define COMPRESS_R11_EAC_SLOW 1
@@ -608,9 +609,26 @@
 #endif
 }
 
-SkBlitter* CreateR11EACBlitter(int width, int height, void* outputBuffer) {
-    return new
-        SkTCompressedAlphaBlitter<4, 8, compress_block_vertical>
+SkBlitter* CreateR11EACBlitter(int width, int height, void* outputBuffer,
+                               SkTBlitterAllocator* allocator) {
+
+    if ((width % 4) != 0 || (height % 4) != 0) {
+        return NULL;
+    }
+
+    // Memset the output buffer to an encoding that decodes to zero. We must do this
+    // in order to avoid having uninitialized values in the buffer if the blitter
+    // decides not to write certain scanlines (and skip entire rows of blocks).
+    // In the case of R11, we use the encoding from recognizing all zero pixels from above.
+    const int nBlocks = (width * height / 16);  // 4x4 pixel blocks.
+    uint64_t *dst = reinterpret_cast<uint64_t *>(outputBuffer);
+    for (int i = 0; i < nBlocks; ++i) {
+        *dst = 0x0020000000002000ULL;
+        ++dst;
+    }
+
+    return allocator->createT<
+        SkTCompressedAlphaBlitter<4, 8, compress_block_vertical>, int, int, void*>
         (width, height, outputBuffer);
 }
 
diff --git a/src/utils/SkTextureCompressor_R11EAC.h b/src/utils/SkTextureCompressor_R11EAC.h
index 1dd8e39..6786513 100644
--- a/src/utils/SkTextureCompressor_R11EAC.h
+++ b/src/utils/SkTextureCompressor_R11EAC.h
@@ -8,14 +8,17 @@
 #ifndef SkTextureCompressor_R11EAC_DEFINED
 #define SkTextureCompressor_R11EAC_DEFINED
 
-#include "SkBlitter.h"
+#include "SkBitmapProcShader.h"
+
+class SkBlitter;
 
 namespace SkTextureCompressor {
 
     bool CompressA8ToR11EAC(uint8_t* dst, const uint8_t* src,
                             int width, int height, int rowBytes);
 
-    SkBlitter* CreateR11EACBlitter(int width, int height, void* outputBuffer);
+    SkBlitter* CreateR11EACBlitter(int width, int height, void* outputBuffer,
+                                   SkTBlitterAllocator* allocator);
 
     void DecompressR11EAC(uint8_t* dst, int dstRB, const uint8_t* src, int width, int height);
 }