Add BlitRect to SkTCompressedAlphaBlitter

R=robertphillips@google.com

Author: krajcevski@google.com

Review URL: https://codereview.chromium.org/456873003
diff --git a/src/utils/SkTextureCompressor_ASTC.cpp b/src/utils/SkTextureCompressor_ASTC.cpp
index 029f7f2..c7c7f14 100644
--- a/src/utils/SkTextureCompressor_ASTC.cpp
+++ b/src/utils/SkTextureCompressor_ASTC.cpp
@@ -2000,6 +2000,12 @@
     }
 }
 
+////////////////////////////////////////////////////////////////////////////////
+//
+// ASTC Comrpession Struct
+//
+////////////////////////////////////////////////////////////////////////////////
+
 // This is the type passed as the CompressorType argument of the compressed
 // blitter for the ASTC format. The static functions required to be in this
 // struct are documented in SkTextureCompressor_Blitter.h
@@ -2013,8 +2019,19 @@
         compress_a8_astc_block<GetAlpha>(&dst, src, srcRowBytes);
     }
 
-    static inline void UpdateBlock(uint8_t* dst, const uint8_t* src) {
+#if PEDANTIC_BLIT_RECT
+    static inline void UpdateBlock(uint8_t* dst, const uint8_t* src, int srcRowBytes,
+                                   const uint8_t* mask) {
+        // TODO: krajcevski
+        // This is kind of difficult for ASTC because the weight values are calculated
+        // as an average of the actual weights. The best we can do is decompress the
+        // weights and recalculate them based on the new texel values. This should
+        // be "not too bad" since we know that anytime we hit this function, we're
+        // compressing 12x12 block dimension alpha-only, and we know the layout
+        // of the block
+        SkFAIL("Implement me!");
     }
+#endif
 };
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/src/utils/SkTextureCompressor_Blitter.h b/src/utils/SkTextureCompressor_Blitter.h
index 4470201..3bf1049 100644
--- a/src/utils/SkTextureCompressor_Blitter.h
+++ b/src/utils/SkTextureCompressor_Blitter.h
@@ -13,6 +13,22 @@
 
 namespace SkTextureCompressor {
 
+// Ostensibly, SkBlitter::BlitRect is supposed to set a rect of pixels to full
+// alpha. This becomes problematic when using compressed texture blitters, since
+// the rect rarely falls along block boundaries. The proper way to handle this is
+// to update the compressed encoding of a block by resetting the proper parameters
+// (and even recompressing the block) where a rect falls inbetween block boundaries.
+// PEDANTIC_BLIT_RECT attempts to do this by requiring the struct passed to
+// SkTCompressedAlphaBlitter to implement an UpdateBlock function call.
+//
+// However, the way that BlitRect gets used almost exclusively is to bracket inverse
+// fills for paths. In other words, the top few rows and bottom few rows of a path
+// that's getting inverse filled are called using blitRect. The rest are called using
+// the standard blitAntiH. As a result, we can just call  blitAntiH with a faux RLE
+// of full alpha values, and then check in our flush() call that we don't run off the
+// edge of the buffer. This is why we do not need this flag to be turned on.
+#define PEDANTIC_BLIT_RECT 1
+
 // This class implements a blitter that blits directly into a buffer that will
 // be used as an compressed alpha texture. We compute this buffer by
 // buffering scan lines and then outputting them all at once. The number of
@@ -29,13 +45,17 @@
 //
 //     // The function used to compress an A8 block. The layout of the
 //     // block is also expected to be in row-major order.
-//     static void CompressA8Horizontal(uint8_t* dst, const uint8_t block[]);
+//     static void CompressA8Horizontal(uint8_t* dst, const uint8_t* src, int srcRowBytes);
 //
+#if PEDANTIC_BLIT_RECT
 //     // The function used to update an already compressed block. This will
-//     // most likely be implementation dependent.
-//     static void UpdateBlock(uint8_t* dst, const uint8_t* src);
+//     // most likely be implementation dependent. The mask variable will have
+//     // 0xFF in positions where the block should be updated and 0 in positions
+//     // where it shouldn't. src contains an uncompressed buffer of pixels.
+//     static void UpdateBlock(uint8_t* dst, const uint8_t* src, int srcRowBytes, 
+//                             const uint8_t* mask);
+#endif
 // };
-//
 template<int BlockDim, int EncodedBlockSize, typename CompressorType>
 class SkTCompressedAlphaBlitter : public SkBlitter {
 public:
@@ -44,7 +64,8 @@
         // debugging to make sure that we're properly setting the nextX distance
         // in flushRuns(). 
 #ifdef SK_DEBUG
-        : fBlitMaskCalled(false),
+        : fCalledOnceWithNonzeroY(false)
+        , fBlitMaskCalled(false),
 #else
         :
 #endif
@@ -73,6 +94,8 @@
     virtual void blitAntiH(int x, int y,
                            const SkAlpha antialias[],
                            const int16_t runs[]) SK_OVERRIDE {
+        SkASSERT(0 == x);
+
         // Make sure that the new row to blit is either the first
         // row that we're blitting, or it's exactly the next scan row
         // since the last row that we blit. This is to ensure that when
@@ -131,12 +154,122 @@
         SkFAIL("Not implemented!");
     }
 
-    // Blit a solid rectangle one or more pixels wide.
+    // Blit a solid rectangle one or more pixels wide. It's assumed that blitRect
+    // is called as a way to bracket blitAntiH where above and below the path the
+    // called path just needs a solid rectangle to fill in the mask.
+#ifdef SK_DEBUG
+    bool fCalledOnceWithNonzeroY;
+#endif
     virtual void blitRect(int x, int y, int width, int height) SK_OVERRIDE {
-        // Analogous to blitRow, this function is intended for RGB targets
-        // and should never be called by this blitter. Any calls to this function
-        // are probably a bug and should be investigated.
-        SkFAIL("Not implemented!");
+
+        // Assumptions:
+        SkASSERT(0 == x);
+        SkASSERT(width <= fWidth);
+
+        // Make sure that we're only ever bracketing calls to blitAntiH.
+        SkASSERT((0 == y) || (!fCalledOnceWithNonzeroY && (fCalledOnceWithNonzeroY = true)));
+        
+#if !(PEDANTIC_BLIT_RECT)
+        for (int i = 0; i < height; ++i) {
+            const SkAlpha kFullAlpha = 0xFF;
+            this->blitAntiH(x, y+i, &kFullAlpha, &kLongestRun);
+        }
+#else
+        const int startBlockX = (x / BlockDim) * BlockDim;
+        const int startBlockY = (y / BlockDim) * BlockDim;
+
+        const int endBlockX = ((x + width) / BlockDim) * BlockDim;
+        const int endBlockY = ((y + height) / BlockDim) * BlockDim;
+
+        // If start and end are the same, then we only need to update a single block...
+        if (startBlockY == endBlockY && startBlockX == endBlockX) {
+            uint8_t mask[BlockDim*BlockDim];
+            memset(mask, 0, sizeof(mask));
+
+            const int xoff = x - startBlockX;
+            SkASSERT((xoff + width) <= BlockDim);
+
+            const int yoff = y - startBlockY;
+            SkASSERT((yoff + height) <= BlockDim);
+            
+            for (int j = 0; j < height; ++j) {
+                memset(mask + (j + yoff)*BlockDim + xoff, 0xFF, width);
+            }
+
+            uint8_t* dst = this->getBlock(startBlockX, startBlockY);
+            CompressorType::UpdateBlock(dst, mask, BlockDim, mask);
+
+        // If start and end are the same in the y dimension, then we can freely update an
+        // entire row of blocks...
+        } else if (startBlockY == endBlockY) {
+
+            this->updateBlockRow(x, y, width, height, startBlockY, startBlockX, endBlockX);
+
+        // Similarly, if the start and end are in the same column, then we can just update
+        // an entire column of blocks...
+        } else if (startBlockX == endBlockX) {
+
+            this->updateBlockCol(x, y, width, height, startBlockX, startBlockY, endBlockY);
+
+        // Otherwise, the rect spans a non-trivial region of blocks, and we have to construct
+        // a kind of 9-patch to update each of the pieces of the rect. The top and bottom
+        // rows are updated using updateBlockRow, and the left and right columns are updated
+        // using updateBlockColumn. Anything in the middle is simply memset to an opaque block
+        // encoding.
+        } else {
+
+            const int innerStartBlockX = startBlockX + BlockDim;
+            const int innerStartBlockY = startBlockY + BlockDim;
+
+            // Blit top row
+            const int topRowHeight = innerStartBlockY - y;
+            this->updateBlockRow(x, y, width, topRowHeight, startBlockY,
+                                 startBlockX, endBlockX);
+
+            // Advance y
+            y += topRowHeight;
+            height -= topRowHeight;
+
+            // Blit middle
+            if (endBlockY > innerStartBlockY) {
+
+                // Update left row
+                this->updateBlockCol(x, y, innerStartBlockX - x, endBlockY, startBlockY,
+                                     startBlockX, innerStartBlockX);
+
+                // Update the middle with an opaque encoding...
+                uint8_t mask[BlockDim*BlockDim];
+                memset(mask, 0xFF, sizeof(mask));
+
+                uint8_t opaqueEncoding[EncodedBlockSize];
+                CompressorType::CompressA8Horizontal(opaqueEncoding, mask, BlockDim);
+
+                for (int j = innerStartBlockY; j < endBlockY; j += BlockDim) {
+                    uint8_t* opaqueDst = this->getBlock(innerStartBlockX, j);
+                    for (int i = innerStartBlockX; i < endBlockX; i += BlockDim) {
+                        memcpy(opaqueDst, opaqueEncoding, EncodedBlockSize);
+                        opaqueDst += EncodedBlockSize;
+                    }
+                }
+
+                // If we need to update the right column, do that too
+                if (x + width > endBlockX) {
+                    this->updateBlockCol(endBlockX, y, x + width - endBlockX, endBlockY,
+                                         endBlockX, innerStartBlockY, endBlockY);
+                }
+
+                // Advance y
+                height = y + height - endBlockY;
+                y = endBlockY;
+            }
+
+            // If we need to update the last row, then do that, too.
+            if (height > 0) {
+                this->updateBlockRow(x, y, width, height, endBlockY,
+                                     startBlockX, endBlockX);
+            }
+        }
+#endif
     }
 
     // Blit a rectangle with one alpha-blended column on the left,
@@ -400,6 +533,12 @@
         // Make sure that we have a valid right-bound X value
         SkASSERT(finalX < 0xFFFFF);
 
+        // If the finalX is the longest run, then just blit until we have
+        // width...
+        if (kLongestRun == finalX) {
+            finalX = fWidth;
+        }
+
         // Run the blitter...
         while (curX != finalX) {
             SkASSERT(finalX >= curX);
@@ -449,19 +588,23 @@
             SkASSERT(curX == finalX);
 
             // Figure out what the next advancement is...
-            for (int i = 0; i < BlockDim; ++i) {
-                if (nextX[i] == finalX) {
-                    const int16_t run = *(fBufferedRuns[i].fRuns);
-                    fBufferedRuns[i].fRuns += run;
-                    fBufferedRuns[i].fAlphas += run;
-                    curAlpha[i] = *(fBufferedRuns[i].fAlphas);
-                    nextX[i] += *(fBufferedRuns[i].fRuns);
+            if (finalX < fWidth) {
+                for (int i = 0; i < BlockDim; ++i) {
+                    if (nextX[i] == finalX) {
+                        const int16_t run = *(fBufferedRuns[i].fRuns);
+                        fBufferedRuns[i].fRuns += run;
+                        fBufferedRuns[i].fAlphas += run;
+                        curAlpha[i] = *(fBufferedRuns[i].fAlphas);
+                        nextX[i] += *(fBufferedRuns[i].fRuns);
+                    }
                 }
-            }
 
-            finalX = 0xFFFFF;
-            for (int i = 0; i < BlockDim; ++i) {
-                finalX = SkMin32(nextX[i], finalX);
+                finalX = 0xFFFFF;
+                for (int i = 0; i < BlockDim; ++i) {
+                    finalX = SkMin32(nextX[i], finalX);
+                }
+            } else {
+                curX = finalX;
             }
         }
 
@@ -483,6 +626,102 @@
 
         fNextRun = 0;
     }
+
+#if PEDANTIC_BLIT_RECT
+    void updateBlockRow(int x, int y, int width, int height,
+                        int blockRow, int startBlockX, int endBlockX) {
+        if (0 == width || 0 == height || startBlockX == endBlockX) {
+            return;
+        }
+
+        uint8_t* dst = this->getBlock(startBlockX, BlockDim * (y / BlockDim));
+
+        // One horizontal strip to update
+        uint8_t mask[BlockDim*BlockDim];
+        memset(mask, 0, sizeof(mask));
+
+        // Update the left cap
+        int blockX = startBlockX;
+        const int yoff = y - blockRow;
+        for (int j = 0; j < height; ++j) {
+            const int xoff = x - blockX;
+            memset(mask + (j + yoff)*BlockDim + xoff, 0xFF, BlockDim - xoff);
+        }
+        CompressorType::UpdateBlock(dst, mask, BlockDim, mask);
+        dst += EncodedBlockSize;
+        blockX += BlockDim;
+
+        // Update the middle
+        if (blockX < endBlockX) {
+            for (int j = 0; j < height; ++j) {
+                memset(mask + (j + yoff)*BlockDim, 0xFF, BlockDim);
+            }
+            while (blockX < endBlockX) {
+                CompressorType::UpdateBlock(dst, mask, BlockDim, mask);
+                dst += EncodedBlockSize;
+                blockX += BlockDim;
+            }
+        }
+
+        SkASSERT(endBlockX == blockX);
+
+        // Update the right cap (if we need to)
+        if (x + width > endBlockX) {
+            memset(mask, 0, sizeof(mask));
+            for (int j = 0; j < height; ++j) {
+                const int xoff = (x+width-blockX);
+                memset(mask + (j+yoff)*BlockDim, 0xFF, xoff);
+            }
+            CompressorType::UpdateBlock(dst, mask, BlockDim, mask);
+        }
+    }
+
+    void updateBlockCol(int x, int y, int width, int height,
+                        int blockCol, int startBlockY, int endBlockY) {
+        if (0 == width || 0 == height || startBlockY == endBlockY) {
+            return;
+        }
+
+        // One vertical strip to update
+        uint8_t mask[BlockDim*BlockDim];
+        memset(mask, 0, sizeof(mask));
+        const int maskX0 = x - blockCol;
+        const int maskWidth = maskX0 + width;
+        SkASSERT(maskWidth <= BlockDim);
+
+        // Update the top cap
+        int blockY = startBlockY;
+        for (int j = (y - blockY); j < BlockDim; ++j) {
+            memset(mask + maskX0 + j*BlockDim, 0xFF, maskWidth);
+        }
+        CompressorType::UpdateBlock(this->getBlock(blockCol, blockY), mask, BlockDim, mask);
+        blockY += BlockDim;
+
+        // Update middle
+        if (blockY < endBlockY) {
+            for (int j = 0; j < BlockDim; ++j) {
+                memset(mask + maskX0 + j*BlockDim, 0xFF, maskWidth);
+            }
+            while (blockY < endBlockY) {
+                CompressorType::UpdateBlock(this->getBlock(blockCol, blockY),
+                                            mask, BlockDim, mask);
+                blockY += BlockDim;
+            }
+        }
+
+        SkASSERT(endBlockY == blockY);
+
+        // Update bottom
+        if (y + height > endBlockY) {
+            for (int j = y+height; j < endBlockY + BlockDim; ++j) {
+                memset(mask + (j-endBlockY)*BlockDim, 0, BlockDim);
+            }
+            CompressorType::UpdateBlock(this->getBlock(blockCol, blockY),
+                                        mask, BlockDim, mask);
+        }
+    }
+#endif  // PEDANTIC_BLIT_RECT
+
 };
 
 }  // namespace SkTextureCompressor
diff --git a/src/utils/SkTextureCompressor_LATC.cpp b/src/utils/SkTextureCompressor_LATC.cpp
index c9bf89e..1e5e142 100644
--- a/src/utils/SkTextureCompressor_LATC.cpp
+++ b/src/utils/SkTextureCompressor_LATC.cpp
@@ -297,8 +297,27 @@
 
 #if COMPRESS_LATC_FAST
 
-// Take the top three indices of each int and pack them into the low 12
+// Take the top three bits of each index and pack them into the low 12
 // bits of the integer.
+static inline uint32_t pack_index(uint32_t x) {
+    // Pack it in...
+#if defined (SK_CPU_BENDIAN)
+    return
+        (x >> 24) |
+        ((x >> 13) & 0x38) |
+        ((x >> 2) & 0x1C0) |
+        ((x << 9) & 0xE00);
+#else
+    return
+        (x & 0x7) |
+        ((x >> 5) & 0x38) |
+        ((x >> 10) & 0x1C0) |
+        ((x >> 15) & 0xE00);
+#endif
+}
+
+// Converts each 8-bit byte in the integer into an LATC index, and then packs
+// the indices into the low 12 bits of the integer.
 static inline uint32_t convert_index(uint32_t x) {
     // Since the palette is 
     // 255, 0, 219, 182, 146, 109, 73, 36
@@ -326,21 +345,8 @@
     // Mask out high bits:
     // 9 7 6 5 4 3 2 0 --> 1 7 6 5 4 3 2 0
     x &= 0x07070707;
-
-    // Pack it in...
-#if defined (SK_CPU_BENDIAN)
-    return
-        (x >> 24) |
-        ((x >> 13) & 0x38) |
-        ((x >> 2) & 0x1C0) |
-        ((x << 9) & 0xE00);
-#else
-    return
-        (x & 0x7) |
-        ((x >> 5) & 0x38) |
-        ((x >> 10) & 0x1C0) |
-        ((x >> 15) & 0xE00);
-#endif
+    
+    return pack_index(x);
 }
 
 typedef uint64_t (*PackIndicesProc)(const uint8_t* alpha, int rowBytes);
@@ -427,8 +433,38 @@
         compress_a8_latc_block<PackRowMajor>(&dst, src, srcRowBytes);
     }
 
-    static inline void UpdateBlock(uint8_t* dst, const uint8_t* src) {
+#if PEDANTIC_BLIT_RECT
+    static inline void UpdateBlock(uint8_t* dst, const uint8_t* src, int srcRowBytes,
+                                   const uint8_t* mask) {
+        // Pack the mask
+        uint64_t cmpMask = 0;
+        for (int i = 0; i < 4; ++i) {
+            const uint32_t idx = *(reinterpret_cast<const uint32_t*>(src + i*srcRowBytes));
+            cmpMask |= static_cast<uint64_t>(pack_index(idx)) << 12*i;
+        }
+        cmpMask = SkEndian_SwapLE64(cmpMask << 16); // avoid header
+
+        uint64_t cmpSrc;
+        uint8_t *cmpSrcPtr = reinterpret_cast<uint8_t*>(&cmpSrc);
+        compress_a8_latc_block<PackRowMajor>(&cmpSrcPtr, src, srcRowBytes);
+
+        // Mask out header
+        cmpSrc = cmpSrc & cmpMask;
+
+        // Read destination encoding
+        uint64_t *cmpDst = reinterpret_cast<uint64_t*>(dst);
+
+        // If the destination is the encoding for a blank block, then we need
+        // to properly set the header
+        if (0 == cmpDst) {
+            *cmpDst = SkTEndian_SwapLE64(0x24924924924900FFULL);
+        }
+
+        // Set the new indices
+        *cmpDst &= ~cmpMask;
+        *cmpDst |= cmpSrc;
     }
+#endif  // PEDANTIC_BLIT_RECT
 };
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/src/utils/SkTextureCompressor_R11EAC.cpp b/src/utils/SkTextureCompressor_R11EAC.cpp
index 5ccd50b..9996eb9 100644
--- a/src/utils/SkTextureCompressor_R11EAC.cpp
+++ b/src/utils/SkTextureCompressor_R11EAC.cpp
@@ -603,8 +603,15 @@
         *(reinterpret_cast<uint64_t*>(dst)) = compress_r11eac_block_fast(src, srcRowBytes);
     }
 
-    static inline void UpdateBlock(uint8_t* dst, const uint8_t* src) {
+#if PEDANTIC_BLIT_RECT
+    static inline void UpdateBlock(uint8_t* dst, const uint8_t* src, int srcRowBytes,
+                                   const uint8_t* mask) {
+        // TODO: krajcevski
+        // The implementation of this function should be similar to that of LATC, since
+        // the R11EAC indices directly correspond to pixel values.
+        SkFAIL("Implement me!");
     }
+#endif
 };
 
 ////////////////////////////////////////////////////////////////////////////////