1 /*
2  * Copyright 2014 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #ifndef SkTextureCompressor_Blitter_DEFINED
9 #define SkTextureCompressor_Blitter_DEFINED
10 
11 #include "SkTypes.h"
12 #include "SkBlitter.h"
13 
14 namespace SkTextureCompressor {
15 
16 // Ostensibly, SkBlitter::BlitRect is supposed to set a rect of pixels to full
17 // alpha. This becomes problematic when using compressed texture blitters, since
18 // the rect rarely falls along block boundaries. The proper way to handle this is
19 // to update the compressed encoding of a block by resetting the proper parameters
20 // (and even recompressing the block) where a rect falls inbetween block boundaries.
21 // PEDANTIC_BLIT_RECT attempts to do this by requiring the struct passed to
22 // SkTCompressedAlphaBlitter to implement an UpdateBlock function call.
23 //
24 // However, the way that BlitRect gets used almost exclusively is to bracket inverse
25 // fills for paths. In other words, the top few rows and bottom few rows of a path
26 // that's getting inverse filled are called using blitRect. The rest are called using
27 // the standard blitAntiH. As a result, we can just call  blitAntiH with a faux RLE
28 // of full alpha values, and then check in our flush() call that we don't run off the
29 // edge of the buffer. This is why we do not need this flag to be turned on.
30 //
31 // NOTE: This code is unfinished, but is inteded as a starting point if an when
32 // bugs are introduced from the existing code.
33 #define PEDANTIC_BLIT_RECT 0
34 
35 // This class implements a blitter that blits directly into a buffer that will
36 // be used as an compressed alpha texture. We compute this buffer by
37 // buffering scan lines and then outputting them all at once. The number of
38 // scan lines buffered is controlled by kBlockSize
39 //
40 // The CompressorType is a struct with a bunch of static methods that provides
41 // the specialized compression functionality of the blitter. A complete CompressorType
42 // will implement the following static functions;
43 //
44 // struct CompressorType {
45 //     // The function used to compress an A8 block. The layout of the
46 //     // block is also expected to be in column-major order.
47 //     static void CompressA8Vertical(uint8_t* dst, const uint8_t block[]);
48 //
49 //     // The function used to compress an A8 block. The layout of the
50 //     // block is also expected to be in row-major order.
51 //     static void CompressA8Horizontal(uint8_t* dst, const uint8_t* src, int srcRowBytes);
52 //
53 #if PEDANTIC_BLIT_RECT
54 //     // The function used to update an already compressed block. This will
55 //     // most likely be implementation dependent. The mask variable will have
56 //     // 0xFF in positions where the block should be updated and 0 in positions
57 //     // where it shouldn't. src contains an uncompressed buffer of pixels.
58 //     static void UpdateBlock(uint8_t* dst, const uint8_t* src, int srcRowBytes,
59 //                             const uint8_t* mask);
60 #endif
61 // };
62 template<int BlockDim, int EncodedBlockSize, typename CompressorType>
63 class SkTCompressedAlphaBlitter : public SkBlitter {
64 public:
SkTCompressedAlphaBlitter(int width,int height,void * compressedBuffer)65     SkTCompressedAlphaBlitter(int width, int height, void *compressedBuffer)
66         // 0x7FFE is one minus the largest positive 16-bit int. We use it for
67         // debugging to make sure that we're properly setting the nextX distance
68         // in flushRuns().
69 #ifdef SK_DEBUG
70         : fCalledOnceWithNonzeroY(false)
71         , fBlitMaskCalled(false),
72 #else
73         :
74 #endif
75         kLongestRun(0x7FFE), kZeroAlpha(0)
76         , fNextRun(0)
77         , fWidth(width)
78         , fHeight(height)
79         , fBuffer(compressedBuffer)
80         {
81             SkASSERT((width % BlockDim) == 0);
82             SkASSERT((height % BlockDim) == 0);
83         }
84 
~SkTCompressedAlphaBlitter()85     virtual ~SkTCompressedAlphaBlitter() { this->flushRuns(); }
86 
87     // Blit a horizontal run of one or more pixels.
blitH(int x,int y,int width)88     void blitH(int x, int y, int width) override {
89         // This function is intended to be called from any standard RGB
90         // buffer, so we should never encounter it. However, if some code
91         // path does end up here, then this needs to be investigated.
92         SkFAIL("Not implemented!");
93     }
94 
95     // Blit a horizontal run of antialiased pixels; runs[] is a *sparse*
96     // zero-terminated run-length encoding of spans of constant alpha values.
blitAntiH(int x,int y,const SkAlpha antialias[],const int16_t runs[])97     void blitAntiH(int x, int y,
98                    const SkAlpha antialias[],
99                    const int16_t runs[]) override {
100         SkASSERT(0 == x);
101 
102         // Make sure that the new row to blit is either the first
103         // row that we're blitting, or it's exactly the next scan row
104         // since the last row that we blit. This is to ensure that when
105         // we go to flush the runs, that they are all the same four
106         // runs.
107         if (fNextRun > 0 &&
108             ((x != fBufferedRuns[fNextRun-1].fX) ||
109              (y-1 != fBufferedRuns[fNextRun-1].fY))) {
110             this->flushRuns();
111         }
112 
113         // Align the rows to a block boundary. If we receive rows that
114         // are not on a block boundary, then fill in the preceding runs
115         // with zeros. We do this by producing a single RLE that says
116         // that we have 0x7FFE pixels of zero (0x7FFE = 32766).
117         const int row = BlockDim * (y / BlockDim);
118         while ((row + fNextRun) < y) {
119             fBufferedRuns[fNextRun].fAlphas = &kZeroAlpha;
120             fBufferedRuns[fNextRun].fRuns = &kLongestRun;
121             fBufferedRuns[fNextRun].fX = 0;
122             fBufferedRuns[fNextRun].fY = row + fNextRun;
123             ++fNextRun;
124         }
125 
126         // Make sure that our assumptions aren't violated...
127         SkASSERT(fNextRun == (y % BlockDim));
128         SkASSERT(fNextRun == 0 || fBufferedRuns[fNextRun - 1].fY < y);
129 
130         // Set the values of the next run
131         fBufferedRuns[fNextRun].fAlphas = antialias;
132         fBufferedRuns[fNextRun].fRuns = runs;
133         fBufferedRuns[fNextRun].fX = x;
134         fBufferedRuns[fNextRun].fY = y;
135 
136         // If we've output a block of scanlines in a row that don't violate our
137         // assumptions, then it's time to flush them...
138         if (BlockDim == ++fNextRun) {
139             this->flushRuns();
140         }
141     }
142 
143     // Blit a vertical run of pixels with a constant alpha value.
blitV(int x,int y,int height,SkAlpha alpha)144     void blitV(int x, int y, int height, SkAlpha alpha) override {
145         // This function is currently not implemented. It is not explicitly
146         // required by the contract, but if at some time a code path runs into
147         // this function (which is entirely possible), it needs to be implemented.
148         //
149         // TODO (krajcevski):
150         // This function will be most easily implemented in one of two ways:
151         // 1. Buffer each vertical column value and then construct a list
152         //    of alpha values and output all of the blocks at once. This only
153         //    requires a write to the compressed buffer
154         // 2. Replace the indices of each block with the proper indices based
155         //    on the alpha value. This requires a read and write of the compressed
156         //    buffer, but much less overhead.
157         SkFAIL("Not implemented!");
158     }
159 
160     // Blit a solid rectangle one or more pixels wide. It's assumed that blitRect
161     // is called as a way to bracket blitAntiH where above and below the path the
162     // called path just needs a solid rectangle to fill in the mask.
163 #ifdef SK_DEBUG
164     bool fCalledOnceWithNonzeroY;
165 #endif
blitRect(int x,int y,int width,int height)166     void blitRect(int x, int y, int width, int height) override {
167 
168         // Assumptions:
169         SkASSERT(0 == x);
170         SkASSERT(width <= fWidth);
171 
172         // Make sure that we're only ever bracketing calls to blitAntiH.
173         SkASSERT((0 == y) || (!fCalledOnceWithNonzeroY && (fCalledOnceWithNonzeroY = true)));
174 
175 #if !(PEDANTIC_BLIT_RECT)
176         for (int i = 0; i < height; ++i) {
177             const SkAlpha kFullAlpha = 0xFF;
178             this->blitAntiH(x, y+i, &kFullAlpha, &kLongestRun);
179         }
180 #else
181         const int startBlockX = (x / BlockDim) * BlockDim;
182         const int startBlockY = (y / BlockDim) * BlockDim;
183 
184         const int endBlockX = ((x + width) / BlockDim) * BlockDim;
185         const int endBlockY = ((y + height) / BlockDim) * BlockDim;
186 
187         // If start and end are the same, then we only need to update a single block...
188         if (startBlockY == endBlockY && startBlockX == endBlockX) {
189             uint8_t mask[BlockDim*BlockDim];
190             memset(mask, 0, sizeof(mask));
191 
192             const int xoff = x - startBlockX;
193             SkASSERT((xoff + width) <= BlockDim);
194 
195             const int yoff = y - startBlockY;
196             SkASSERT((yoff + height) <= BlockDim);
197 
198             for (int j = 0; j < height; ++j) {
199                 memset(mask + (j + yoff)*BlockDim + xoff, 0xFF, width);
200             }
201 
202             uint8_t* dst = this->getBlock(startBlockX, startBlockY);
203             CompressorType::UpdateBlock(dst, mask, BlockDim, mask);
204 
205         // If start and end are the same in the y dimension, then we can freely update an
206         // entire row of blocks...
207         } else if (startBlockY == endBlockY) {
208 
209             this->updateBlockRow(x, y, width, height, startBlockY, startBlockX, endBlockX);
210 
211         // Similarly, if the start and end are in the same column, then we can just update
212         // an entire column of blocks...
213         } else if (startBlockX == endBlockX) {
214 
215             this->updateBlockCol(x, y, width, height, startBlockX, startBlockY, endBlockY);
216 
217         // Otherwise, the rect spans a non-trivial region of blocks, and we have to construct
218         // a kind of 9-patch to update each of the pieces of the rect. The top and bottom
219         // rows are updated using updateBlockRow, and the left and right columns are updated
220         // using updateBlockColumn. Anything in the middle is simply memset to an opaque block
221         // encoding.
222         } else {
223 
224             const int innerStartBlockX = startBlockX + BlockDim;
225             const int innerStartBlockY = startBlockY + BlockDim;
226 
227             // Blit top row
228             const int topRowHeight = innerStartBlockY - y;
229             this->updateBlockRow(x, y, width, topRowHeight, startBlockY,
230                                  startBlockX, endBlockX);
231 
232             // Advance y
233             y += topRowHeight;
234             height -= topRowHeight;
235 
236             // Blit middle
237             if (endBlockY > innerStartBlockY) {
238 
239                 // Update left row
240                 this->updateBlockCol(x, y, innerStartBlockX - x, endBlockY, startBlockY,
241                                      startBlockX, innerStartBlockX);
242 
243                 // Update the middle with an opaque encoding...
244                 uint8_t mask[BlockDim*BlockDim];
245                 memset(mask, 0xFF, sizeof(mask));
246 
247                 uint8_t opaqueEncoding[EncodedBlockSize];
248                 CompressorType::CompressA8Horizontal(opaqueEncoding, mask, BlockDim);
249 
250                 for (int j = innerStartBlockY; j < endBlockY; j += BlockDim) {
251                     uint8_t* opaqueDst = this->getBlock(innerStartBlockX, j);
252                     for (int i = innerStartBlockX; i < endBlockX; i += BlockDim) {
253                         memcpy(opaqueDst, opaqueEncoding, EncodedBlockSize);
254                         opaqueDst += EncodedBlockSize;
255                     }
256                 }
257 
258                 // If we need to update the right column, do that too
259                 if (x + width > endBlockX) {
260                     this->updateBlockCol(endBlockX, y, x + width - endBlockX, endBlockY,
261                                          endBlockX, innerStartBlockY, endBlockY);
262                 }
263 
264                 // Advance y
265                 height = y + height - endBlockY;
266                 y = endBlockY;
267             }
268 
269             // If we need to update the last row, then do that, too.
270             if (height > 0) {
271                 this->updateBlockRow(x, y, width, height, endBlockY,
272                                      startBlockX, endBlockX);
273             }
274         }
275 #endif
276     }
277 
278     // Blit a rectangle with one alpha-blended column on the left,
279     // width (zero or more) opaque pixels, and one alpha-blended column
280     // on the right. The result will always be at least two pixels wide.
blitAntiRect(int x,int y,int width,int height,SkAlpha leftAlpha,SkAlpha rightAlpha)281     void blitAntiRect(int x, int y, int width, int height,
282                       SkAlpha leftAlpha, SkAlpha rightAlpha) override {
283         // This function is currently not implemented. It is not explicitly
284         // required by the contract, but if at some time a code path runs into
285         // this function (which is entirely possible), it needs to be implemented.
286         //
287         // TODO (krajcevski):
288         // This function will be most easily implemented as follows:
289         // 1. If width/height are smaller than a block, then update the
290         //    indices of the affected blocks.
291         // 2. If width/height are larger than a block, then construct a 9-patch
292         //    of block encodings that represent the rectangle, and write them
293         //    to the compressed buffer as necessary. Whether or not the blocks
294         //    are overwritten by zeros or just their indices are updated is up
295         //    to debate.
296         SkFAIL("Not implemented!");
297     }
298 
299     // Blit a pattern of pixels defined by a rectangle-clipped mask; We make an
300     // assumption here that if this function gets called, then it will replace all
301     // of the compressed texture blocks that it touches. Hence, two separate calls
302     // to blitMask that have clips next to one another will cause artifacts. Most
303     // of the time, however, this function gets called because constructing the mask
304     // was faster than constructing the RLE for blitAntiH, and this function will
305     // only be called once.
306 #ifdef SK_DEBUG
307     bool fBlitMaskCalled;
308 #endif
blitMask(const SkMask & mask,const SkIRect & clip)309     void blitMask(const SkMask& mask, const SkIRect& clip) override {
310 
311         // Assumptions:
312         SkASSERT(!fBlitMaskCalled);
313         SkDEBUGCODE(fBlitMaskCalled = true);
314         SkASSERT(SkMask::kA8_Format == mask.fFormat);
315         SkASSERT(mask.fBounds.contains(clip));
316 
317         // Start from largest block boundary less than the clip boundaries.
318         const int startI = BlockDim * (clip.left() / BlockDim);
319         const int startJ = BlockDim * (clip.top() / BlockDim);
320 
321         for (int j = startJ; j < clip.bottom(); j += BlockDim) {
322 
323             // Get the destination for this block row
324             uint8_t* dst = this->getBlock(startI, j);
325             for (int i = startI; i < clip.right(); i += BlockDim) {
326 
327                 // At this point, the block should intersect the clip.
328                 SkASSERT(SkIRect::IntersectsNoEmptyCheck(
329                              SkIRect::MakeXYWH(i, j, BlockDim, BlockDim), clip));
330 
331                 // Do we need to pad it?
332                 if (i < clip.left() || j < clip.top() ||
333                     i + BlockDim > clip.right() || j + BlockDim > clip.bottom()) {
334 
335                     uint8_t block[BlockDim*BlockDim];
336                     memset(block, 0, sizeof(block));
337 
338                     const int startX = SkMax32(i, clip.left());
339                     const int startY = SkMax32(j, clip.top());
340 
341                     const int endX = SkMin32(i + BlockDim, clip.right());
342                     const int endY = SkMin32(j + BlockDim, clip.bottom());
343 
344                     for (int y = startY; y < endY; ++y) {
345                         const int col = startX - i;
346                         const int row = y - j;
347                         const int valsWide = endX - startX;
348                         SkASSERT(valsWide <= BlockDim);
349                         SkASSERT(0 <= col && col < BlockDim);
350                         SkASSERT(0 <= row && row < BlockDim);
351                         memcpy(block + row*BlockDim + col,
352                                mask.getAddr8(startX, j + row), valsWide);
353                     }
354 
355                     CompressorType::CompressA8Horizontal(dst, block, BlockDim);
356                 } else {
357                     // Otherwise, just compress it.
358                     uint8_t*const src = mask.getAddr8(i, j);
359                     const uint32_t rb = mask.fRowBytes;
360                     CompressorType::CompressA8Horizontal(dst, src, rb);
361                 }
362 
363                 dst += EncodedBlockSize;
364             }
365         }
366     }
367 
368     // If the blitter just sets a single value for each pixel, return the
369     // bitmap it draws into, and assign value. If not, return NULL and ignore
370     // the value parameter.
justAnOpaqueColor(uint32_t * value)371     const SkBitmap* justAnOpaqueColor(uint32_t* value) override {
372         return NULL;
373     }
374 
375     /**
376      * Compressed texture blitters only really work correctly if they get
377      * BlockDim rows at a time. That being said, this blitter tries it's best
378      * to preserve semantics if blitAntiH doesn't get called in too many
379      * weird ways...
380      */
requestRowsPreserved()381     int requestRowsPreserved() const override { return BlockDim; }
382 
383 private:
384     static const int kPixelsPerBlock = BlockDim * BlockDim;
385 
386     // The longest possible run of pixels that this blitter will receive.
387     // This is initialized in the constructor to 0x7FFE, which is one less
388     // than the largest positive 16-bit integer. We make sure that it's one
389     // less for debugging purposes. We also don't make this variable static
390     // in order to make sure that we can construct a valid pointer to it.
391     const int16_t kLongestRun;
392 
393     // Usually used in conjunction with kLongestRun. This is initialized to
394     // zero.
395     const SkAlpha kZeroAlpha;
396 
397     // This is the information that we buffer whenever we're asked to blit
398     // a row with this blitter.
399     struct BufferedRun {
400         const SkAlpha* fAlphas;
401         const int16_t* fRuns;
402         int fX, fY;
403     } fBufferedRuns[BlockDim];
404 
405     // The next row [0, BlockDim) that we need to blit.
406     int fNextRun;
407 
408     // The width and height of the image that we're blitting
409     const int fWidth;
410     const int fHeight;
411 
412     // The compressed buffer that we're blitting into. It is assumed that the buffer
413     // is large enough to store a compressed image of size fWidth*fHeight.
414     void* const fBuffer;
415 
416     // Various utility functions
blocksWide()417     int blocksWide() const { return fWidth / BlockDim; }
blocksTall()418     int blocksTall() const { return fHeight / BlockDim; }
totalBlocks()419     int totalBlocks() const { return (fWidth * fHeight) / kPixelsPerBlock; }
420 
421     // Returns the block index for the block containing pixel (x, y). Block
422     // indices start at zero and proceed in raster order.
getBlockOffset(int x,int y)423     int getBlockOffset(int x, int y) const {
424         SkASSERT(x < fWidth);
425         SkASSERT(y < fHeight);
426         const int blockCol = x / BlockDim;
427         const int blockRow = y / BlockDim;
428         return blockRow * this->blocksWide() + blockCol;
429     }
430 
431     // Returns a pointer to the block containing pixel (x, y)
getBlock(int x,int y)432     uint8_t *getBlock(int x, int y) const {
433         uint8_t* ptr = reinterpret_cast<uint8_t*>(fBuffer);
434         return ptr + EncodedBlockSize*this->getBlockOffset(x, y);
435     }
436 
437     // Updates the block whose columns are stored in block. curAlphai is expected
438     // to store the alpha values that will be placed within each of the columns in
439     // the range [col, col+colsLeft).
440     typedef uint32_t Column[BlockDim/4];
441     typedef uint32_t Block[BlockDim][BlockDim/4];
updateBlockColumns(Block block,const int col,const int colsLeft,const Column curAlphai)442     inline void updateBlockColumns(Block block, const int col,
443                                    const int colsLeft, const Column curAlphai) {
444         SkASSERT(block);
445         SkASSERT(col + colsLeft <= BlockDim);
446 
447         for (int i = col; i < (col + colsLeft); ++i) {
448             memcpy(block[i], curAlphai, sizeof(Column));
449         }
450     }
451 
452     // The following function writes the buffered runs to compressed blocks.
453     // If fNextRun < BlockDim, then we fill the runs that we haven't buffered with
454     // the constant zero buffer.
flushRuns()455     void flushRuns() {
456         // If we don't have any runs, then just return.
457         if (0 == fNextRun) {
458             return;
459         }
460 
461 #ifndef NDEBUG
462         // Make sure that if we have any runs, they all match
463         for (int i = 1; i < fNextRun; ++i) {
464             SkASSERT(fBufferedRuns[i].fY == fBufferedRuns[i-1].fY + 1);
465             SkASSERT(fBufferedRuns[i].fX == fBufferedRuns[i-1].fX);
466         }
467 #endif
468 
469         // If we don't have as many runs as we have rows, fill in the remaining
470         // runs with constant zeros.
471         for (int i = fNextRun; i < BlockDim; ++i) {
472             fBufferedRuns[i].fY = fBufferedRuns[0].fY + i;
473             fBufferedRuns[i].fX = fBufferedRuns[0].fX;
474             fBufferedRuns[i].fAlphas = &kZeroAlpha;
475             fBufferedRuns[i].fRuns = &kLongestRun;
476         }
477 
478         // Make sure that our assumptions aren't violated.
479         SkASSERT(fNextRun > 0 && fNextRun <= BlockDim);
480         SkASSERT((fBufferedRuns[0].fY % BlockDim) == 0);
481 
482         // The following logic walks BlockDim rows at a time and outputs compressed
483         // blocks to the buffer passed into the constructor.
484         // We do the following:
485         //
486         //      c1 c2 c3 c4
487         // -----------------------------------------------------------------------
488         // ... |  |  |  |  |  ----> fBufferedRuns[0]
489         // -----------------------------------------------------------------------
490         // ... |  |  |  |  |  ----> fBufferedRuns[1]
491         // -----------------------------------------------------------------------
492         // ... |  |  |  |  |  ----> fBufferedRuns[2]
493         // -----------------------------------------------------------------------
494         // ... |  |  |  |  |  ----> fBufferedRuns[3]
495         // -----------------------------------------------------------------------
496         //
497         // curX -- the macro X value that we've gotten to.
498         // c[BlockDim] -- the buffers that represent the columns of the current block
499         //                  that we're operating on
500         // curAlphaColumn -- buffer containing the column of alpha values from fBufferedRuns.
501         // nextX -- for each run, the next point at which we need to update curAlphaColumn
502         //          after the value of curX.
503         // finalX -- the minimum of all the nextX values.
504         //
505         // curX advances to finalX outputting any blocks that it passes along
506         // the way. Since finalX will not change when we reach the end of a
507         // run, the termination criteria will be whenever curX == finalX at the
508         // end of a loop.
509 
510         // Setup:
511         Block block;
512         sk_bzero(block, sizeof(block));
513 
514         Column curAlphaColumn;
515         sk_bzero(curAlphaColumn, sizeof(curAlphaColumn));
516 
517         SkAlpha *curAlpha = reinterpret_cast<SkAlpha*>(&curAlphaColumn);
518 
519         int nextX[BlockDim];
520         for (int i = 0; i < BlockDim; ++i) {
521             nextX[i] = 0x7FFFFF;
522         }
523 
524         uint8_t* outPtr = this->getBlock(fBufferedRuns[0].fX, fBufferedRuns[0].fY);
525 
526         // Populate the first set of runs and figure out how far we need to
527         // advance on the first step
528         int curX = 0;
529         int finalX = 0xFFFFF;
530         for (int i = 0; i < BlockDim; ++i) {
531             nextX[i] = *(fBufferedRuns[i].fRuns);
532             curAlpha[i] = *(fBufferedRuns[i].fAlphas);
533 
534             finalX = SkMin32(nextX[i], finalX);
535         }
536 
537         // Make sure that we have a valid right-bound X value
538         SkASSERT(finalX < 0xFFFFF);
539 
540         // If the finalX is the longest run, then just blit until we have
541         // width...
542         if (kLongestRun == finalX) {
543             finalX = fWidth;
544         }
545 
546         // Run the blitter...
547         while (curX != finalX) {
548             SkASSERT(finalX >= curX);
549 
550             // Do we need to populate the rest of the block?
551             if ((finalX - (BlockDim*(curX / BlockDim))) >= BlockDim) {
552                 const int col = curX % BlockDim;
553                 const int colsLeft = BlockDim - col;
554                 SkASSERT(curX + colsLeft <= finalX);
555 
556                 this->updateBlockColumns(block, col, colsLeft, curAlphaColumn);
557 
558                 // Write this block
559                 CompressorType::CompressA8Vertical(outPtr, reinterpret_cast<uint8_t*>(block));
560                 outPtr += EncodedBlockSize;
561                 curX += colsLeft;
562             }
563 
564             // If we can advance even further, then just keep memsetting the block
565             if ((finalX - curX) >= BlockDim) {
566                 SkASSERT((curX % BlockDim) == 0);
567 
568                 const int col = 0;
569                 const int colsLeft = BlockDim;
570 
571                 this->updateBlockColumns(block, col, colsLeft, curAlphaColumn);
572 
573                 // While we can keep advancing, just keep writing the block.
574                 uint8_t lastBlock[EncodedBlockSize];
575                 CompressorType::CompressA8Vertical(lastBlock, reinterpret_cast<uint8_t*>(block));
576                 while((finalX - curX) >= BlockDim) {
577                     memcpy(outPtr, lastBlock, EncodedBlockSize);
578                     outPtr += EncodedBlockSize;
579                     curX += BlockDim;
580                 }
581             }
582 
583             // If we haven't advanced within the block then do so.
584             if (curX < finalX) {
585                 const int col = curX % BlockDim;
586                 const int colsLeft = finalX - curX;
587 
588                 this->updateBlockColumns(block, col, colsLeft, curAlphaColumn);
589                 curX += colsLeft;
590             }
591 
592             SkASSERT(curX == finalX);
593 
594             // Figure out what the next advancement is...
595             if (finalX < fWidth) {
596                 for (int i = 0; i < BlockDim; ++i) {
597                     if (nextX[i] == finalX) {
598                         const int16_t run = *(fBufferedRuns[i].fRuns);
599                         fBufferedRuns[i].fRuns += run;
600                         fBufferedRuns[i].fAlphas += run;
601                         curAlpha[i] = *(fBufferedRuns[i].fAlphas);
602                         nextX[i] += *(fBufferedRuns[i].fRuns);
603                     }
604                 }
605 
606                 finalX = 0xFFFFF;
607                 for (int i = 0; i < BlockDim; ++i) {
608                     finalX = SkMin32(nextX[i], finalX);
609                 }
610             } else {
611                 curX = finalX;
612             }
613         }
614 
615         // If we didn't land on a block boundary, output the block...
616         if ((curX % BlockDim) > 0) {
617 #ifdef SK_DEBUG
618             for (int i = 0; i < BlockDim; ++i) {
619                 SkASSERT(nextX[i] == kLongestRun || nextX[i] == curX);
620             }
621 #endif
622             const int col = curX % BlockDim;
623             const int colsLeft = BlockDim - col;
624 
625             memset(curAlphaColumn, 0, sizeof(curAlphaColumn));
626             this->updateBlockColumns(block, col, colsLeft, curAlphaColumn);
627 
628             CompressorType::CompressA8Vertical(outPtr, reinterpret_cast<uint8_t*>(block));
629         }
630 
631         fNextRun = 0;
632     }
633 
634 #if PEDANTIC_BLIT_RECT
updateBlockRow(int x,int y,int width,int height,int blockRow,int startBlockX,int endBlockX)635     void updateBlockRow(int x, int y, int width, int height,
636                         int blockRow, int startBlockX, int endBlockX) {
637         if (0 == width || 0 == height || startBlockX == endBlockX) {
638             return;
639         }
640 
641         uint8_t* dst = this->getBlock(startBlockX, BlockDim * (y / BlockDim));
642 
643         // One horizontal strip to update
644         uint8_t mask[BlockDim*BlockDim];
645         memset(mask, 0, sizeof(mask));
646 
647         // Update the left cap
648         int blockX = startBlockX;
649         const int yoff = y - blockRow;
650         for (int j = 0; j < height; ++j) {
651             const int xoff = x - blockX;
652             memset(mask + (j + yoff)*BlockDim + xoff, 0xFF, BlockDim - xoff);
653         }
654         CompressorType::UpdateBlock(dst, mask, BlockDim, mask);
655         dst += EncodedBlockSize;
656         blockX += BlockDim;
657 
658         // Update the middle
659         if (blockX < endBlockX) {
660             for (int j = 0; j < height; ++j) {
661                 memset(mask + (j + yoff)*BlockDim, 0xFF, BlockDim);
662             }
663             while (blockX < endBlockX) {
664                 CompressorType::UpdateBlock(dst, mask, BlockDim, mask);
665                 dst += EncodedBlockSize;
666                 blockX += BlockDim;
667             }
668         }
669 
670         SkASSERT(endBlockX == blockX);
671 
672         // Update the right cap (if we need to)
673         if (x + width > endBlockX) {
674             memset(mask, 0, sizeof(mask));
675             for (int j = 0; j < height; ++j) {
676                 const int xoff = (x+width-blockX);
677                 memset(mask + (j+yoff)*BlockDim, 0xFF, xoff);
678             }
679             CompressorType::UpdateBlock(dst, mask, BlockDim, mask);
680         }
681     }
682 
updateBlockCol(int x,int y,int width,int height,int blockCol,int startBlockY,int endBlockY)683     void updateBlockCol(int x, int y, int width, int height,
684                         int blockCol, int startBlockY, int endBlockY) {
685         if (0 == width || 0 == height || startBlockY == endBlockY) {
686             return;
687         }
688 
689         // One vertical strip to update
690         uint8_t mask[BlockDim*BlockDim];
691         memset(mask, 0, sizeof(mask));
692         const int maskX0 = x - blockCol;
693         const int maskWidth = maskX0 + width;
694         SkASSERT(maskWidth <= BlockDim);
695 
696         // Update the top cap
697         int blockY = startBlockY;
698         for (int j = (y - blockY); j < BlockDim; ++j) {
699             memset(mask + maskX0 + j*BlockDim, 0xFF, maskWidth);
700         }
701         CompressorType::UpdateBlock(this->getBlock(blockCol, blockY), mask, BlockDim, mask);
702         blockY += BlockDim;
703 
704         // Update middle
705         if (blockY < endBlockY) {
706             for (int j = 0; j < BlockDim; ++j) {
707                 memset(mask + maskX0 + j*BlockDim, 0xFF, maskWidth);
708             }
709             while (blockY < endBlockY) {
710                 CompressorType::UpdateBlock(this->getBlock(blockCol, blockY),
711                                             mask, BlockDim, mask);
712                 blockY += BlockDim;
713             }
714         }
715 
716         SkASSERT(endBlockY == blockY);
717 
718         // Update bottom
719         if (y + height > endBlockY) {
720             for (int j = y+height; j < endBlockY + BlockDim; ++j) {
721                 memset(mask + (j-endBlockY)*BlockDim, 0, BlockDim);
722             }
723             CompressorType::UpdateBlock(this->getBlock(blockCol, blockY),
724                                         mask, BlockDim, mask);
725         }
726     }
727 #endif  // PEDANTIC_BLIT_RECT
728 
729 };
730 
731 }  // namespace SkTextureCompressor
732 
733 #endif  // SkTextureCompressor_Blitter_DEFINED
734